{"id":13527417,"url":"https://github.com/swarthy/redis-semaphore","last_synced_at":"2026-01-25T17:40:23.758Z","repository":{"id":28286961,"uuid":"117260693","full_name":"swarthy/redis-semaphore","owner":"swarthy","description":"Distributed mutex and semaphore based on Redis","archived":false,"fork":false,"pushed_at":"2025-10-01T11:04:11.000Z","size":1545,"stargazers_count":196,"open_issues_count":9,"forks_count":29,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-10-22T01:52:38.010Z","etag":null,"topics":["mutex","nodejs","redis","semaphore"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/swarthy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-01-12T16:02:14.000Z","updated_at":"2025-10-17T23:39:54.000Z","dependencies_parsed_at":"2024-02-28T12:28:38.656Z","dependency_job_id":"51eb2d03-65ff-4fae-b4c9-4695569e5ff1","html_url":"https://github.com/swarthy/redis-semaphore","commit_stats":{"total_commits":206,"total_committers":8,"mean_commits":25.75,"dds":0.1747572815533981,"last_synced_commit":"da0aa098e4ad4590ce77194ce888597606dddb1e"},"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"purl":"pkg:github/swarthy/redis-semaphore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swarthy%2Fredis-semaphore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swarthy%2Fredis-semaphore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swarthy%2Fredis-semaphore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swarthy%2Fredis-semaphore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/swarthy","download_url":"https://codeload.github.com/swarthy/redis-semaphore/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/swarthy%2Fredis-semaphore/sbom","scorecard":{"id":861680,"data":{"date":"2025-08-11","repo":{"name":"github.com/swarthy/redis-semaphore","commit":"8dd197de674558abf0410aedd736bda3d743af8e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 2/11 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/branches.yml:1","Warn: no topLevel permission defined: .github/workflows/pull-requests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/branches.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/swarthy/redis-semaphore/branches.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/branches.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/swarthy/redis-semaphore/branches.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pull-requests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/swarthy/redis-semaphore/pull-requests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pull-requests.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/swarthy/redis-semaphore/pull-requests.yml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating node:alpine to node:alpine@sha256:51dbfc749ec3018c7d4bf8b9ee65299ff9a908e38918ce163b0acfcd5dd931d9","Warn: npmCommand not pinned by hash: Dockerfile:2","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 23 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":3,"reason":"7 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-xffm-g5w8-qvg7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-6hwc-9h8r-3vmf","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T01:28:58.919Z","repository_id":28286961,"created_at":"2025-08-24T01:28:58.919Z","updated_at":"2025-08-24T01:28:58.919Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28756166,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T16:32:25.380Z","status":"ssl_error","status_checked_at":"2026-01-25T16:32:09.189Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["mutex","nodejs","redis","semaphore"],"created_at":"2024-08-01T06:01:47.593Z","updated_at":"2026-01-25T17:40:23.735Z","avatar_url":"https://github.com/swarthy.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# redis-semaphore\n\n[![NPM version][npm-image]][npm-url]\n[![Build status][ci-image]][ci-url]\n![FOSSA Status][typescript-image]\n[![Coverage Status][coverage-image]][coverage-url]\n[![Maintainability][codeclimate-image]][codeclimate-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![FOSSA Status][fossa-badge-image]][fossa-badge-url]\n\n[Mutex](\u003chttps://en.wikipedia.org/wiki/Lock_(computer_science)\u003e) and [Semaphore](\u003chttps://en.wikipedia.org/wiki/Semaphore_(programming)\u003e) implementations based on [Redis](https://redis.io/) ready for distributed systems\n\n## Features\n\n- Fail-safe (all actions performed by LUA scripts (atomic))\n\n## Usage\n\n### Installation\n\n```bash\nnpm install --save redis-semaphore ioredis\n# or\nyarn add redis-semaphore ioredis\n```\n\nioredis is the officially supported Redis client. This library's test code runs on it.\n\nUsers of other Redis clients should ensure ioredis-compatible API (see src/types.ts) when creating lock objects.\n\n### Mutex\n\n\u003e See [RedisLabs: Locks with timeouts](https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-2-distributed-locking/6-2-5-locks-with-timeouts/)\n\n##### new Mutex(redisClient, key [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8, identifier = crypto.randomUUID() }])\n\n- `redisClient` - **required**, configured `redis` client\n- `key` - **required**, key for locking resource (final key in redis: `mutex:\u003ckey\u003e`)\n- `options` - _optional_\n  - `lockTimeout` - _optional_ ms, time after mutex will be auto released (expired)\n  - `acquireTimeout` - _optional_ ms, max timeout for `.acquire()` call\n  - `acquireAttemptsLimit` - _optional_ max number of attempts to be made in `.acquire()` call\n  - `retryInterval` - _optional_ ms, time between acquire attempts if resource locked\n  - `refreshInterval` - _optional_ ms, auto-refresh interval; to disable auto-refresh behaviour set `0`\n  - `identifier` - _optional_ uuid, custom mutex identifier. Must be unique between parallel executors, otherwise multiple locks with same identifier *can* be treated as the same lock holder. Override only if you know what you are doing (see `acquiredExternally` option).\n  - `acquiredExternally` - _optional_ `true`, If `identifier` provided and `acquiredExternally` is `true` then `_refresh` will be used instead of `_acquire` in `.tryAcquire()`/`.acquire()`. Useful for lock sharing between processes: acquire in scheduler, refresh and release in handler.\n  - `onLockLost` - _optional_ function, called when lock loss is detected due refresh cycle; default onLockLost throws unhandled LostLockError\n\n#### Example\n\n```javascript\nconst Mutex = require('redis-semaphore').Mutex\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { Mutex } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClient = new Redis()\n\nasync function doSomething() {\n  const mutex = new Mutex(redisClient, 'lockingResource')\n  await mutex.acquire()\n  try {\n    // critical code\n  } finally {\n    await mutex.release()\n  }\n}\n```\n\n#### Example with lost lock handling\n\n```javascript\nasync function doSomething() {\n  const mutex = new Mutex(redisClient, 'lockingResource', {\n    // By default onLockLost throws unhandled LostLockError\n    onLockLost(err) {\n      console.error(err)\n    }\n  })\n  await mutex.acquire()\n  try {\n    while (mutex.isAcquired) {\n      // critical cycle iteration\n    }\n  } finally {\n    // It's safe to always call release, because if lock is no longer belongs to this mutex, .release() will have no effect\n    await mutex.release()\n  }\n}\n```\n\n#### Example with optional lock\n\n```javascript\nasync function doSomething() {\n  const mutex = new Mutex(redisClient, 'lockingResource', {\n    acquireAttemptsLimit: 1\n  })\n  const lockAcquired = await mutex.tryAcquire()\n  if (!lockAcquired) {\n    return\n  }\n  try {\n    while (mutex.isAcquired) {\n      // critical cycle iteration\n    }\n  } finally {\n    // It's safe to always call release, because if lock is no longer belongs to this mutex, .release() will have no effect\n    await mutex.release()\n  }\n}\n```\n\n#### Example with temporary refresh\n\n```javascript\nasync function doSomething() {\n  const mutex = new Mutex(redisClient, 'lockingResource', {\n    lockTimeout: 120000,\n    refreshInterval: 15000\n  })\n  const lockAcquired = await mutex.tryAcquire()\n  if (!lockAcquired) {\n    return\n  }\n  try {\n    // critical cycle iteration\n  } finally {\n    // We want to let lock expire over time after operation is finished\n    await mutex.stopRefresh()\n  }\n}\n```\n\n#### Example with dynamically adjusting existing lock\n\n```javascript\nconst Mutex = require('redis-semaphore').Mutex\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { Mutex } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClient = new Redis()\n\n// This creates an original lock\nconst preMutex = new Mutex(redisClient, 'lockingResource', {\n  lockTimeout: 10 * 1e3, // lock for 10s\n  refreshInterval: 0\n});\n\n// This modifies lock with a new TTL and starts refresh\nconst mutex = new Mutex(redisClient, 'lockingResource', {\n  identifier: preMutex.identifier,\n  acquiredExternally: true, // required in this case\n  lockTimeout: 30 * 60 * 1e3, // lock for 30min\n  refreshInterval: 60 * 1e3\n});\n\n```\n\n#### Example with shared lock between scheduler and handler apps\n\n```javascript\nconst Mutex = require('redis-semaphore').Mutex\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { Mutex } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClient = new Redis()\n\n// scheduler app code\nasync function every10MinutesCronScheduler() {\n  const mutex = new Mutex(redisClient, 'lockingResource', {\n    lockTimeout: 30 * 60 * 1e3, // lock for 30min\n    refreshInterval: 0\n  })\n  if (await mutex.tryAcquire()) {\n    someQueue.publish({ mutexIdentifier: mutex.identifier })\n  } else {\n    logger.info('Job already scheduled. Do nothing in current cron cycle')\n  }\n}\n\n// handler app code\nasync function queueHandler(queueMessageData) {\n  const { mutexIdentifier } = queueMessageData\n  const mutex = new Mutex(redisClient, 'lockingResource', {\n    lockTimeout: 10 * 1e3, // 10sec\n    identifier: mutexIdentifier,\n    acquiredExternally: true // required in this case\n  })\n\n  // actually will do `refresh` with new lockTimeout instead of acquire\n  // if mutex was locked by another process or lock was expired - exception will be thrown (default refresh behavior)\n  await mutex.acquire()\n\n  try {\n    // critical code\n  } finally {\n    await mutex.release()\n  }\n}\n```\n\n### Semaphore\n\n\u003e See [RedisLabs: Basic counting sempahore](https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/6-3-1-building-a-basic-counting-semaphore/)\n\nThis implementation is slightly different from the algorithm described in the book, but the main idea has not changed.\n\n`zrank` check replaced with `zcard`, so now it is fair as [RedisLabs: Fair semaphore](https://redislabs.com/ebook/part-2-core-concepts/chapter-6-application-components-in-redis/6-3-counting-semaphores/6-3-2-fair-semaphores/) (see tests).\n\nIn edge cases (node time difference is greater than `lockTimeout`) both algorithms are not fair due cleanup stage (removing expired members from sorted set), so `FairSemaphore` API has been removed (it's safe to replace it with `Semaphore`).\n\nMost reliable way to use: `lockTimeout` is greater than possible node clock differences, `refreshInterval` is not 0 and is less enough than `lockTimeout` (by default is `lockTimeout * 0.8`)\n\n##### new Semaphore(redisClient, key, maxCount [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])\n\n- `redisClient` - **required**, configured `redis` client\n- `key` - **required**, key for locking resource (final key in redis: `semaphore:\u003ckey\u003e`)\n- `maxCount` - **required**, maximum simultaneously resource usage count\n- `options` _optional_ See `Mutex` options\n\n#### Example\n\n```javascript\nconst Semaphore = require('redis-semaphore').Semaphore\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { Semaphore } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClient = new Redis()\n\nasync function doSomething() {\n  const semaphore = new Semaphore(redisClient, 'lockingResource', 5)\n  await semaphore.acquire()\n  try {\n    // maximum 5 simultaneously executions\n  } finally {\n    await semaphore.release()\n  }\n}\n```\n\n### MultiSemaphore\n\nSame as `Semaphore` with one difference - MultiSemaphore will try to acquire multiple permits instead of one.\n\n`MultiSemaphore` and `Semaphore` shares same key namespace and can be used together (see test/src/RedisMultiSemaphore.test.ts).\n\n##### new MultiSemaphore(redisClient, key, maxCount, permits [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])\n\n- `redisClient` - **required**, configured `redis` client\n- `key` - **required**, key for locking resource (final key in redis: `semaphore:\u003ckey\u003e`)\n- `maxCount` - **required**, maximum simultaneously resource usage count\n- `permits` - **required**, number of acquiring permits\n- `options` _optional_ See `Mutex` options\n\n#### Example\n\n```javascript\nconst MultiSemaphore = require('redis-semaphore').MultiSemaphore\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { MultiSemaphore } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClient = new Redis()\n\nasync function doSomething() {\n  const semaphore = new MultiSemaphore(redisClient, 'lockingResource', 5, 2)\n\n  await semaphore.acquire()\n  try {\n    // make 2 parallel calls to remote service which allow only 5 simultaneously calls\n  } finally {\n    await semaphore.release()\n  }\n}\n```\n\n### RedlockMutex\n\nDistributed `Mutex` version\n\n\u003e See [The Redlock algorithm](https://redis.io/topics/distlock#the-redlock-algorithm)\n\n##### new RedlockMutex(redisClients, key [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])\n\n- `redisClients` - **required**, array of configured `redis` client connected to independent nodes\n- `key` - **required**, key for locking resource (final key in redis: `mutex:\u003ckey\u003e`)\n- `options` _optional_ See `Mutex` options\n\n#### Example\n\n```javascript\nconst RedlockMutex = require('redis-semaphore').RedlockMutex\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { RedlockMutex } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClients = [\n  new Redis('127.0.0.1:6377'),\n  new Redis('127.0.0.1:6378'),\n  new Redis('127.0.0.1:6379')\n] // \"Those nodes are totally independent, so we don’t use replication or any other implicit coordination system.\"\n\nasync function doSomething() {\n  const mutex = new RedlockMutex(redisClients, 'lockingResource')\n  await mutex.acquire()\n  try {\n    // critical code\n  } finally {\n    await mutex.release()\n  }\n}\n```\n\n### RedlockSemaphore\n\nDistributed `Semaphore` version\n\n\u003e See [The Redlock algorithm](https://redis.io/topics/distlock#the-redlock-algorithm)\n\n##### new RedlockSemaphore(redisClients, key, maxCount [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])\n\n- `redisClients` - **required**, array of configured `redis` client connected to independent nodes\n- `key` - **required**, key for locking resource (final key in redis: `semaphore:\u003ckey\u003e`)\n- `maxCount` - **required**, maximum simultaneously resource usage count\n- `options` _optional_ See `Mutex` options\n\n#### Example\n\n```javascript\nconst RedlockSemaphore = require('redis-semaphore').RedlockSemaphore\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { RedlockSemaphore } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClients = [\n  new Redis('127.0.0.1:6377'),\n  new Redis('127.0.0.1:6378'),\n  new Redis('127.0.0.1:6379')\n] // \"Those nodes are totally independent, so we don’t use replication or any other implicit coordination system.\"\n\nasync function doSomething() {\n  const semaphore = new Semaphore(redisClients, 'lockingResource', 5)\n  await semaphore.acquire()\n  try {\n    // maximum 5 simultaneously executions\n  } finally {\n    await semaphore.release()\n  }\n}\n```\n\n### RedlockMultiSemaphore\n\nDistributed `MultiSemaphore` version\n\n\u003e See [The Redlock algorithm](https://redis.io/topics/distlock#the-redlock-algorithm)\n\n##### new RedlockMultiSemaphore(redisClients, key, maxCount, permits [, { lockTimeout = 10000, acquireTimeout = 10000, acquireAttemptsLimit = Number.POSITIVE_INFINITY, retryInterval = 10, refreshInterval = lockTimeout * 0.8 }])\n\n- `redisClients` - **required**, array of configured `redis` client connected to independent nodes\n- `key` - **required**, key for locking resource (final key in redis: `semaphore:\u003ckey\u003e`)\n- `maxCount` - **required**, maximum simultaneously resource usage count\n- `permits` - **required**, number of acquiring permits\n- `options` _optional_ See `Mutex` options\n\n#### Example\n\n```javascript\nconst RedlockMultiSemaphore = require('redis-semaphore').RedlockMultiSemaphore\nconst Redis = require('ioredis')\n\n// TypeScript\n// import { RedlockMultiSemaphore } from 'redis-semaphore'\n// import Redis from 'ioredis'\n\nconst redisClients = [\n  new Redis('127.0.0.1:6377'),\n  new Redis('127.0.0.1:6378'),\n  new Redis('127.0.0.1:6379')\n] // \"Those nodes are totally independent, so we don’t use replication or any other implicit coordination system.\"\n\nasync function doSomething() {\n  const semaphore = new RedlockMultiSemaphore(\n    redisClients,\n    'lockingResource',\n    5,\n    2\n  )\n\n  await semaphore.acquire()\n  try {\n    // make 2 parallel calls to remote service which allow only 5 simultaneously calls\n  } finally {\n    await semaphore.release()\n  }\n}\n```\n\n## Development\n\n```shell\nyarn --immutable\n./setup-redis-servers.sh\nyarn dev\n```\n\n## License\n\nMIT\n\n[![FOSSA Status][fossa-large-image]][fossa-large-url]\n\n[npm-image]: https://img.shields.io/npm/v/redis-semaphore.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/redis-semaphore\n[ci-image]: https://github.com/swarthy/redis-semaphore/actions/workflows/branches.yml/badge.svg\n[ci-url]: https://github.com/swarthy/redis-semaphore/actions/workflows/branches.yml\n[codeclimate-image]: https://api.codeclimate.com/v1/badges/02778c96bb5983eb150c/maintainability\n[codeclimate-url]: https://codeclimate.com/github/swarthy/redis-semaphore/maintainability\n[snyk-image]: https://snyk.io/test/npm/redis-semaphore/badge.svg\n[snyk-url]: https://snyk.io/test/npm/redis-semaphore\n[coverage-image]: https://coveralls.io/repos/github/swarthy/redis-semaphore/badge.svg?branch=master\n[coverage-url]: https://coveralls.io/r/swarthy/redis-semaphore?branch=master\n[fossa-badge-image]: https://app.fossa.com/api/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git.svg?type=shield\n[fossa-badge-url]: https://app.fossa.com/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git?ref=badge_shield\n[fossa-large-image]: https://app.fossa.com/api/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git.svg?type=large\n[fossa-large-url]: https://app.fossa.com/projects/custom%2B10538%2Fgit%40github.com%3Aswarthy%2Fredis-semaphore.git?ref=badge_large\n[typescript-image]: https://badgen.net/npm/types/tslib\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswarthy%2Fredis-semaphore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswarthy%2Fredis-semaphore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswarthy%2Fredis-semaphore/lists"}