In the previous article of this mini-series, we saw how to atomically acquire a lock through a MongoDB collection *unique* index in order to ensure mutual exclusion between multiple copies of a program.

A lock of this kind can be released because of two independent events:

- a copy of the program finishing and explicitly releasing it.

- its expiration after a timeout, due to the acquiring program crashing..

Here is the PHP code that manipulates the collection to release the lock:

<?php
$programName = 'my_report_generation';
$processName = gethostname() . ':' . getmypid();
$now = new DateTime();
$expiration = (new DateTime())->add(new DateInterval("PT10M"));
$collection = (new MongoClient())->test->locks;

$removeOutdatedLocks = function() use ($collection, $programName, $now) {
    $collection->remove([
        'program' => $programName,
        'expires_at' => [
        '$lt' => new MongoDate($now->getTimestamp()),
        ],
    ]);
};

$query = [
    'program' => $programName,
    //'process' = $processName,
];
$operationResult = $collection->remove($query);
$affectedDocuments = $operationResult['n'];
if ($affectedDocuments != 1) {
    throw new RuntimeException("{$processName} does not have a lock for {$programName} to release");
}

echo "Lock released!", PHP_EOL;

$removeOutdatedLocks should be called before any attempt to acquire a lock, to force the expiration of previous holders where possible. You could rely on MongoDB TTL collections to expire documents automatically, but their granularity is on the order of minutes and may not suffice for your needs; moreover, it's similar to employing stored procedures, resulting in very low testability.

Releasing a lock consists of a simple remove command targeting the program name, the only field in the unique index. For extra robustness, you can add the process name so that a particular process is only able to release the lock he has created.

To check the result of the remove, we use the result field indicating the number of affected documents: this should always be 1 when actually releasing a lock. In case the remove does not affect any document, the process is attempting to release a lock it does not hold and this may be an application error.

Giorgio Sironi

Developer at Onebip , Website , Git home page , @giorgiosironi , Linkedin profile
I search for the harmony between form and context, which is a fancy way of saying I build software to fit in the world I'm in and its rapid changes. In the specific, my areas of expertise are testing, OOP design and distributed computing.

All articles by Giorgio Sironi

Comments

comments powered by Disqus

cloudparty

Follow Us