-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
PHPORM-99 Add optimized cache and lock drivers #2877
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
<?php | ||
|
||
namespace MongoDB\Laravel\Cache; | ||
|
||
use Illuminate\Cache\Lock; | ||
use MongoDB\Laravel\Collection; | ||
use MongoDB\Operation\FindOneAndUpdate; | ||
use Override; | ||
|
||
use function random_int; | ||
|
||
final class MongoLock extends Lock | ||
{ | ||
/** | ||
* Create a new lock instance. | ||
* | ||
* @param Collection $collection The MongoDB collection | ||
* @param string $name Name of the lock | ||
* @param int $seconds Time-to-live of the lock in seconds | ||
* @param string|null $owner A unique string that identifies the owner. Random if not set | ||
* @param array $lottery The prune probability odds | ||
* @param int $defaultTimeoutInSeconds The default number of seconds that a lock should be held | ||
jmikola marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
public function __construct( | ||
private readonly Collection $collection, | ||
string $name, | ||
int $seconds, | ||
?string $owner = null, | ||
private readonly array $lottery = [2, 100], | ||
private readonly int $defaultTimeoutInSeconds = 86400, | ||
) { | ||
parent::__construct($name, $seconds, $owner); | ||
} | ||
|
||
/** | ||
* Attempt to acquire the lock. | ||
*/ | ||
public function acquire(): bool | ||
{ | ||
// The lock can be acquired if: it doesn't exist, it has expired, | ||
// or it is already owned by the same lock instance. | ||
$isExpiredOrAlreadyOwned = [ | ||
'$or' => [ | ||
['$lte' => ['$expiration', $this->currentTime()]], | ||
['$eq' => ['$owner', $this->owner]], | ||
], | ||
]; | ||
$result = $this->collection->findOneAndUpdate( | ||
['_id' => $this->name], | ||
[ | ||
[ | ||
'$set' => [ | ||
'owner' => [ | ||
'$cond' => [ | ||
'if' => $isExpiredOrAlreadyOwned, | ||
'then' => $this->owner, | ||
'else' => '$owner', | ||
], | ||
], | ||
'expiration' => [ | ||
'$cond' => [ | ||
'if' => $isExpiredOrAlreadyOwned, | ||
'then' => $this->expiresAt(), | ||
'else' => '$expiration', | ||
], | ||
], | ||
], | ||
], | ||
], | ||
[ | ||
'upsert' => true, | ||
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER, | ||
'projection' => ['owner' => 1], | ||
], | ||
); | ||
|
||
if (random_int(1, $this->lottery[1]) <= $this->lottery[0]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize this is beyond the PR, but is there a particular reason that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comes from Laravel's Lottery: https://laravel.com/api/11.x/Illuminate/Support/Lottery.html Used in DatabaseLock config: https://github.com/laravel/framework/blob/9b3bb15ab4d8e7a80bda82ffbe92613f15f69fee/src/Illuminate/Cache/DatabaseLock.php#L49 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see see the same structure used in DatabaseLock, but not the Lottery class itself. Consistency is probably more important here. It may be worth elaborating on the structure of that two-element array in the doc block comment, though. As is, it's not entirely clear how the two values relate to one another. |
||
$this->collection->deleteMany(['expiration' => ['$lte' => $this->currentTime()]]); | ||
} | ||
|
||
return $result['owner'] === $this->owner; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted to use array-access to please static analysis tools that doesn't understand that https://phpstan.org/r/1e7b5d92-8b98-4e53-863f-b0c8652820c1 $doc = new ArrayObject(['foo' => 'bar'], ArrayObject::ARRAY_AS_PROPS);
$doc->bar; It's also more robust in case |
||
} | ||
|
||
/** | ||
* Release the lock. | ||
*/ | ||
#[Override] | ||
public function release(): bool | ||
{ | ||
$result = $this->collection | ||
->deleteOne([ | ||
'_id' => $this->name, | ||
'owner' => $this->owner, | ||
]); | ||
|
||
return $result->getDeletedCount() > 0; | ||
} | ||
|
||
/** | ||
* Releases this lock in disregard of ownership. | ||
*/ | ||
#[Override] | ||
public function forceRelease(): void | ||
{ | ||
$this->collection->deleteOne([ | ||
'_id' => $this->name, | ||
]); | ||
} | ||
|
||
/** | ||
* Returns the owner value written into the driver for this lock. | ||
*/ | ||
#[Override] | ||
protected function getCurrentOwner(): ?string | ||
{ | ||
return $this->collection->findOne( | ||
[ | ||
'_id' => $this->name, | ||
'expiration' => ['$gte' => $this->currentTime()], | ||
], | ||
['projection' => ['owner' => 1]], | ||
)['owner'] ?? null; | ||
} | ||
|
||
/** | ||
* Get the UNIX timestamp indicating when the lock should expire. | ||
*/ | ||
private function expiresAt(): int | ||
{ | ||
$lockTimeout = $this->seconds > 0 ? $this->seconds : $this->defaultTimeoutInSeconds; | ||
|
||
return $this->currentTime() + $lockTimeout; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Required to access
Cache::isOwnedCurrentProcess
laravel/framework@234444e