npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

exclusive-lock

v2.0.0

Published

Create atomic locks that can be used in a distributed environment

Downloads

349

Readme

ExclusiveLock

Need to synchronize a bunch of workers so that only 1 does a particular thing? If yes, then you need a lock file. This package uses a cache store (redis or keydb) to create records atomically that only 1 instance will "own."

NOTES

This library covers most common use cases for exclusion when working with multiple instances of the same application / container:

  • Exclusive initialization: have one of the application instances perform a task on init.
  • Periodic checks: Have one of the application instances perform a task periodically.

For long running tasks and rare edge cases, it could be possible that the lock is perceived as acquired by more than one instance. This library does not provide strict guarantees for exclusion for tasks that must be processed exactly once in a non-idempotent manner (e.g. appending an item in order processing). Consider using upserts/cas on your persistent DB in those cases or using a dedicated solution for exclusion like etcd / zookeeper.

Installation

npm install @logdna/exclusive-lock --save

Usage

const ExclusiveLock = require('@logdna/exclusive-lock')
const cache_connection = require('./my-keydb-connection.js')
const pino = require('pino')

const exclusive_lock = new ExclusiveLock({
  name: 'my-distributed-app'
, log: pino()
, cache_manager
})

async function main() {
  const acquired = await exclusive_lock.acquire()
  if (acquired) {
    log.info('You win the race! Lock acquired')
  }
  exclusive_lock.release()
}

main()

Auto-refreshing

As a feature, exclusive-lock will start a timer to automatically refresh the TTL on the lock file, as it's assumed that the lock holder will always want to hold the lock until explicitly released. This feature is handled by a setInterval timer and can encouter errors asynchronously. See the error event for details on that.

  • The timer is not started until a lock is successfully acquired.
  • For clean shutdown, always release() an acquired lock so that the timer is canceled.
  • refreshed is emitted for every successful refresh.

API


new ExclusiveLock(options)

  • options <Object>

    • name <String> - Required A unique string to identify your application
    • cache_connection <Object> - Required A connection to the cache, either Redis or Keydb
    • log <Object> - A optional logger instance such as pino. Must support levels info, warn, error, debug. Default: abstract-logging
    • lock_ttl_ms <Number> - Optional. Specify a TTL in milliseconds for the lock. Default: 3000
    • lock_refres_ms <Number> - Optional. specify a time in milliseconds for refreshing the lock on an interval. Default: 1000
    • lock_contents <String> - Optional. Specify the string contents to put in the lock file, e.g. a server/instance name.

    Throws: <Error> for validation errors

acquire()

Attempts to exclusively acquire a lock based on the given name. If multiple instances are competing, only 1 will win the lock.

Returns: Promise<Boolean> if the lock was a success
Emits: acquired

inspect()

Returns an object containing the remaining TTL (if any) on the lock, and the serialized lock contents. Useful if lock_contents was used to store valuable information in the lock, or to analyze the TTL. Any client can inspect the lock regardless of who acquired it.

If there is no lock, null is returned

Returns: Promise<Object<lock_ttl_ms: Number, lock_contents: Object|Number|Boolean|String>> An object containing the remaining TTL (in milliseconds), plus the contents of the lock
Throws: <Error> for cache store pipeline errors

release()

Unlock based on name. Idempotent. Instances that do not have the lock will be a no-op.

Returns: Promise<undefined>
Emits: released

Events

'acquired'

  • key <String> - The key value of the lock stored in cache

This event is emitted after a successful lock is acquired. Because the acquire() method is async, there's no real need to listen for this, but it has been added to remain consistent with the 'released' event which can happen asynchronously.

'refreshed'

  • <Object> - The payload object
    • key <String> - The key value of the lock stored in cache
    • lock_ttl_ms <Number> - The TTL value that it was refreshed to

This event is emitted after a lock's TTL is successfully refreshed.

'released'

  • key <String> - The key value of the lock stored in cache

This event is emitted when a lock is released.

'error'

  • err <Error> - A thrown error that occurred

This event is thrown when errors are encountered in the TTL refresh timer. In this case, the error is emitted and the following actions are taken:

  • release() will automatically be called. This is so that the lock does not become expired leaving the lock holder thinking that it's still acquired. Users should listen for these events and act accordingly.
  • If the above release() also fails, an 'error' event will be emitted again with those details. At that point, the instance will no longer think the lock is acquired, but its record will remain until the TTL expires. This is because the failure in release() will be because of a failed cache.del() operation, thus leaving the key.

Authors

License

Copyright © 2022 Mezmo, released under an MIT license. See the LICENSE file and https://opensource.org/licenses/MIT

Contributing

This project is open-sourced, and accepts PRs from the public for bugs or feature enhancements. These are the guidelines for contributing: