Enforce a single instance of a node.js process across multiple hosts, using a binary Semaphore-style mechanism.
Let's say you have some kind of background worker that performs a complex or time-consuming set of operations on shared data in a database. And if another instance of your worker were to start working on the data while the first is still active, your data could get corrupted. Perhaps you run your worker on a scheduled basis and occasionally a worker's execution can take longer than the duration between scheduled runs. Or maybe you want multiple deployments of your worker for reliability purposes, but of course only want one of them to actually execute at a time.
The singleton-process library provides a mechanism for a Node.js application to enforce a singleton instance by creating an atomic lock that all instances of your app check before executing a protected block of code.
Start by creating a
Singleton instance, passing in the desired Persister:
var singletonProcess = ;// create MongoDB persistervar persister = 'mongodb://uri/to/your/mongo-db';// create singleton instance with the persistervar singleton = 'a-unique-singleton-name' persister;
If you're hip to Promises, then a typical workflow looks like this:
singleton-process uses the RSVP promise library which allows you to use the
catch(onRejected) method instead of
then(undefined, onRejected) for handling errors.
singleton-process methods also take callbacks, so we can accomplish roughly the same thing with the following:
When singleton-process obtains a lock, it registers an event handler with the
SIGTERM event of the host process that automatically releases the lock should the process be singnaled for termination. This can be important in situations like PaaS deployments where your Node.js processes aren't under your direct management.
To ensure that your singleton instance also releases its lock when an error occurs, favor the promises approach. If you insist on using callbacks, you can try using a Node.js domain:
var singletonProcess = ;var domain = ;var sinlgeton = null;var d = domain;d;d;
Singleton constructor also takes an options parameter:
var options = lockExpireSeconds: 300 ;var singleton = name persister options;
which currently supports the following attributes:
All locks created by the
Singletoninstance will expire after the specified number of seconds so that lock creation attempts will automatically delete expired locks before creating new ones. When an expired lock is deleted, the
Attempts to obtain a lock for the named singleton instance. Returns a promise with a
successvalue which is
trueif the lock was obtained and
falseif a lock by the same name already exists. Arguments:
callback(err, success)(optional): A callback to invoke when the lock attempt is complete.
err: An error occurred while performing the lock.
success: same meaning as the returned promise value
Attempts to release an existing singleton lock. Returns an empty promise if the release was successful.
callback(err)(optional): A callback to invoke when the lock release is complete.
err: An error occurred while releasing the lock.
Determines if a named singleton instance currently exists. Returns a promise with an
existsvalue which is
trueif the lock exists; otherwise
callback(err, exists)(optional): A callback to invoke when the exist check is complete.
err: An error occurred while performing the check.
exists: same meaning as the returned promise value
Singleton instance also emits events! They are optional to handle since the core workflow can be accomplished using the promises or callbacks as demonstrated in the Usage section.
lockmethod has been called and an attempt will now be made to aquire the lock.
A lock has been successfully obtained.
The lock was not successful because another singleton currently has the lock.
An expired lock was deleted, which will allow a new lock to be created.
releasemethod has been called and an attempt will now be made to release the existing singleton lock.
The lock release was successful.
An error has occurred during locking or releasing of the singleton.
All events (except
error) return a
message parameter that can be used for logging. The
error event returns the standard
Locking is performed by persisting state to a shared data store. singleton-process comes with following persisters:
- MongoDB: Created using the
singletonProcess.persisters.MongoDbclass (as shown in the Usage example above).
Please feel free to fork the repo and add more!
A persister class should provide the following interface:
Atomically save a lock to the data store.
name: The name of the lock to persist.
callback(err, success, conflictCreated): A callback to invoke when persistence is complete.
err: An error object if an error occurs during persistence.
trueif the lock was successfully persisted;
falseif an existing lock prevented the persistence.
conflictCreated: if the lock was not successful, the date that the existing lock was created or
nullif that can't be determined.
Atomically remove an existing lock.
name: The name of the lock to delete.
callback(err): A callback to invoke when the lock removal is complete.
err: An error object if an error occurs during lock removal.
Queries the data store to check for the existance of a lock.
name: The name of the lock to check.
callback(err, exists): A callback to invoke when the check is complete.
err: An error object if an error occurs during the check.
trueif the lock exists; otherwise