From 6e6f54d433ddb2564f23fc3791944b1aec0bcb2c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 1 May 2020 16:56:46 -0500 Subject: [PATCH 1/7] work on database lock support --- src/Illuminate/Cache/CacheManager.php | 6 +- src/Illuminate/Cache/DatabaseLock.php | 138 +++++++++++++++++++++++++ src/Illuminate/Cache/DatabaseStore.php | 56 +++++++++- 3 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/Illuminate/Cache/DatabaseLock.php diff --git a/src/Illuminate/Cache/CacheManager.php b/src/Illuminate/Cache/CacheManager.php index 1aa9331f584..65007e75e40 100755 --- a/src/Illuminate/Cache/CacheManager.php +++ b/src/Illuminate/Cache/CacheManager.php @@ -214,7 +214,11 @@ protected function createDatabaseDriver(array $config) return $this->repository( new DatabaseStore( - $connection, $config['table'], $this->getPrefix($config) + $connection, + $config['table'], + $this->getPrefix($config), + $config['lock_table'] ?? 'cache_locks', + $config['lock_lottery'] ?? [2, 100], ) ); } diff --git a/src/Illuminate/Cache/DatabaseLock.php b/src/Illuminate/Cache/DatabaseLock.php new file mode 100644 index 00000000000..e715e095e58 --- /dev/null +++ b/src/Illuminate/Cache/DatabaseLock.php @@ -0,0 +1,138 @@ +connection = $connection; + $this->table = $table; + $this->lottery = $lottery; + } + + /** + * Attempt to acquire the lock. + * + * @return bool + */ + public function acquire() + { + $acquired = false; + + try { + $this->connection->table($this->table)->insert([ + 'id' => $this->name, + 'owner' => $this->owner, + 'expires_at' => $this->expiresAt() + ]); + + $acquired = true; + } catch (QueryException $e) { + $updated = $this->connection->table($this->table) + ->where('id', $this->name) + ->where(function ($query) { + return $query->where('owner', $this->owner)->orWhere('expires_at', '<=', time()); + })->update([ + 'owner' => $this->owner, + 'expires_at' => $this->expiresAt() + ]); + + $acquired = $updated >= 1; + } + + if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) { + $this->connection->table($this->table)->where('expires_at', '<=', time())->delete(); + } + + return $acquired; + } + + /** + * Get the UNIX timestamp indicating when the lock should expire. + * + * @return int + */ + protected function expiresAt() + { + return $this->seconds > 0 ? time() + $this->seconds : now()->addDays(1)->getTimestamp(); + } + + /** + * Release the lock. + * + * @return bool + */ + public function release() + { + if ($this->isOwnedByCurrentProcess()) { + $this->connection->table($this->table) + ->where('id', $this->name) + ->where('owner', $this->owner) + ->delete(); + + return true; + } + + return false; + } + + /** + * Releases this lock in disregard of ownership. + * + * @return void + */ + public function forceRelease() + { + $this->connection->table($this->table) + ->where('id', $this->name) + ->delete(); + } + + /** + * Returns the owner value written into the driver for this lock. + * + * @return string + */ + protected function getCurrentOwner() + { + return optional($this->connection->table($this->table)->where('id', $this->name)->first())->token; + } +} diff --git a/src/Illuminate/Cache/DatabaseStore.php b/src/Illuminate/Cache/DatabaseStore.php index 48844ebd740..3263b2fee7e 100755 --- a/src/Illuminate/Cache/DatabaseStore.php +++ b/src/Illuminate/Cache/DatabaseStore.php @@ -35,19 +35,41 @@ class DatabaseStore implements Store */ protected $prefix; + /** + * The name of the cache locks table. + * + * @var string + */ + protected $lockTable; + + /** + * A array representation of the lock lottery odds. + * + * @var string + */ + protected $lockLottery; + /** * Create a new database store. * * @param \Illuminate\Database\ConnectionInterface $connection * @param string $table * @param string $prefix + * @param string $lockTable + * @param array $lockLottery * @return void */ - public function __construct(ConnectionInterface $connection, $table, $prefix = '') + public function __construct(ConnectionInterface $connection, + $table, + $prefix = '', + $lockTable = 'cache_locks', + $lockLottery = [2, 100]) { $this->table = $table; $this->prefix = $prefix; $this->connection = $connection; + $this->lockTable = $lockTable; + $this->lockLottery = $lockLottery; } /** @@ -205,6 +227,38 @@ public function forever($key, $value) return $this->put($key, $value, 315360000); } + /** + * Get a lock instance. + * + * @param string $name + * @param int $seconds + * @param string|null $owner + * @return \Illuminate\Contracts\Cache\Lock + */ + public function lock($name, $seconds = 0, $owner = null) + { + return new DatabaseLock( + $this->connection, + $this->lockTable, + $this->prefix.$name, + $seconds, + $owner, + $this->lockLottery + ); + } + + /** + * Restore a lock instance using the owner identifier. + * + * @param string $name + * @param string $owner + * @return \Illuminate\Contracts\Cache\Lock + */ + public function restoreLock($name, $owner) + { + return $this->lock($name, 0, $owner); + } + /** * Remove an item from the cache. * From 1f8c8bdd76d45a5682f2bd23f6040eeeb3acb8ca Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 1 May 2020 16:58:05 -0500 Subject: [PATCH 2/7] Apply fixes from StyleCI (#32640) --- src/Illuminate/Cache/DatabaseLock.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Cache/DatabaseLock.php b/src/Illuminate/Cache/DatabaseLock.php index e715e095e58..ec6d844bdd7 100644 --- a/src/Illuminate/Cache/DatabaseLock.php +++ b/src/Illuminate/Cache/DatabaseLock.php @@ -61,7 +61,7 @@ public function acquire() $this->connection->table($this->table)->insert([ 'id' => $this->name, 'owner' => $this->owner, - 'expires_at' => $this->expiresAt() + 'expires_at' => $this->expiresAt(), ]); $acquired = true; @@ -71,9 +71,9 @@ public function acquire() ->where(function ($query) { return $query->where('owner', $this->owner)->orWhere('expires_at', '<=', time()); })->update([ - 'owner' => $this->owner, - 'expires_at' => $this->expiresAt() - ]); + 'owner' => $this->owner, + 'expires_at' => $this->expiresAt(), + ]); $acquired = $updated >= 1; } From d23616892cf3d58781606047983a7cf481c19fb8 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Fri, 1 May 2020 17:31:41 -0500 Subject: [PATCH 3/7] Update CacheManager.php --- src/Illuminate/Cache/CacheManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/CacheManager.php b/src/Illuminate/Cache/CacheManager.php index 65007e75e40..73f4acc35f7 100755 --- a/src/Illuminate/Cache/CacheManager.php +++ b/src/Illuminate/Cache/CacheManager.php @@ -218,7 +218,7 @@ protected function createDatabaseDriver(array $config) $config['table'], $this->getPrefix($config), $config['lock_table'] ?? 'cache_locks', - $config['lock_lottery'] ?? [2, 100], + $config['lock_lottery'] ?? [2, 100] ) ); } From 3b06a5c29e62ba9f1cda4ecd1077c3881af9d251 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 4 May 2020 10:23:26 +0100 Subject: [PATCH 4/7] Fixed typo --- src/Illuminate/Cache/DatabaseLock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/DatabaseLock.php b/src/Illuminate/Cache/DatabaseLock.php index ec6d844bdd7..5b21b62e2e0 100644 --- a/src/Illuminate/Cache/DatabaseLock.php +++ b/src/Illuminate/Cache/DatabaseLock.php @@ -24,7 +24,7 @@ class DatabaseLock extends Lock /** * The prune probability odds. * - * @var string + * @var array */ protected $lottery; From 8385c0b17d2c750af06ac421615fee9f937a4f19 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 4 May 2020 10:33:49 +0100 Subject: [PATCH 5/7] Update DatabaseStore.php --- src/Illuminate/Cache/DatabaseStore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/DatabaseStore.php b/src/Illuminate/Cache/DatabaseStore.php index 3263b2fee7e..89f095554e1 100755 --- a/src/Illuminate/Cache/DatabaseStore.php +++ b/src/Illuminate/Cache/DatabaseStore.php @@ -45,7 +45,7 @@ class DatabaseStore implements Store /** * A array representation of the lock lottery odds. * - * @var string + * @var array */ protected $lockLottery; From 62a635d2bfb335b968bd2486c9fa5457753c172c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 4 May 2020 10:04:23 -0500 Subject: [PATCH 6/7] add tests --- src/Illuminate/Cache/DatabaseLock.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/DatabaseLock.php b/src/Illuminate/Cache/DatabaseLock.php index 5b21b62e2e0..9f4b266a91f 100644 --- a/src/Illuminate/Cache/DatabaseLock.php +++ b/src/Illuminate/Cache/DatabaseLock.php @@ -133,6 +133,6 @@ public function forceRelease() */ protected function getCurrentOwner() { - return optional($this->connection->table($this->table)->where('id', $this->name)->first())->token; + return optional($this->connection->table($this->table)->where('id', $this->name)->first())->owner; } } From 54d27d612cee3ed50bcc9b14270c69c19c6461fc Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 4 May 2020 10:04:29 -0500 Subject: [PATCH 7/7] add fiel --- .../Integration/Database/DatabaseLockTest.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/Integration/Database/DatabaseLockTest.php diff --git a/tests/Integration/Database/DatabaseLockTest.php b/tests/Integration/Database/DatabaseLockTest.php new file mode 100644 index 00000000000..6ec6e567e0a --- /dev/null +++ b/tests/Integration/Database/DatabaseLockTest.php @@ -0,0 +1,65 @@ +string('id')->primary(); + $table->string('owner'); + $table->integer('expires_at'); + }); + } + + public function testLockCanBeAcquired() + { + $lock = Cache::driver('database')->lock('foo'); + $this->assertTrue($lock->get()); + + $otherLock = Cache::driver('database')->lock('foo'); + $this->assertFalse($otherLock->get()); + + $lock->release(); + + $otherLock = Cache::driver('database')->lock('foo'); + $this->assertTrue($otherLock->get()); + + $otherLock->release(); + } + + public function testLockCanBeForceReleased() + { + $lock = Cache::driver('database')->lock('foo'); + $this->assertTrue($lock->get()); + + $otherLock = Cache::driver('database')->lock('foo'); + $otherLock->forceRelease(); + $this->assertTrue($otherLock->get()); + + $otherLock->release(); + } + + public function testExpiredLockCanBeRetrieved() + { + $lock = Cache::driver('database')->lock('foo'); + $this->assertTrue($lock->get()); + DB::table('cache_locks')->update(['expires_at' => now()->subDays(1)->getTimestamp()]); + + $otherLock = Cache::driver('database')->lock('foo'); + $this->assertTrue($otherLock->get()); + + $otherLock->release(); + } +}