Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.x] Fix race condition in locks issued by the file cache driver #46011

Merged
merged 4 commits into from Feb 13, 2023

Conversation

bytestream
Copy link
Contributor

Fixes #43627

Before:

root@fa0e2058d0ad:/app# php artisan app:test-locks --workers=2 --iterations=1000 --cache-driver=file
Running 1000 iterations with 2 workers using the cache driver 'file' for locking.
Process 1/2 started.
Process 2/2 started.
Process 1/2 finished.
Process 2/2 finished.
Process 1/2 started at 0 and ended at 1938.
Process 2/2 started at 0 and ended at 1932.
Counted only to 1938 instead of the expected 2000.

After:

root@fa0e2058d0ad:/app# php artisan app:test-locks --workers=2 --iterations=1000 --cache-driver=file
Running 1000 iterations with 2 workers using the cache driver 'file' for locking.
Process 1/2 started.
Process 2/2 started.
Process 1/2 finished.
Process 2/2 finished.
Process 1/2 started at 0 and ended at 1922.
Process 2/2 started at 26 and ended at 2000.
Successfully counted to 2000.

@taylorotwell
Copy link
Member

Please explain how this changes anything? The FileStore already has an add method so it would already be falling into this conditional block anyways?

@bytestream
Copy link
Contributor Author

@taylorotwell the problem is when $this->seconds = 0 it falls to the get / put combination which isn't atomic. The add method in the file driver is able to handle when $seconds = 0, and treats it as cache forever

@bytestream
Copy link
Contributor Author

@taylorotwell 362fc83 is a cleaner way of handling it while maintaining BC

@taylorotwell
Copy link
Member

Are other drivers not able to handle $seconds = 0?

@taylorotwell
Copy link
Member

I also don't agree your second commit is necessarily better. The first was simpler?

@bytestream
Copy link
Contributor Author

bytestream commented Feb 9, 2023

@taylorotwell CacheLock and HasCacheLock are only actually used by the file driver. They're a legacy from #35139 which originally added APC driver support as well, but was removed before it was merged. The simplest fix may just be the one that was originally suggested in #43661. If it were me though, I'd remove the HasCacheLock and rename CacheLock to FileLock. That's clearer and also follows the way other drivers are implemented. That's obviously a BC break though which is why I didn't go that route...

@taylorotwell
Copy link
Member

taylorotwell commented Feb 9, 2023

Sorry - I'm not following at all but I don't have time to dig deeper at the moment.

  1. Can other drivers not handle $seconds = 0?

  2. Why is this not the simplest fix and easier?

CleanShot 2023-02-09 at 16 55 37@2x

@taylorotwell
Copy link
Member

For what it's worth: I've updated the docs to not even show an example of not providing an expiration time (in seconds) to the lock methods.

@bytestream
Copy link
Contributor Author

bytestream commented Feb 13, 2023

Can other drivers not handle $seconds = 0?

TLDR; not relevant as CacheLock is only used by the FileStore. To directly answer the question though:

  • APC - ❌ , missing add method
  • Array - 🆗 , missing add method, but ArrayLock handles $seconds = 0
  • Database - 🆗 , providing $seconds = 0 to add() results in immediate expiry, but DatabaseLock uses expiry of 1 day (86400 seconds) if $seconds = 0
  • DynamoDB - 🆗 , providing $seconds = 0 to add() results in immediate expiry, but DynamoDbLock uses expiry of 1 day (86400 seconds) if $seconds = 0
  • File - 🆗 , expiry is set to 9999999999 in add()
  • Memcached - 🆗 , 0 means never expire - https://www.php.net/manual/en/memcache.add.php
  • Null - ❌ , missing add method
  • Redis - 🆗 , providing $seconds = 0 to add() expires after 1 second, but RedisLock correctly handles $seconds = 0

Why is this not the simplest fix and easier?

Simplest fix in the sense that it's a 1 line change, but given CacheLock is only used by the FileStore it really makes sense to refactor the usage. #43661 is the simplest fix IMO without any additional refactoring, and has no breaking changes / consequences.

@taylorotwell taylorotwell merged commit fcb6558 into laravel:9.x Feb 13, 2023
@bytestream bytestream deleted the bugfix/file-lock-race-condition branch February 14, 2023 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Atomic locks do not provide reliable mutual exclusion when using the file cache driver
2 participants