Inter-Process Lockfile Utility

4.1.2 · active · verified Sun Apr 19

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.

Common errors

Warnings

Install

Imports

Quickstart

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.

import { lock, unlock } from 'proper-lockfile';
import { promises as fs } from 'fs';
import path from 'path';

const filePath = path.join(process.cwd(), 'my-resource.txt');
const lockFileOptions = {
  stale: 15000, // Consider lock stale after 15 seconds
  update: 5000, // Update mtime every 5 seconds
  retries: {
    retries: 5,
    factor: 2,
    minTimeout: 1000,
    maxTimeout: 10000,
    randomize: true,
  },
  onCompromised: (err) => {
    console.error('Lock was compromised:', err.message);
    // Implement critical error handling here, e.g., exit process
    process.exit(1);
  },
};

async function accessResourceWithLock() {
  let release;
  try {
    // Ensure the target file exists before trying to lock it
    await fs.writeFile(filePath, 'Initial content.', { flag: 'a+' });

    console.log('Attempting to acquire lock...');
    release = await lock(filePath, lockFileOptions);
    console.log('Lock acquired. Performing sensitive operation...');

    // Simulate work
    await new Promise(resolve => setTimeout(resolve, Math.random() * 3000 + 1000));
    await fs.appendFile(filePath, `\nAccessed at ${new Date().toISOString()} by process ${process.pid}`);

    console.log('Sensitive operation complete. Releasing lock...');
  } catch (error) {
    console.error('Failed to acquire or release lock:', error.message);
    if (error.code === 'ELOCKED') {
      console.warn('File is already locked by another process.');
    }
  } finally {
    if (release) {
      try {
        await release();
        console.log('Lock released successfully.');
      } catch (error) {
        console.error('Error releasing lock:', error.message);
      }
    } else {
        // If lock was never acquired, or release failed (e.g. compromised), ensure cleanup.
        // In a real scenario, you might also attempt `unlock(filePath)` here if `release` failed
        // but only if you are confident it's safe to force-unlock.
        console.log('No release function available or lock acquisition failed. No explicit release needed/possible.');
    }
  }
}

accessResourceWithLock();

view raw JSON →