proper-lockfile

repository·master·Indexed 42 days ago

https://github.com/moxystudio/node-proper-lockfile

An inter-process and inter-machine lockfile utility for local and network file systems using an atomic mkdir strategy. Version 4.1.2 provides asynchronous and synchronous APIs including lock(), unlock(), and check() to manage file locks with staleness prevention and compromise detection.

Tokens
2.3K
Snippets
10
Records
13
Agent score
5%

What's inside proper-lockfile

  1. Understanding lockfile compromise and limitations

    master

    What is a compromised lock?

    proper-lockfile detects if the lock is compromised when:

    • Updates to the lockfile mtime fail.
    • Updates take longer than expected.

    Limitations (What is NOT detected)

    proper-lockfile cannot detect:

    • If a lockfile was manually deleted and a new lock was acquired immediately after.
    • If different stale or update values are being used for the same file by different processes (which can lead to multiple locks on the same file).
  2. How proper-lockfile works (Design)

    master

    The library uses a mkdir strategy to achieve atomic file locking, which works reliably on both local and network file systems (unlike O_EXCL which can be broken on NFS).

    Key Mechanisms:

    • Lockfile Path: A lockfile is created by suffixing the target file path with .lock.
    • Staleness Prevention: When a lock is acquired, the lockfile's mtime (modified time) is periodically updated. This prevents the lock from being considered "stale" while the process is still running.
    • Staleness Detection: A lock is considered stale if its mtime has not been updated within a specified stale threshold.
    • Compromise Detection: If updating the mtime fails multiple times, the lock is considered compromised.
  3. Release a lock with .unlock(file, [options])

    master

    Releases a previously acquired lock on file. While it is recommended to use the release function returned by .lock(), .unlock() is useful when you cannot maintain a reference to the release function.

    Options:

    • realpath: Resolve symlinks using realpath. Defaults to true (requires file to exist).
    • fs: A custom fs implementation. Defaults to graceful-fs.
    • lockfilePath: Custom path for the lockfile.
    const lockfile = require('proper-lockfile');
    
    lockfile.lock('some/file')
    .then(() => {
        // Do something while the file is locked
    
        // Later..
        return lockfile.unlock('some/file');
    });
  4. Check if a file is locked with .check(file, [options])

    master

    Checks if the file is currently locked and its lockfile is not stale. Returns a Promise that resolves to a boolean.

    Options:

    • stale: Duration in ms after which the lock is considered stale. Defaults to 10000 (min 5000).
    • realpath: Resolve symlinks using realpath. Defaults to true (requires file to exist).
    • fs: A custom fs implementation. Defaults to graceful-fs.
    • lockfilePath: Custom path for the lockfile.
    const lockfile = require('proper-lockfile');
    
    lockfile.check('some/file')
    .then((isLocked) => {
        // isLocked will be true if 'some/file' is locked, false otherwise
    });
  5. Acquire a lock with .lock(file, [options])

    master

    Tries to acquire a lock on the specified file. If successful, it returns a Promise that resolves to a release function. You should call this release function to unlock the file.

    Options:

    • stale: Duration in ms after which the lock is considered stale. Defaults to 10000 (min 5000).
    • update: Interval in ms to update the lockfile's mtime. Defaults to stale/2 (min 1000, max stale/2).
    • retries: Number of retries or a retry options object. Defaults to 0.
    • realpath: Resolve symlinks using realpath. Defaults to true (requires file to exist).
    • fs: A custom fs implementation. Defaults to graceful-fs.
    • onCompromised: Callback if the lock is compromised. Defaults to throwing an error.
    • lockfilePath: Custom path for the lockfile (e.g., <dir path>/dir.lock).
    const lockfile = require('proper-lockfile');
    
    lockfile.lock('some/file')
    .then((release) => {
        // Do something while the file is locked
    
        // Call the provided release function when you're done
        return release();
    })
    .catch((e) => {
        // either lock could not be acquired
        // or releasing it failed
        console.error(e);
    });
  6. Synchronous API: lockSync, unlockSync, and checkSync

    master

    For synchronous execution, use the following methods:

    • .lockSync(file, [options]): Returns the release function or throws on error.
    • .unlockSync(file, [options]): Releases the lock. Throws on error.
    • .checkSync(file, [options]): Returns a boolean or throws on error.
  7. Acquire a file lock with lock()

    master

    The lock(file, [options]) function is the primary asynchronous entry point for acquiring a lock on a specific file. It returns a Promise that resolves to a release function. When the release function is called, the lock is removed.

    Parameters:

    • file: The path to the file you want to lock.
    • options: Configuration options for the lock (see specific option documentation in the library).

    Returns:

    • Promise<Function>: A promise that resolves to a release function used to unlock the file.
    const lock = require('proper-lockfile');
    
    async function performTask() {
        const release = await lock('path/to/file.txt');
        try {
            // Perform atomic operations on the file here
        } finally {
            // Always release the lock in a finally block
            await release();
        }
    }
  8. Release a lock synchronously with unlockSync()

    master

    The unlockSync(file, [options]) function provides a synchronous way to release a lock on a file.

    Parameters:

    • file: The path to the file to unlock.
    • options: Configuration options for the unlock operation.

    Returns:

    • void: Returns immediately after the unlock operation is performed.
    const lock = require('proper-lockfile');
    
    lock.unlockSync('path/to/file.txt');
  9. Release a lock with unlock()

    master

    The unlock(file, [options]) function allows you to explicitly release a lock on a file asynchronously. While the release function returned by lock() is the preferred way to unlock, unlock() can be used to target a specific file path.

    Parameters:

    • file: The path to the file to unlock.
    • options: Configuration options for the unlock operation.

    Returns:

    • Promise<void>: A promise that resolves when the lock has been released.
    const lock = require('proper-lockfile');
    
    async function releaseManually() {
        await lock.unlock('path/to/file.txt');
    }
  10. Check if a file is locked with check()

    master

    The check(file, [options]) function checks whether a lock exists on the specified file.

    Parameters:

    • file: The path to the file to check.
    • options: Configuration options for the check operation.

    Returns:

    • Promise<boolean>: A promise that resolves to true if the file is locked, and false otherwise.
    const lock = require('proper-lockfile');
    
    async function isFileBusy() {
        const isLocked = await lock.check('path/to/file.txt');
        console.log(isLocked ? 'File is locked' : 'File is free');
    }
  11. Check if a file is locked synchronously with checkSync()

    master

    The checkSync(file, [options]) function provides a synchronous way to check if a lock exists on a file.

    Parameters:

    • file: The path to the file to check.
    • options: Configuration options for the check operation.

    Returns:

    • boolean: true if the file is locked, false otherwise.
    const lock = require('proper-lockfile');
    
    const isLocked = lock.checkSync('path/to/file.txt');