{"id":11592,"library":"proper-lockfile","title":"Inter-Process Lockfile Utility","description":"proper-lockfile is a robust JavaScript utility for managing inter-process and inter-machine file locks across local and network file systems. Currently at version 4.1.2, it is actively maintained with updates released as needed. Its core design uses an atomic `mkdir` strategy for lockfile creation, which is more reliable than `open` with `O_EXCL` flags, especially on network file systems (NFS) where `O_EXCL` is prone to race conditions. The library differentiates itself by constantly updating the lockfile's `mtime` (modified time) to accurately check for staleness, a significant improvement over `ctime` (creation time) for long-running processes. Furthermore, it incorporates mechanisms to detect when a lockfile might be compromised due to failed updates or unexpected delays, enhancing overall reliability compared to alternatives.","status":"active","version":"4.1.2","language":"javascript","source_language":"en","source_url":"ssh://git@github.com/moxystudio/node-proper-lockfile","tags":["javascript","lock","locking","file","lockfile","fs","cross-process"],"install":[{"cmd":"npm install proper-lockfile","lang":"bash","label":"npm"},{"cmd":"yarn add proper-lockfile","lang":"bash","label":"yarn"},{"cmd":"pnpm add proper-lockfile","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Used for internal debugging and logging.","package":"debug","optional":true},{"reason":"Handles retries for acquiring locks, providing configurable backoff strategies.","package":"p-retry","optional":false}],"imports":[{"note":"The default export from 'proper-lockfile' is also the `lock` function, so `import lock from 'proper-lockfile';` is also common. Named imports are preferred for clarity.","wrong":"const lock = require('proper-lockfile').lock;","symbol":"lock","correct":"import { lock } from 'proper-lockfile';"},{"note":"While `lock` returns a `release` function, `unlock` provides a direct API to remove a lock if the `release` function reference is lost or cannot be used.","wrong":"const unlock = require('proper-lockfile').unlock;","symbol":"unlock","correct":"import { unlock } from 'proper-lockfile';"},{"note":"Used to synchronously or asynchronously check if a file is currently locked and not stale.","wrong":"const check = require('proper-lockfile').check;","symbol":"check","correct":"import { check } from 'proper-lockfile';"}],"quickstart":{"code":"import { lock, unlock } from 'proper-lockfile';\nimport { promises as fs } from 'fs';\nimport path from 'path';\n\nconst filePath = path.join(process.cwd(), 'my-resource.txt');\nconst lockFileOptions = {\n  stale: 15000, // Consider lock stale after 15 seconds\n  update: 5000, // Update mtime every 5 seconds\n  retries: {\n    retries: 5,\n    factor: 2,\n    minTimeout: 1000,\n    maxTimeout: 10000,\n    randomize: true,\n  },\n  onCompromised: (err) => {\n    console.error('Lock was compromised:', err.message);\n    // Implement critical error handling here, e.g., exit process\n    process.exit(1);\n  },\n};\n\nasync function accessResourceWithLock() {\n  let release;\n  try {\n    // Ensure the target file exists before trying to lock it\n    await fs.writeFile(filePath, 'Initial content.', { flag: 'a+' });\n\n    console.log('Attempting to acquire lock...');\n    release = await lock(filePath, lockFileOptions);\n    console.log('Lock acquired. Performing sensitive operation...');\n\n    // Simulate work\n    await new Promise(resolve => setTimeout(resolve, Math.random() * 3000 + 1000));\n    await fs.appendFile(filePath, `\\nAccessed at ${new Date().toISOString()} by process ${process.pid}`);\n\n    console.log('Sensitive operation complete. Releasing lock...');\n  } catch (error) {\n    console.error('Failed to acquire or release lock:', error.message);\n    if (error.code === 'ELOCKED') {\n      console.warn('File is already locked by another process.');\n    }\n  } finally {\n    if (release) {\n      try {\n        await release();\n        console.log('Lock released successfully.');\n      } catch (error) {\n        console.error('Error releasing lock:', error.message);\n      }\n    } else {\n        // If lock was never acquired, or release failed (e.g. compromised), ensure cleanup.\n        // In a real scenario, you might also attempt `unlock(filePath)` here if `release` failed\n        // but only if you are confident it's safe to force-unlock.\n        console.log('No release function available or lock acquisition failed. No explicit release needed/possible.');\n    }\n  }\n}\n\naccessResourceWithLock();\n","lang":"typescript","description":"Demonstrates how to acquire, use, and release an inter-process lock on a file using asynchronous functions, including error handling and retry options. It also shows basic file system interaction."},"warnings":[{"fix":"Ensure consistent `stale` and `update` options are used for a given lockable file across all processes accessing it. Store these values in a shared configuration.","message":"Using different `stale` or `update` option values for the same file across different processes can lead to race conditions and multiple processes acquiring what they believe is an exclusive lock.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Avoid direct manipulation of `.lock` files. Always use the library's `release()` or `unlock()` functions. Implement robust error handling for `onCompromised` callbacks.","message":"Manual removal of a lockfile by an external process or user can lead to the lock being compromised. `proper-lockfile` detects certain compromises (failed updates) but cannot detect external, arbitrary deletions which would allow another process to acquire a lock immediately after.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always `await` or `.catch()` the promise returned by the `release()` function and implement appropriate error recovery or logging.","message":"The `release()` function returned by `lock()` can reject its promise if the lock has been compromised (e.g., updates failed, or it became stale). Consumers must handle this rejection to prevent unhandled promise rejections or misinterpretations of lock status.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Adjust the `stale` option according to the expected maximum duration of the critical section and the network/filesystem characteristics. The `update` interval (default `stale/2`) should also be considered.","message":"The default `stale` option is 10 seconds (10000ms), with a minimum of 5 seconds (5000ms). Setting `stale` too low for long-running operations or processes with high latency can cause locks to be considered stale prematurely.","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-19T00:00:00.000Z","next_check":"2026-07-18T00:00:00.000Z","problems":[{"fix":"Handle the `ELOCKED` error code (if available) or the general error by implementing retry logic with exponential backoff or waiting for the lock to be released. Configure the `retries` option for `lock()`.","cause":"Another process currently holds the lock, and the current attempt to acquire it did not succeed within the configured retries/timeout.","error":"Error: Lock file is already being held"},{"fix":"Implement robust error handling in the `onCompromised` callback and for the `release()` promise. This usually indicates a critical state requiring process termination or immediate re-evaluation of resource access.","cause":"The lockfile's integrity check failed during a background update, or it was manually removed, indicating the lock is no longer reliably held by this process. This error can be thrown by the `onCompromised` callback or when `release()` is called on a compromised lock.","error":"Error: Lock was already compromised"},{"fix":"Ensure the target file you intend to lock exists before calling `lock()` or `check()`. Use `fs.promises.writeFile(filePath, '', { flag: 'a+' })` to create it if it doesn't exist.","cause":"The base file (e.g., 'path/to/file.txt') did not exist when `lock()` or `check()` was called, and `realpath` option is true (default). `proper-lockfile` expects the target file to exist to resolve its real path and create the `.lock` file alongside it.","error":"Error: ENOENT: no such file or directory, lstat 'path/to/file.txt.lock'"}],"ecosystem":"npm"}