WCF — Cross-machine semaphore with WCF

Came across an inter­est­ing ques­tion on Stack­Over­flow on how one might be able to throt­tle the num­ber of requests across mul­ti­ple servers run­ning the same WCF ser­vice. So for instance, if you have 3 servers sit­ting behind a load bal­ancer and for one rea­son or anoth­er you can only allow 5 requests to be made against the ser­vice at any moment in time and any sub­se­quent requests need to be queued until one of the pre­vi­ous requests fin­ish.

For those of you famil­iar with the pro­gram­ming con­cept of a sem­a­phore, you might see that the above require­ment describes a sem­a­phore which applies across mul­ti­ple machines. A quick search on Google for ‘cross-machine sem­a­phore’ reveals sev­er­al imple­men­ta­tions of such sys­tem using mem­cached.

Nat­u­ral­ly, a dis­trib­uted cache is a good way to go about imple­ment­ing a cross-machine sem­a­phore IF you are already using it for some­thing else. Oth­er­wise the over­head and cost of run­ning a dis­trib­uted cache clus­ter pure­ly for the sake of a cross-machine sem­a­phore makes this approach a no-go for most of us..

Instead, you could eas­i­ly imple­ment a sem­a­phore ser­vice to pro­vide the same func­tion­al­i­ty to mul­ti­ple WCF clients as the bog-stan­dard Sem­a­phore class do to mul­ti­ple threads. Such a ser­vice might look some­thing like this:

[ServiceContract]
public interface ISemaphorService
{
    [OperationContract]
    void Acquire();

    [OperationContract]
    void Release();
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class SemaphoreService
{
    private readonly static Semaphore Pool = new Semaphore(5, 5);

    public void Acquire()
    {
        Pool.WaitOne();
    }

    public void Release()
    {
        Pool.Release();
    }
}

This approach will intro­duce a sin­gle point of fail­ure as for sem­a­phore ser­vice to work cor­rect­ly it’ll need to be run­ning on a sin­gle machine. If you were to use this approach you should have build up some infra­struc­ture around it so that you can recov­er swift­ly if and when the serv­er run­ning the sem­a­phore ser­vice goes down.

In terms of the client code you should make sure that Release is called for every Acquire call, so would be a good idea to put a try-final­ly block around it:

var client = new SemaphoreServiceClient();

try
{
    // acquire the semaphore before processing the request
    client.Acquire();

    // process request
    ...
}
finally
{
    // always remember the release the semaphore
    client.Release();
}