import { bold, cyan } from '../lib/picocolors';
import * as Log from './output/log';
import { getBindingsSync } from './swc';
const RETRY_DELAY_MS = 10;
const MAX_RETRY_MS = 1000;
/**
 * A cross-platform on-disk best-effort advisory exclusive lockfile
 * implementation.
 *
 * On Windows, this opens a file in write mode with the `FILE_SHARE_WRITE` flag
 * unset, so it still allows reading the lockfile. This avoids breaking tools
 * that read the contents of `.next`.
 *
 * On POSIX platforms, this uses `flock()` via `std::fs::File::try_lock`:
 * https://doc.rust-lang.org/std/fs/struct.File.html#method.try_lock
 *
 * On WASM, a dummy implementation is used which always "succeeds" in acquiring
 * the lock.
 *
 * This provides a more idiomatic wrapper around the lockfile APIs exposed on
 * the native bindings object.
 *
 * If this lock is not explicitly closed with `unlock`, we will:
 * - If `unlockOnExit` is set (the default), it will make a best-effort attempt
 *   to unlock the lockfile using `process.on('exit', ...)`. This is preferrable
 *   on Windows where it may take some time after process exit for the operating
 *   system to clean up locks that are not explicitly released by the process.
 * - If we fail to ever release the lockfile, the operating system will clean up
 *   the lock and file descriptor upon process exit.
 */ export class Lockfile {
    constructor(bindings, nativeLockfile){
        this.bindings = bindings;
        this.nativeLockfile = nativeLockfile;
    }
    /**
   * Attempts to create or acquire an exclusive lockfile on disk. Lockfiles are
   * best-effort, depending on the platform.
   *
   * - If we fail to acquire the lock, we return `undefined`.
   * - If we're on wasm, this always returns a dummy `Lockfile` object.
   */ static tryAcquire(path, unlockOnExit = true) {
        const bindings = getBindingsSync();
        if (bindings.isWasm) {
            Log.info(`Skipping creating a lockfile at ${cyan(path)} because we're using WASM bindings`);
            return new Lockfile(bindings, undefined);
        } else {
            let nativeLockfile;
            try {
                nativeLockfile = bindings.lockfileTryAcquireSync(path);
            } catch (e) {
                // this happens if there's an IO error (e.g. `ENOENT`), which is
                // different than if we just didn't acquire the lock
                throw Object.defineProperty(new Error('An IO error occurred while attempting to create and acquire the lockfile', {
                    cause: e
                }), "__NEXT_ERROR_CODE", {
                    value: "E859",
                    enumerable: false,
                    configurable: true
                });
            }
            if (nativeLockfile != null) {
                const jsLockfile = new Lockfile(bindings, nativeLockfile);
                if (unlockOnExit) {
                    const exitListener = ()=>{
                        // Best-Effort: If we don't do this, the operating system will
                        // release the lock for us. This gives an opportunity to delete the
                        // unlocked lockfile (which is not otherwise deleted on POSIX).
                        //
                        // This must be synchronous because `process.on('exit', ...)` is
                        // synchronous.
                        jsLockfile.unlockSync();
                    };
                    process.on('exit', exitListener);
                    jsLockfile.listener = exitListener;
                }
                return jsLockfile;
            } else {
                return undefined;
            }
        }
    }
    /**
   * Attempts to create or acquire a lockfile using `Lockfile.tryAcquire`. If
   * that returns `undefined`, indicating that another process or caller has the
   * lockfile, then this will output an error message and exit the process with
   * a non-zero exit code.
   *
   * This will retry a small number of times. This can be useful when running
   * processes in a loop, e.g. if cleanup isn't fully synchronous due to child
   * parent/processes.
   */ static async acquireWithRetriesOrExit(path, processName, unlockOnExit = true) {
        const startMs = Date.now();
        let lockfile;
        while(Date.now() - startMs < MAX_RETRY_MS){
            lockfile = Lockfile.tryAcquire(path, unlockOnExit);
            if (lockfile !== undefined) break;
            await new Promise((resolve)=>setTimeout(resolve, RETRY_DELAY_MS));
        }
        if (lockfile === undefined) {
            Log.error(`Unable to acquire lock at ${cyan(path)}, is another instance of ${cyan(processName)} running?`);
            Log.info(`${bold('Suggestion:')} If you intended to restart ${cyan(processName)}, terminate the other process, and then try again.`);
            process.exit(1);
        }
        return lockfile;
    }
    /**
   * Releases the lockfile and closes the file descriptor.
   *
   * If this is not called, the lock will be released by the operating system
   * when the file handle is closed during process exit.
   */ async unlock() {
        const { nativeLockfile, listener } = this;
        if (nativeLockfile !== undefined) {
            await this.bindings.lockfileUnlock(nativeLockfile);
        }
        if (listener !== undefined) {
            process.off('exit', listener);
        }
    }
    /**
   * A blocking version of `unlock`.
   */ unlockSync() {
        const { nativeLockfile, listener } = this;
        if (nativeLockfile !== undefined) {
            this.bindings.lockfileUnlockSync(nativeLockfile);
        }
        if (listener !== undefined) {
            process.off('exit', listener);
        }
    }
}

//# sourceMappingURL=lockfile.js.map