You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The locks returned by FileStore are instances of the class CacheLock. CacheLock's implementation of the acquire method is not atomic when the lock timeout is 0 (the default):
When the lock timeout is 0, first a get and then a put is performed. When combined, these operations are not atomic and thus multiple actors can enter the critical section protected by the lock. The possibility for race conditions was already discussed in the PR #35139 that introduced the change.
Proposed quick fix
A quick fix would be to always call $this->store->add even if $this->seconds <= 0 because the store can handle 0 timeouts just fine:
There is no need to even check that the add method exists because it is the stores responsibility to return a compatible lock instance.
Proposed proper fix
The proper fix would be to write a custom lock implementation for each store. There already exists DatabaseLock, RedisLock, DynamoDbLock etc. which can guarantee mutual exclusion because their implementation is tailored to the specific store. There is no good way to write an abstract lock implementation that CacheLock attempts to be because the Store contract does not provide the necessary atomic primitives. Calling an undocumented add method on the store, which may or may not be atomic, seems like the wrong thing to do.
The text was updated successfully, but these errors were encountered:
hbgl
changed the title
Atomic locks do not provide mutual exclusion when using the file cache driver
Atomic locks do not provide reliable mutual exclusion when using the file cache driver
Aug 9, 2022
Description:
The locks returned by FileStore are instances of the class CacheLock. CacheLock's implementation of the acquire method is not atomic when the lock timeout is 0 (the default):
framework/src/Illuminate/Cache/CacheLock.php
Lines 30 to 50 in 053840f
When the lock timeout is 0, first a
get
and then aput
is performed. When combined, these operations are not atomic and thus multiple actors can enter the critical section protected by the lock. The possibility for race conditions was already discussed in the PR #35139 that introduced the change.Proposed quick fix
A quick fix would be to always call
$this->store->add
even if$this->seconds <= 0
because the store can handle 0 timeouts just fine:There is no need to even check that the
add
method exists because it is the stores responsibility to return a compatible lock instance.Proposed proper fix
The proper fix would be to write a custom lock implementation for each store. There already exists DatabaseLock, RedisLock, DynamoDbLock etc. which can guarantee mutual exclusion because their implementation is tailored to the specific store. There is no good way to write an abstract lock implementation that CacheLock attempts to be because the Store contract does not provide the necessary atomic primitives. Calling an undocumented
add
method on the store, which may or may not be atomic, seems like the wrong thing to do.Steps To Reproduce:
I created a a repo that can pretty reliably reproduce the race condition:
https://github.com/hbgl/laravel-test-locks
Instructions:
git clone https://github.com/hbgl/laravel-test-locks cd laravel-test-locks composer install php artisan app:test-locks --workers=2 --iterations=1000 --cache-driver=file
The text was updated successfully, but these errors were encountered: