async-lock vs async-mutex vs mutexify
JavaScript Concurrency Control Libraries
async-lockasync-mutexmutexifySimilar Packages:

JavaScript Concurrency Control Libraries

Concurrency control libraries in JavaScript provide mechanisms to manage access to shared resources in an asynchronous environment. They help prevent race conditions and ensure that critical sections of code are executed safely, especially in scenarios involving multiple asynchronous operations. These libraries implement various locking mechanisms that allow developers to control the flow of execution, ensuring that only one piece of code can access a resource at a time. This is crucial in applications where data integrity and consistency are paramount, such as in database operations or when manipulating shared state in a web application.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
async-lock042618.3 kB52 years agoMIT
async-mutex01,41463 kB152 years agoMIT
mutexify0905.71 kB54 years agoMIT

Feature Comparison: async-lock vs async-mutex vs mutexify

Locking Mechanism

  • async-lock:

    async-lock provides a simple locking mechanism that allows for both exclusive and nested locks. It is designed to be easy to use, enabling developers to wrap asynchronous functions with locks to ensure that they do not execute concurrently, thus preventing race conditions.

  • async-mutex:

    async-mutex offers a more sophisticated locking mechanism, supporting both mutex locks and read/write locks. This allows for greater flexibility in managing access to shared resources, enabling multiple readers to access a resource simultaneously while ensuring that writers have exclusive access.

  • mutexify:

    mutexify implements a straightforward mutex locking mechanism, focusing on simplicity and ease of use. It allows you to wrap functions to ensure that only one instance of the function can execute at a time, making it ideal for protecting critical sections of code.

Complexity

  • async-lock:

    async-lock is designed to be simple and intuitive, making it easy for developers to implement locking in their applications without a steep learning curve. Its API is straightforward, allowing for quick integration into existing codebases.

  • async-mutex:

    async-mutex is more complex due to its support for multiple locking strategies, which may require a deeper understanding of concurrency concepts. However, this complexity allows for more powerful and flexible locking solutions in advanced scenarios.

  • mutexify:

    mutexify is minimalistic and easy to use, providing a very low barrier to entry for developers looking to implement basic locking. Its simplicity makes it suitable for quick implementations without the need for extensive documentation.

Performance

  • async-lock:

    async-lock is optimized for performance, allowing for quick lock acquisition and release. However, the performance may vary depending on the complexity of the locking strategy used, especially in scenarios with high contention.

  • async-mutex:

    async-mutex is designed to handle complex locking scenarios efficiently, but the overhead of managing multiple lock types may introduce some performance trade-offs. It is suitable for applications where the benefits of advanced locking outweigh potential performance costs.

  • mutexify:

    mutexify is lightweight and has minimal overhead, making it very performant for simple use cases. It is ideal for scenarios where locking is needed but performance is a critical concern.

Use Cases

  • async-lock:

    async-lock is best suited for applications that require simple locking mechanisms for asynchronous operations, such as managing access to shared resources or ensuring that certain operations do not run concurrently.

  • async-mutex:

    async-mutex is ideal for complex applications that need to manage multiple resources with varying access patterns, such as databases or shared state in large applications where read/write access needs to be controlled carefully.

  • mutexify:

    mutexify is perfect for straightforward use cases where a single function needs to be protected from concurrent execution, such as API calls or critical sections in a web application.

Community and Support

  • async-lock:

    async-lock has a growing community and is well-documented, making it easy to find support and examples for implementation. Its simplicity contributes to its popularity among developers.

  • async-mutex:

    async-mutex has a smaller community compared to async-lock, but it is still actively maintained. The documentation provides detailed examples, which can help developers understand its more complex features.

  • mutexify:

    mutexify is lightweight and has a smaller user base, which may result in less community support. However, its simplicity means that most developers can quickly understand and implement it without extensive guidance.

How to Choose: async-lock vs async-mutex vs mutexify

  • async-lock:

    Choose async-lock if you need a simple and straightforward locking mechanism that allows for nested locks and supports asynchronous functions. It is ideal for scenarios where you want to ensure that a particular section of code is executed without interference from other asynchronous operations.

  • async-mutex:

    Choose async-mutex if you require a more advanced locking mechanism that supports both mutex and read/write locks. It is suitable for complex scenarios where you need to manage multiple resources and want to allow concurrent read access while ensuring exclusive write access.

  • mutexify:

    Choose mutexify if you are looking for a lightweight and minimalistic approach to locking. It is particularly useful for simple use cases where you want to protect a single function or resource without the overhead of more complex locking mechanisms.

