{"id":13756716,"url":"https://github.com/danielstjules/redislock","last_synced_at":"2025-04-07T05:12:38.514Z","repository":{"id":48783775,"uuid":"21188724","full_name":"danielstjules/redislock","owner":"danielstjules","description":"Node distributed locking using redis","archived":false,"fork":false,"pushed_at":"2019-12-01T21:28:39.000Z","size":49,"stargazers_count":110,"open_issues_count":2,"forks_count":32,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-31T04:07:01.411Z","etag":null,"topics":["javascript","locks","nodejs","redis"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielstjules.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-06-25T03:15:41.000Z","updated_at":"2025-03-03T06:36:30.000Z","dependencies_parsed_at":"2022-08-31T09:11:40.574Z","dependency_job_id":null,"html_url":"https://github.com/danielstjules/redislock","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fredislock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fredislock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fredislock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielstjules%2Fredislock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielstjules","download_url":"https://codeload.github.com/danielstjules/redislock/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247595335,"owners_count":20963943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","locks","nodejs","redis"],"created_at":"2024-08-03T11:00:51.983Z","updated_at":"2025-04-07T05:12:38.488Z","avatar_url":"https://github.com/danielstjules.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"![redislock](http://danielstjules.com/github/redislock-logo.png)\n\nNode distributed locking using redis with lua scripts. Compatible with\nredis \u003e= 2.6.12. A better alternative to locking strategies based on SETNX or\nWATCH/MULTI. Refer to [Implementation](#implementation) and\n[Alternatives](#alternatives) for details.\n\n[![Build Status](https://travis-ci.org/danielstjules/redislock.png)](https://travis-ci.org/danielstjules/redislock)\n\n* [Installation](#installation)\n* [Overview](#overview)\n* [Implementation](#implementation)\n* [Alternatives](#alternatives)\n* [API](#api)\n    * [redislock.createLock(client, \\[options\\])](#redislockcreatelockclient-options)\n    * [redislock.setDefaults(options)](#redislocksetdefaultsoptions)\n    * [redislock.getAcquiredLocks()](#redislockgetacquiredlocks)\n    * [redislock.LockAcquisitionError](#redislocklockacquisitionerror)\n    * [redislock.LockReleaseError](#redislocklockreleaseerror)\n    * [redislock.LockExtendError](#redislocklockextenderror)\n* [Class: Lock](#class-lock)\n    * [lock.acquire(key, \\[fn\\])](#lockacquirekey-fn)\n    * [lock.release(\\[fn\\])](#lockreleasefn)\n    * [lock.extend(key, \\[fn\\])](#lockextendtime-fn)\n* [Tests](#tests)\n\n## Installation\n\nUsing npm, you can install redislock with `npm install --save redislock`.\nYou can also require it as a dependency in your `package.json` file:\n\n```\n\"dependencies\": {\n    \"redislock\": \"*\"\n}\n```\n\n## Overview\n\nredislock offers both atomic acquire and release operations, avoiding race\nconditions among clients, as well as the need for lock-specific redis\nconnections. Lock creation requires a node_redis client, and accepts an\nobject specifying the following three options:\n\n * timeout: Time in milliseconds before which a lock expires (default: 10000 ms)\n * retries: Maximum number of retries in acquiring a lock if the first attempt failed (default: 0, infinite: -1)\n * delay:   Time in milliseconds to wait between each attempt (default: 50 ms)\n\n``` javascript\nvar client = require('redis').createClient();\nvar lock   = require('redislock').createLock(client, {\n  timeout: 20000,\n  retries: 3,\n  delay: 100\n});\n\nlock.acquire('app:feature:lock', function(err) {\n  // if (err) ... Failed to acquire the lock\n\n  lock.release(function(err) {\n    // if (err) ... Failed to release\n  });\n});\n```\n\nSupports promises, thanks to bluebird, out of the box:\n\n``` javascript\nvar client    = require('redis').createClient();\nvar redislock = require('redislock');\nvar lock      = redislock.createLock(client);\n\nvar LockAcquisitionError = redislock.LockAcquisitionError;\nvar LockReleaseError     = redislock.LockReleaseError;\n\nlock.acquire('app:feature:lock').then(function() {\n  // Lock has been acquired\n  return lock.release();\n}).then(function() {\n  // Lock has been released\n}).catch(LockAcquisitionError, function(err) {\n  // The lock could not be acquired\n}).catch(LockReleaseError, function(err) {\n  // The lock could not be released\n});\n```\n\nAnd an example with co:\n\n``` javascript\nvar co     = require('co');\nvar client = require('redis').createClient();\nvar lock   = require('redislock').createLock(client);\n\nco(function *(){\n  try {\n    yield lock.acquire('app:feature:lock');\n  } catch (e) {\n    // Failed to acquire the lock\n  }\n\n  try {\n    yield lock.release();\n  } catch (e) {\n    // Failed to release\n  }\n})();\n```\n\n## Implementation\n\nLocking is performed using the following redis command:\n\n```\nSET key uuid PX timeout NX\n```\n\nIf the SET returns OK, the lock has been acquired on the given key, and an\nexpiration has been set. Then, releasing a lock uses the following redis script:\n\n``` lua\nif redis.call('GET', KEYS[1]) == ARGV[1] then\n  return redis.call('DEL', KEYS[1])\nend\nreturn 0\n```\n\nThis ensures that the key is deleted only if it is currently holding the lock,\nby passing its UUID as an argument. Extending a lock is done with a similar\nlua script:\n\n``` lua\nif redis.call('GET', KEYS[1]) == ARGV[1] then\n  return redis.call('PEXPIRE', KEYS[1], ARGV[2])\nend\nreturn 0\n```\n\n## Alternatives\n\nSome alternative locking implementations do not use a random identifier, but\ninstead simply invoke `SETNX`, assigning a timestamp. This has the problem of\nrequiring synchronization of clocks between all instances to maintain timeout\naccuracy. Furthermore, freeing a lock with such an implementation may risk\ndeleting a key set by a different lock.\n\nAnother technique used is to `WATCH` the key for changes when freeing,\nachieving a CAS-like operation, as described below:\n\n```\nWATCH key  # Begin watching the key for changes\nGET key    # Retrieve its value, return an error if not equal to the lock's UUID\nMULTI      # Start transaction\nDEL key    # Delete the key\nEXEC       # Execute the transaction, which will fail if the key had expired\n```\n\nHowever, this has the issue of requiring that you use a 1:1 mapping of redis\nclients to locks to ensure that a competing `MULTI` is not invoked, and that\nthe release is unaffected by other watched keys.\n\nIn addition to the above, most locking libraries aren't compatible with promises\nby default, and due to their API, require \"promisifying\" individual locks.\n`redislock` avoids this issue by taking advantage of bluebird's `nodeify`\nfunction to offer an API that easily supports both callbacks and promises.\n\n## API\n\nThe module exports three functions for lock creation and management, as well\nas two errors for simplified error handling when using promises.\n\n#### redislock.createLock(client, [options])\n\nCreates and returns a new Lock instance, configured for use with the supplied\nredis client, as well as options, if provided. The options object may contain\nfollowing three keys, as outlined at the start of the documentation: timeout,\nretries and delay.\n\n``` javascript\nvar lock = redislock.createLock(client, {\n  timeout: 10000,\n  retries: 3,\n  delay: 100\n})\n```\n\n#### redislock.setDefaults(options)\n\nSets the default options to be used by any new lock created by redislock.\nOnly available options are modified, and all other keys are ignored.\n\n``` javascript\nredislock.setDefaults({\n  timeout: 200000,\n  retries: 1,\n  delay: 50\n});\n```\n\n#### redislock.getAcquiredLocks()\n\nReturns an array of currently active/acquired locks.\n\n``` javascript\n// Create 3 locks, but only acquire 2\nredislock.createLock(client);\n\nredislock.createLock(client).acquire('app:lock1', function(err) {\n  redislock.createLock(client).acquire('app:lock2', function(err) {\n    var locks = redislock.getAcquiredLocks(); // [lock, lock]\n  });\n});\n```\n\n#### redislock.LockAcquisitionError\n\nThe constructor for a LockAcquisitionError. Thrown or returned when a lock\ncould not be acquired.\n\n#### redislock.LockReleaseError\n\nThe constructor for a LockReleaseError. Thrown or returned when a lock\ncould not be released.\n\n#### redislock.LockExtendError\n\nThe constructor for a LockExtendError. Thrown or returned when a lock\ncould not be extended.\n\n## Class: Lock\n\nThe lock class exposed by redislock. Each instance is assigned a UUID v1 string\nas an id, and is configured to work with the given redis client. The default\noptions from which is inherits may be changed by using redislock.setDefaults.\n\n#### lock.acquire[key, [fn]]\n\nAttempts to acquire a lock, given a key, and an optional callback function.\nIf the initial lock fails, additional attempts will be made for the\nconfigured number of retries, and padded by the delay. The callback is\ninvoked with an error on failure, and returns a promise if no callback is\nsupplied. If invoked in the context of a promise, it may throw a\nLockAcquisitionError.\n\n``` javascript\nvar lock = redislock.createLock(client);\nlock.acquire('example:lock', function(err) {\n  if (err) return console.log(err.message); // 'Lock already held'\n});\n```\n\n#### lock.release([fn])\n\nAttempts to release the lock, and accepts an optional callback function.\nThe callback is invoked with an error on failure, and returns a promise\nif no callback is supplied. If invoked in the context of a promise, it may\nthrow a LockReleaseError.\n\n``` javascript\nvar lock = redislock.createLock(client);\nlock.acquire('app:lock', function(err) {\n  if (err) return;\n\n  setTimeout(function() {\n    lock.release(function(err) {\n      if (err) return console.log(err.message); // 'Lock on app:lock has expired'\n    });\n  }, 20000)\n});\n```\n\n#### lock.extend(time, [fn])\n\nAttempts to extend the timeout of a lock, and accepts an optional callback\nfunction. The callback is invoked with an error on failure, and returns a\npromise if no callback is supplied. If invoked in the context of a promise,\nit may throw a LockExtendError.\n\n``` javascript\nvar lock = redislock.createLock(client);\nlock.acquire('app:lock', function(err) {\n  if (err) return;\n\n  setTimeout(function() {\n    lock.extend(20000, function(err) {\n      if (err) return console.log(err.message); // 'Lock on app:lock has expired'\n    });\n  }, 20000)\n});\n```\n\n## Tests\n\nUnit and functional tests are available in the base spec directory, and can\nbe ran using `npm test`. Additional integration tests, which require an active\nredis-server configured on the default port and host, can be ran using\n`mocha spec/integration/`. Both tests suites are ran as part of the Travis CI\nbuild thanks to their support for services such as redis.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielstjules%2Fredislock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielstjules%2Fredislock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielstjules%2Fredislock/lists"}