"Singleton" processes such as cron jobs must usually run in isolation in our application, without multiple copies of them being started even if the code is deployed on several machines without shared resources.

To prevent identical copies of these processes from starting at the same time on different machines, a shared persistent lock is needed. MongoDB fits the bill, due to the easiness with which a new collection can be created and be globally accessible.

Here's how a process can atomically acquire a lock for a particular program name through a MongoDB collection:

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

$collection->ensureIndex(['program' => 1], ['unique' => true]);
try {
    $collection->insert([
        'program' => $programName,
        'process' => $processName,
        'acquired_at' => new MongoDate($now->getTimestamp()),
        'expires_at' => new MongoDate($expiration->getTimestamp()),
    ]);
} catch (MongoCursorException $e) {
    if ($e->getCode() == 11000) {
        throw new RuntimeException("{$processName} cannot acquire a lock for the program {$programName}");
    }
    throw $e;
}
echo "Lock acquired!", PHP_EOL;

The index on program name guarantees only one row can be present in the collection at the same time for a particular value of this field. In case a duplicate insertion is attempted, a MongoCursorException is thrown with code 11000.

If you run this program once, it will output "Lock acquired!" If you run it a second time, a RuntimeException will be thrown explaining that the existing lock cannot be acquired.

Along with the lock we're tracking several pieces of data:

- the process machine and pid;

- the creation date for the lock;

- the scheduled end date of the lock in case the process never removes it (for example after a crash or a machine failure).

Of course, the removal of the lock needs to be performed - we will see how to do so efficiently in the part 2 of this article.

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