-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Lock] Added Symfony/Component/Lock/Store/MongoDbStore #27346
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
Changes from 1 commit
157fcd7
e73f663
3838412
1a660e6
d65fddf
f3cc220
ec8d217
a1a3be7
1b150ba
73bb802
5e86904
8572c21
dbdad36
84c4d15
d577191
2ce68f5
5446b11
a9b85d6
6fdc602
9d80a0d
1ac702a
4ba5b41
58ef873
36c3af4
168f194
abc72f8
e062bf2
f2e23b4
97e5711
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…le fields
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,8 @@ | |
use Symfony\Component\Lock\Exception\InvalidArgumentException; | ||
use Symfony\Component\Lock\Exception\LockConflictedException; | ||
use Symfony\Component\Lock\Exception\LockExpiredException; | ||
use Symfony\Component\Lock\Exception\LockStorageException; | ||
use Symfony\Component\Lock\Exception\NotSupportedException; | ||
use Symfony\Component\Lock\Key; | ||
use Symfony\Component\Lock\StoreInterface; | ||
|
||
|
@@ -31,34 +33,36 @@ class MongoDbStore implements StoreInterface | |
|
||
/** | ||
* @param \MongoDB\Client $mongo | ||
* @param array $options | ||
* @param array $options | ||
* | ||
* database: The name of the database [required] | ||
* collection: The name of the collection [default: lock] | ||
* resource_field: The field name for storing the lock id [default: _id] MUST be uniquely indexed if you chage it | ||
* token_field: The field name for storing the lock token [default: token] | ||
* acquired_field: The field name for storing the acquisition timestamp [default: acquired_at] | ||
* expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. | ||
* database: The name of the database [required] | ||
* collection: The name of the collection [default: lock] | ||
* | ||
* It is strongly recommended to put an index on the `expiry_field` for | ||
* garbage-collection. Alternatively it's possible to automatically expire | ||
* the locks in the database as described below: | ||
* A TTL index MUST BE used on MongoDB 2.2+ to automatically clean up expired locks. | ||
* Please be aware any clock drift between the application and mongo servers could | ||
* cause locks to be released prematurely. To account for any drift; | ||
* expireAfterSeconds can be set to a value higher than 0. The logical expiry of | ||
* locks is handled by the application so setting a higher ``expireAfterSeconds`` | ||
* has no effect other than keeping stale data for longer. | ||
* | ||
* A TTL collections can be used on MongoDB 2.2+ to cleanup expired locks | ||
* automatically. Such an index can for example look like this: | ||
* | ||
* db.<session-collection>.ensureIndex( | ||
* { "<expiry-field>": 1 }, | ||
* { "expireAfterSeconds": 0 } | ||
* db.lock.ensureIndex( | ||
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 suggest to provide a 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've added a method to |
||
* { "expires_at": 1 }, | ||
* { "expireAfterSeconds": 60 } | ||
* ) | ||
* | ||
* More details on: http://docs.mongodb.org/manual/tutorial/expire-data/ | ||
* @see http://docs.mongodb.org/manual/tutorial/expire-data/ | ||
* | ||
* Please note, the Symfony\Component\Lock\Key's $resource | ||
* must not exceed 1024 bytes including structual overhead. | ||
* | ||
* @see https://docs.mongodb.com/manual/reference/limits/#Index-Key-Limit | ||
* | ||
* @param float $initialTtl The expiration delay of locks in seconds | ||
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. Why 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'll move it to I've been thinking it would be simpler to remove the |
||
*/ | ||
public function __construct(\MongoDB\Client $mongo, array $options = array(), float $initialTtl = 300.0) | ||
public function __construct(\MongoDB\Client $mongo, array $options, float $initialTtl = 300.0) | ||
{ | ||
if (!isset($options['database'])) { | ||
throw new \InvalidArgumentException( | ||
throw new InvalidArgumentException( | ||
'You must provide the "database" option for MongoDBStore' | ||
); | ||
} | ||
|
@@ -67,60 +71,39 @@ public function __construct(\MongoDB\Client $mongo, array $options = array(), fl | |
|
||
$this->options = array_merge(array( | ||
'collection' => 'lock', | ||
'resource_field' => '_id', | ||
'token_field' => 'token', | ||
'acquired_field' => 'acquired_at', | ||
'expiry_field' => 'expires_at', | ||
), $options); | ||
|
||
$this->initialTtl = $initialTtl; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* db.lock.update( | ||
* { | ||
* _id: "test", | ||
* expires_at: { | ||
* $lte : new Date() | ||
* } | ||
* }, | ||
* { | ||
* _id: "test", | ||
* token: {# unique token #}, | ||
* acquired: new Date(), | ||
* expires_at: new Date({# now + ttl #}) | ||
* }, | ||
* { | ||
* upsert: 1 | ||
* } | ||
* ); | ||
*/ | ||
public function save(Key $key) | ||
{ | ||
$expiry = $this->createDateTime(microtime(true) + $this->initialTtl); | ||
$now = microtime(true); | ||
$expiry = $this->createDateTime($now + $this->initialTtl); | ||
$token = $this->getToken($key); | ||
|
||
$filter = array( | ||
$this->options['resource_field'] => (string) $key, | ||
'_id' => (string) $key, | ||
'$or' => array( | ||
array( | ||
$this->options['token_field'] => $this->getToken($key), | ||
'token' => $token, | ||
), | ||
array( | ||
$this->options['expiry_field'] => array( | ||
'$lte' => $this->createDateTime(), | ||
'expires_at' => array( | ||
'$lte' => $this->createDateTime($now), | ||
), | ||
), | ||
), | ||
); | ||
|
||
$update = array( | ||
'$set' => array( | ||
$this->options['resource_field'] => (string) $key, | ||
$this->options['token_field'] => $this->getToken($key), | ||
$this->options['acquired_field'] => $this->createDateTime(), | ||
$this->options['expiry_field'] => $expiry, | ||
'_id' => (string) $key, | ||
'token' => $token, | ||
'expires_at' => $expiry, | ||
), | ||
); | ||
|
||
|
@@ -131,8 +114,10 @@ public function save(Key $key) | |
$key->reduceLifetime($this->initialTtl); | ||
try { | ||
$this->getCollection()->updateOne($filter, $update, $options); | ||
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. Is it atomic? on every version of mongodb? What's about réplication propagation? 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. MongoDB is atomic on a singular statement / document only. Hence the use of a update with filter (1 statement). MongoDB 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. With regards to I have added some documentation regarding this decision to |
||
} catch (\MongoDB\Driver\Exception\BulkWriteException $e) { | ||
} catch (\MongoDB\Driver\Exception\WriteException $e) { | ||
throw new LockConflictedException('Failed to acquire lock', 0, $e); | ||
} catch (\Exception $e) { | ||
throw new LockStorageException($e->getMessage(), 0, $e); | ||
} | ||
|
||
if ($key->isExpired()) { | ||
|
@@ -142,48 +127,32 @@ public function save(Key $key) | |
|
||
public function waitAndSave(Key $key) | ||
{ | ||
throw new InvalidArgumentException(sprintf( | ||
throw new NotSupportedException(sprintf( | ||
'The store "%s" does not supports blocking locks.', | ||
__CLASS__ | ||
)); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* db.lock.update( | ||
* { | ||
* _id: "test", | ||
* token: {# unique token #}, | ||
* expires_at: { | ||
* $gte : new Date() | ||
* } | ||
* }, | ||
* { | ||
* _id: "test", | ||
* expires_at: new Date({# now + ttl #}) | ||
* }, | ||
* { | ||
* upsert: 1 | ||
* } | ||
* ); | ||
*/ | ||
public function putOffExpiration(Key $key, $ttl) | ||
{ | ||
$expiry = $this->createDateTime(microtime(true) + $ttl); | ||
$now = microtime(true); | ||
$expiry = $this->createDateTime($now + $ttl); | ||
|
||
$filter = array( | ||
$this->options['resource_field'] => (string) $key, | ||
$this->options['token_field'] => $this->getToken($key), | ||
$this->options['expiry_field'] => array( | ||
'$gte' => $this->createDateTime(), | ||
'_id' => (string) $key, | ||
'token' => $this->getToken($key), | ||
'expires_at' => array( | ||
'$gte' => $this->createDateTime($now), | ||
), | ||
); | ||
|
||
$update = array( | ||
'$set' => array( | ||
$this->options['resource_field'] => (string) $key, | ||
$this->options['expiry_field'] => $expiry, | ||
'_id' => (string) $key, | ||
'expires_at' => $expiry, | ||
), | ||
); | ||
|
||
|
@@ -194,8 +163,10 @@ public function putOffExpiration(Key $key, $ttl) | |
$key->reduceLifetime($ttl); | ||
try { | ||
$this->getCollection()->updateOne($filter, $update, $options); | ||
} catch (\MongoDB\Driver\Exception\BulkWriteException $e) { | ||
} catch (\MongoDB\Driver\Exception\WriteException $e) { | ||
throw new LockConflictedException('Failed to put off the expiration of the lock', 0, $e); | ||
} catch (\Exception $e) { | ||
throw new LockStorageException($e->getMessage(), 0, $e); | ||
} | ||
|
||
if ($key->isExpired()) { | ||
|
@@ -208,40 +179,25 @@ public function putOffExpiration(Key $key, $ttl) | |
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* db.lock.remove({ | ||
* _id: "test" | ||
* }); | ||
*/ | ||
public function delete(Key $key) | ||
{ | ||
$filter = array( | ||
$this->options['resource_field'] => (string) $key, | ||
$this->options['token_field'] => $this->getToken($key), | ||
'_id' => (string) $key, | ||
'token' => $this->getToken($key), | ||
); | ||
|
||
try { | ||
$result = $this->getCollection()->deleteOne($filter); | ||
} catch (\MongoDB\Driver\Exception\BulkWriteException $e) { | ||
throw new LockConflictedException('Failed to delete lock', 0, $e); | ||
} | ||
$this->getCollection()->deleteOne($filter); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* db.lock.find({ | ||
* _id: "test", | ||
* expires_at: { | ||
* $gte : new Date() | ||
* } | ||
* }); | ||
*/ | ||
public function exists(Key $key) | ||
{ | ||
$filter = array( | ||
$this->options['resource_field'] => (string) $key, | ||
$this->options['expiry_field'] => array( | ||
'_id' => (string) $key, | ||
'expires_at' => array( | ||
'$gte' => $this->createDateTime(), | ||
), | ||
); | ||
|
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.
Is there a way to pass a dns for lazy connection? See PdoTrait from cache components?
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.
A
MongoDB\Client
instance is already lazy. I can turn off my database and still:only if I add something like:
does it throw an exception.