DistributedLock icon indicating copy to clipboard operation
DistributedLock copied to clipboard

Semaphores - differing slot counts - is there any solution for this issue?

Open dazinator opened this issue 3 years ago • 3 comments

From here

Whenever a distributed semaphore is created, you must specify the max count value as well as the name. Specifying the same name with different max count values on different instances of a semaphore has undefined and unpredictable results, so make sure to use the same max count value for each instance of a particular semaphore.

I am wondering if there is any possible solution for this issue. Should implementations have to share / store info about existing semaphore names and slot counts so that clients can check the count matches or throw an error for example?

dazinator avatar Mar 19 '21 13:03 dazinator

  • the reason I think this might be a problem is in a microservice landscape, some services might be updated before others. If multiple services are using the semaphore and you want to change the slot count, you might have a new version of the service with the new slot count deployed and running for a time, before the other services are upgraded. Ideally that shouldn't happen but if it did, it would be safer to see exceptions at the point of locks being consumed than it would to allow undefined behaviour.

dazinator avatar Mar 19 '21 13:03 dazinator

One solution (workaround) I can think of involves consumers of this library reading their semaphore slot counts from some global config. So for example you might have some globally accessbile config file like so

{
"MySemaphor": 4,
"MyOtherSemaphore": 2
}

And in application code, you'd want your application to be responsive to changes in that config when creating sempahores:

// uses the Redis implementation; others are available
var latestSemaphoreConfig = GetSemaphoreConfig("MySemaphor");
var semaphore = new RedisDistributedSemaphore("MySemaphor", latestSemaphoreConfig.MaxCount, database: database);
using (semaphore.Acquire())
{   
    UseComputeDatabase();
}

This basically pushes the responsibility to the client application to workaround this issue which I think is viable. However it doesn't technically prevent mis-use or other processes using the semaphore and specifying incorrect slot counts, so it doesn't really solve the problem fully.

I'm thinking that provider of the semaphore (i.e redis, sql etc) is in the best position to maintain some information about the sempahore names in use and what he slot counts are. If this could be "checked" when new semaphores are created with the same name, it would be possible to throw an exception on any disparity.

dazinator avatar Mar 19 '21 13:03 dazinator

In practice, different implementations will behave differently in the scenario where the max counts vary. For example, with a Redis semaphore, callers are effectively waiting in a queue until they end up as one of the top N waiters. If two callers disagree on N, they will simply disagree about when they think they've taken the semaphore but they won't corrupt the state or anything.

The SQL semaphore isn't fair in the same way; callers iterate over a the list of tickets and try to claim them repeatedly. Therefore, the behavior depends on which tickets are held. Again, state won't be corrupted.

Using WaitHandle-based semaphores, we're at the mercy of whatever the native Windows behavior is for this.

This variance makes me not want to commit to any sort of hard guarantee about the behavior.

For someone who expects to be changing their semaphore counts, the best approach would be to pull them from a configuration system that updates quickly like you said. Alternatively, they should be able to easily test what the particular behavior is for the implementation they are using (or ask and I'll document!); they can then decide whether they need extra guardrails.

madelson avatar Mar 20 '21 12:03 madelson