Open todofixthis opened 7 years ago
Hmmm ... yes, it would be possible, good thinking.
If process 3 would be trying to get the lock at the same time it's released, it would be possible.
How likely is this to happen is up for debate :)
This proved to be a real issue for my project, especially when using this for operations that involve high concurrency. What we observed was some of the operations needing the lock would block for the full timeout even though the lock should have already been released by another process.
I just don't think you can use BLPOP for this, although it was a good idea. If you are looking for a more battle-tested solution, I would just use the redis-py lock: https://github.com/andymccurdy/redis-py/blob/master/redis/lock.py
@grjones I think you have an entirely different issue ("block for the full timeout"?!?), perhaps open another issue and give details? Redis-py's lock implementation has even worse "fairness".
As for this issue, if anyone has any idea on how to make the release part more fair (iow, disallow clients jumping the queue if they try to acquire exactly when lock is released) please write your thoughts.
@todofixthis how about this change: replace the setnx with a script that does a LLEN
on the signal list before the setnx - if it's nonempty then would be the same as setnx failing (and would fix your problem).
Actually scratch that, llen would most always be 0 (an element is added there on unlock).
I don't really think I can fix this ...
Hm. Yeah, I think you're right. The only ways I can think of to get around this require transactions, but transactions are allergic to blocking commands. Hm.
Maybe there's a way using redis streams but I have to wonder if this is worth fixing.
If anyone wants to take a stab there's the https://github.com/ionelmc/python-redis-lock/tree/race-on-unlock branch with a test for this.
my implementation uses a sorted set, where the 'score' is the timestamp the client joined the queue and the value is their own unique client key. When the lock is released, each awoken client checks if they have the lowest score, and executes SETNX if they do. If not, they insert their score/key into a second temporary sorted set, wait 2 seconds, then if they are the lowest try a SETNX again. If they are successful, discard any entries from the main sortedset with a score lower than theirs. This allows those at the front to have a short grace period before other clients can jump in, while still preserving FIFO for whoever is listening to the queue.
Consider a situation with two processes:
BLPOP
to return.Now, suppose that the following sequence of events unfolds:
UNLOCK
script to release the lock.BLPOP
returns.SETNX
, Process 3 callsSETNX
, thereby jumping to the front of the queue and making Process 2 very cross indeed.Am I thinking about this correctly, or is this not actually possible?