README for async-lock

async-lock

Lock on asynchronous code

Build Status

  • ES6 promise supported
  • Multiple keys lock supported
  • Timeout supported
  • Occupation time limit supported
  • Execution time limit supported
  • Pending task limit supported
  • Domain reentrant supported
  • 100% code coverage

Disclaimer

I did not create this package, and I will not add any features to it myself. I was granted the ownership because it was no longer being maintained, and I volunteered to fix a bug.

If you have a new feature you would like to have incorporated, please send me a PR and I will be happy to work with you and get it merged. For any bugs, PRs are most welcome but when possible I will try to get them resolved as soon as possible.

Why do you need locking on single threaded nodejs?

Nodejs is single threaded, and the code execution never gets interrupted inside an event loop, so locking is unnecessary? This is true ONLY IF your critical section can be executed inside a single event loop. However, if you have any async code inside your critical section (it can be simply triggered by any I/O operation, or timer), your critical logic will across multiple event loops, therefore it's not concurrency safe!

Consider the following code

redis.get('key', function(err, value) {
	redis.set('key', value * 2);
});

The above code simply multiply a redis key by 2. However, if two users run concurrently, the execution order may like this

user1: redis.get('key') -> 1
user2: redis.get('key') -> 1
user1: redis.set('key', 1 x 2) -> 2
user2: redis.set('key', 1 x 2) -> 2

Obviously it's not what you expected

With asyncLock, you can easily write your async critical section

lock.acquire('key', function(cb) {
	// Concurrency safe
	redis.get('key', function(err, value) {
		redis.set('key', value * 2, cb);
	});
}, function(err, ret) {
});

Get Started

var AsyncLock = require('async-lock');
var lock = new AsyncLock();

/**
 * @param {String|Array} key 	resource key or keys to lock
 * @param {function} fn 	execute function
 * @param {function} cb 	(optional) callback function, otherwise will return a promise
 * @param {Object} opts 	(optional) options
 */
lock.acquire(key, function(done) {
	// async work
	done(err, ret);
}, function(err, ret) {
	// lock released
}, opts);

// Promise mode
lock.acquire(key, function() {
	// return value or promise
}, opts).then(function() {
	// lock released
});

Error Handling

// Callback mode
lock.acquire(key, function(done) {
	done(new Error('error'));
}, function(err, ret) {
	console.log(err.message) // output: error
});

// Promise mode
lock.acquire(key, function() {
	throw new Error('error');
}).catch(function(err) {
	console.log(err.message) // output: error
});

Acquire multiple keys

lock.acquire([key1, key2], fn, cb);

Domain reentrant lock

Lock is reentrant in the same domain

var domain = require('domain');
var lock = new AsyncLock({domainReentrant : true});

var d = domain.create();
d.run(function() {
	lock.acquire('key', function() {
		//Enter lock
		return lock.acquire('key', function() {
			//Enter same lock twice
		});
	});
});

Options

// Specify timeout - max amount of time an item can remain in the queue before acquiring the lock
var lock = new AsyncLock({timeout: 5000});
lock.acquire(key, fn, function(err, ret) {
	// timed out error will be returned here if lock not acquired in given time
});

// Specify max occupation time - max amount of time allowed between entering the queue and completing execution
var lock = new AsyncLock({maxOccupationTime: 3000});
lock.acquire(key, fn, function(err, ret) {
	// occupation time exceeded error will be returned here if job not completed in given time
});

// Specify max execution time - max amount of time allowed between acquiring the lock and completing execution
var lock = new AsyncLock({maxExecutionTime: 3000});
lock.acquire(key, fn, function(err, ret) {
	// execution time exceeded error will be returned here if job not completed in given time
});

// Set max pending tasks - max number of tasks allowed in the queue at a time
var lock = new AsyncLock({maxPending: 1000});
lock.acquire(key, fn, function(err, ret) {
	// Handle too much pending error
})

// Whether there is any running or pending async function
lock.isBusy();

// Use your own promise library instead of the global Promise variable
var lock = new AsyncLock({Promise: require('bluebird')}); // Bluebird
var lock = new AsyncLock({Promise: require('q')}); // Q

// Add a task to the front of the queue waiting for a given lock
lock.acquire(key, fn1, cb); // runs immediately
lock.acquire(key, fn2, cb); // added to queue
lock.acquire(key, priorityFn, cb, {skipQueue: true}); // jumps queue and runs before fn2

Changelog

See Changelog

Issues

See issue tracker.

License

MIT, see LICENSE