{"id":15378,"library":"rwlockfile","title":"Read/Write Lockfile Utility","description":"rwlockfile is a Node.js utility that provides a file-based readers-writers lock mechanism, allowing multiple readers or a single writer to access a resource. It is currently at version 2.0.25 and appears to be actively maintained as needed. A key differentiator is its explicit support for the standard Readers-Writers Lock design pattern, which the author claims is unique among Node.js packages. Unlike simpler lockfile solutions, rwlockfile allows for flexible, nested locking logic on a single `RWLockfile` instance, where `add()` and `remove()` methods manage an internal counter to ensure the physical lock is only held when needed and released only when all nested calls are complete. This makes it suitable for complex asynchronous workflows requiring fine-grained concurrency control over shared resources.","status":"active","version":"2.0.25","language":"javascript","source_language":"en","source_url":"https://github.com/jdxcode/rwlockfile","tags":["javascript","lockfile","locking","typescript"],"install":[{"cmd":"npm install rwlockfile","lang":"bash","label":"npm"},{"cmd":"yarn add rwlockfile","lang":"bash","label":"yarn"},{"cmd":"pnpm add rwlockfile","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"RWLockfile is a named export. ESM import should use destructuring. CommonJS also uses destructuring from `require()`.","wrong":"import RWLockfile from 'rwlockfile';","symbol":"RWLockfile","correct":"import { RWLockfile } from 'rwlockfile';"},{"note":"Even in CommonJS, the `RWLockfile` class is a named export and must be destructured from the `require()` call.","wrong":"const RWLockfile = require('rwlockfile');","symbol":"RWLockfile","correct":"const { RWLockfile } = require('rwlockfile');"},{"note":"For TypeScript users, import types using `import type` to prevent runtime import issues if not bundled correctly, though direct named import `import { RWLockfileOptions } from 'rwlockfile'` often works if tree-shaking is effective.","wrong":"import { RWLockfileOptions } from 'rwlockfile';","symbol":"RWLockfileOptions","correct":"import type { RWLockfileOptions } from 'rwlockfile';"}],"quickstart":{"code":"import { RWLockfile } from 'rwlockfile';\n\nasync function runLockExample() {\n  // 'lockfile-target' is the base path; '.lock' will be appended for the actual lock file.\n  const lock = new RWLockfile('lockfile-target', {\n    timeout: 5000, // Wait up to 5 seconds for a lock\n    retryInterval: 50 // Check every 50ms\n  });\n\n  try {\n    console.log('Attempting to acquire a write lock...');\n    await lock.add('write');\n    console.log('Write lock acquired. Performing critical write operation...');\n    await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work\n    console.log('Write operation complete. Releasing lock.');\n  } finally {\n    await lock.remove('write');\n    console.log('Write lock released.');\n  }\n\n  console.log('\\nAttempting to acquire a read lock...');\n  await lock.add('read');\n  try {\n    console.log('Read lock acquired. Performing read operation...');\n    await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work\n    console.log('Read operation complete. Releasing lock.');\n  } finally {\n    await lock.remove('read');\n    console.log('Read lock released.');\n  }\n\n  // Demonstrate nested locking (instance-specific counting)\n  async function nestedWrite() {\n    await lock.add('write');\n    try {\n      console.log('  Nested write lock added by inner function.');\n      await new Promise(resolve => setTimeout(resolve, 200));\n    } finally {\n      await lock.remove('write');\n      console.log('  Nested write lock removed by inner function.');\n    }\n  }\n\n  try {\n    console.log('\\nAttempting top-level write lock for nesting...');\n    await lock.add('write');\n    console.log('Top-level write lock acquired.');\n    await nestedWrite(); // Call a function that also adds/removes a lock on the same instance\n    console.log('Back in top-level. Main write operation continuing.');\n    await new Promise(resolve => setTimeout(resolve, 300));\n  } finally {\n    await lock.remove('write');\n    console.log('Top-level write lock finally released after all nested calls.');\n  }\n}\n\nrunLockExample().catch(console.error);\n","lang":"typescript","description":"Demonstrates basic synchronous and asynchronous usage of `RWLockfile` for establishing read and write locks, including an example of the instance-specific nested lock handling described in the documentation, showcasing how `add` and `remove` manage an internal counter before affecting the actual file lock."},"warnings":[{"fix":"Always pair `add()` with a `remove()` call within a `try...finally` block to ensure locks are properly decremented, especially in asynchronous code. Understand that multiple `add('write')` calls on the same instance before a `remove('write')` will only hold one actual file-system write lock.","message":"The `add()` and `remove()` methods manage an internal counter on the `RWLockfile` instance itself, not directly reflecting the global lock status for each call. A physical lock is acquired or released only when this internal counter crosses the 0/1 threshold. This design enables nested function calls to acquire locks without releasing the global lock prematurely, but it can be counter-intuitive if expecting a one-to-one correspondence between method calls and file lock state changes.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Implement external cleanup mechanisms for environments where processes might crash (e.g., cron jobs to remove old lockfiles, or a separate heartbeat mechanism). Consider using shorter `timeout` values and robust error handling to recover from potential deadlocks.","message":"If the process holding a lock crashes abruptly without calling `remove()`, the lockfile may become stale, preventing other processes from acquiring the lock. While the library supports timeouts for acquiring locks, it doesn't automatically detect and clean up stale locks from crashed processes.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Adjust `retryInterval` based on your application's specific needs, balancing responsiveness with resource usage. The library automatically adds some noise and duplicates this number each check, so a base value between 50ms and 500ms is often a good starting point, depending on disk I/O characteristics and expected lock contention.","message":"The `retryInterval` option, which specifies the minimum time between lock checks, can significantly impact performance or the responsiveness of lock acquisition. A very low interval can lead to high CPU usage due to frequent file system polling, while a high interval can make lock acquisition slow and unresponsive.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Increase the `timeout` option when initializing `RWLockfile` if the wait time is acceptable, or investigate the cause of long-held locks. Ensure `remove()` is always called in `finally` blocks. Manually clean up any stale lockfiles from crashed processes if necessary.","cause":"The lock could not be acquired within the specified `timeout` period, likely because another process held the lock for too long or a stale lockfile was present.","error":"Error: ETIMEDOUT: acquire lock timeout"},{"fix":"Use `const { RWLockfile } = require('rwlockfile');` for CommonJS or `import { RWLockfile } from 'rwlockfile';` for ESM, as `RWLockfile` is a named export.","cause":"Incorrect import statement, attempting to use `require('rwlockfile')` or `import RWLockfile from 'rwlockfile'` directly as the constructor without destructuring.","error":"TypeError: RWLockfile is not a constructor"}],"ecosystem":"npm"}