Skip to content

Commit

Permalink
symfony#27345 Changed createTtlIndexProbability to gcProbablity, fixe…
Browse files Browse the repository at this point in the history
…d gte / lte conflict
  • Loading branch information
Joe Bennett committed Oct 15, 2018
1 parent 631a8cf commit 3d0402a
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 68 deletions.
128 changes: 65 additions & 63 deletions src/Symfony/Component/Lock/Store/MongoDbStore.php
Expand Up @@ -54,9 +54,9 @@ class MongoDbStore implements StoreInterface
* @throws InvalidArgumentException if required options are not provided
*
* Options:
* database: The name of the database [required]
* collection: The name of the collection [default: lock]
* createTtlIndexProbability: Should a TTL Index be created expressed as a probability from 0.0 to 1.0 [default: 0.001]
* database: The name of the database [required]
* collection: The name of the collection [default: lock]
* gcProbablity: Should a TTL Index be created expressed as a probability from 0.0 to 1.0 [default: 0.001]
*
* CAUTION: The locked resource name is indexed in the _id field of the
* lock collection. An indexed field's value in MongoDB can be a maximum
Expand All @@ -69,13 +69,13 @@ class MongoDbStore implements StoreInterface
* To ensure locks don't expire prematurely; the lock TTL should be set
* with enough extra time to account for any clock drift between nodes.
*
* If createTtlIndexProbability is set to a value greater than 0.0
* there will be a chance this store will attempt to create a TTL index on
* self::save(). If however the server version is less than MongoDB 2.2
* indicating TTL Indexes are unsupported, a db.lock.remove() query will
* be used to cleanup old locks instead. If you prefer to create your
* TTL Index manually you can set createTtlIndexProbability to 0.0 and
* optionally leverage self::createTtlIndex(int $expireAfterSeconds = 0).
* If gcProbablity is set to a value greater than 0.0 there is a chance
* this store will attempt to create a TTL index on self::save().
* If however the server version is less than MongoDB 2.2 indicating TTL
* Indexes are unsupported, a db.lock.remove() query will be used to
* cleanup old locks instead. If you prefer to create your TTL Index
* manually you can set gcProbablity to 0.0 and optionally leverage
* self::createTtlIndex(int $expireAfterSeconds = 0).
*
* writeConcern, readConcern and readPreference are not specified by
* MongoDbStore meaning the collection's settings will take effect.
Expand All @@ -89,7 +89,7 @@ public function __construct(Client $mongo, array $options, float $initialTtl = 3

$this->options = array_merge(array(
'collection' => 'lock',
'createTtlIndexProbability' => 0.001,
'gcProbablity' => 0.001,
), $options);

if (!isset($this->options['database'])) {
Expand All @@ -98,11 +98,11 @@ public function __construct(Client $mongo, array $options, float $initialTtl = 3
);
}

if ($this->options['createTtlIndexProbability'] < 0.0 || $this->options['createTtlIndexProbability'] > 1.0) {
if ($this->options['gcProbablity'] < 0.0 || $this->options['gcProbablity'] > 1.0) {
throw new InvalidArgumentException(sprintf(
'"%s" createTtlIndexProbability must be a float from 0.0 to 1.0, "%f" given.',
'"%s" gcProbablity must be a float from 0.0 to 1.0, "%f" given.',
__METHOD__,
$this->options['createTtlIndexProbability']
$this->options['gcProbablity']
));
}

Expand All @@ -118,12 +118,12 @@ public function __construct(Client $mongo, array $options, float $initialTtl = 3
/**
* Create a TTL index to automatically remove expired locks.
*
* If the createTtlIndexProbability option is set higher than 0.0
* (defaults to 0.001); this will be called automatically on self::save().
* If the gcProbablity option is set higher than 0.0 (defaults to 0.001);
* there is a chance this will be called on self::save().
*
* Otherwise; this should be called once during database setup.
* Otherwise; this should be called once manually during database setup.
*
* Alternatively the TTL index can be created manually:
* Alternatively the TTL index can be created manually on the database:
*
* db.lock.ensureIndex(
* { "expires_at": 1 },
Expand Down Expand Up @@ -213,10 +213,10 @@ public function save(Key $key)
throw new LockExpiredException(sprintf('Failed to store the "%s" lock.', $key));
}

if ($this->options['createTtlIndexProbability'] > 0.0
if ($this->options['gcProbablity'] > 0.0
&& (
1.0 === $this->options['createTtlIndexProbability']
|| (random_int(0, PHP_INT_MAX) / PHP_INT_MAX) <= $this->options['createTtlIndexProbability']
1.0 === $this->options['gcProbablity']
|| (random_int(0, PHP_INT_MAX) / PHP_INT_MAX) <= $this->options['gcProbablity']
)
) {
if ($this->getDatabaseVersion() < '2.2') {
Expand All @@ -227,49 +227,12 @@ public function save(Key $key)
}
}

/**
* Remove expired locks from the collection. This is a fallback for MongoDB
* servers prior to version 2.2. For versions 2.2+ self::createTtlIndex is
* called instead based on the createTtlIndexProbability options.
*/
private function removeExpiredLocks()
{
$now = microtime(true);

$filter = array(
'expires_at' => array(
'$lt' => $this->createDateTime($now),
),
);

$this->getCollection()->deleteMany($filter);
}

/**
* {@inheritdoc}
*/
public function waitAndSave(Key $key)
{
throw new NotSupportedException(sprintf(
'The store "%s" does not support blocking locks.',
__CLASS__
));
}

/**
* @return bool
*/
private function isDuplicateKeyException(
WriteException $e
): bool {
$writeErrors = $e->getWriteResult()->getWriteErrors();
if (1 === \count($writeErrors)) {
$code = $writeErrors[0]->getCode();
} else {
$code = $e->getCode();
}
// Mongo error E11000 - DuplicateKey
return 11000 === $code;
throw new NotSupportedException(sprintf('The store "%s" does not support blocking locks.', __CLASS__));
}

/**
Expand All @@ -284,9 +247,15 @@ public function putOffExpiration(Key $key, $ttl)

$filter = array(
'_id' => (string) $key,
'token' => $this->getUniqueToken($key),
'expires_at' => array(
'$gte' => $this->createDateTime($now),
'$or' => array(
array(
'token' => $token,
),
array(
'expires_at' => array(
'$lte' => $this->createDateTime($now),
),
),
),
);

Expand Down Expand Up @@ -344,7 +313,7 @@ public function exists(Key $key): bool
'_id' => (string) $key,
'token' => $this->getUniqueToken($key),
'expires_at' => array(
'$gte' => $this->createDateTime(),
'$gt' => $this->createDateTime(),
),
);

Expand All @@ -353,6 +322,39 @@ public function exists(Key $key): bool
return null !== $doc;
}

/**
* Remove expired locks from the collection. This is a fallback for MongoDB
* servers prior to version 2.2. For versions 2.2+ self::createTtlIndex is
* called instead based on the gcProbablity options.
*/
private function removeExpiredLocks()
{
$now = microtime(true);

$filter = array(
'expires_at' => array(
'$lte' => $this->createDateTime($now),
),
);

$this->getCollection()->deleteMany($filter);
}

/**
* @return bool
*/
private function isDuplicateKeyException(WriteException $e): bool
{
$writeErrors = $e->getWriteResult()->getWriteErrors();
if (1 === \count($writeErrors)) {
$code = $writeErrors[0]->getCode();
} else {
$code = $e->getCode();
}
// Mongo error E11000 - DuplicateKey
return 11000 === $code;
}

/**
* @return Manager
*/
Expand Down
9 changes: 4 additions & 5 deletions src/Symfony/Component/Lock/Tests/Store/MongoDbStoreTest.php
Expand Up @@ -64,16 +64,15 @@ public function testCreateIndex()
$this->assertEquals($store->createTtlIndex(), 'expires_at_1');
}

/**
* @expectedException NotSupportedException
*/
public function testNonBlocking()
{
$store = $this->getStore();

$key = new Key(uniqid(__METHOD__, true));

try {
$store->waitAndSave($key);
$this->fail('The store shouldn\'t support waitAndSave');
} catch (NotSupportedException $e) {
}
$store->waitAndSave($key);
}
}

0 comments on commit 3d0402a

Please sign in to comment.