"use strict";
/* Socket Lib - Built with rolldown */
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const require_primordials_error = require('../primordials/error.js');
const require_primordials_math = require('../primordials/math.js');
const require_primordials_map_set = require('../primordials/map-set.js');
const require_node_fs = require('../node/fs.js');
const require_node_path = require('../node/path.js');
const require_primordials_date = require('../primordials/date.js');
const require_errors_message = require('../errors/message.js');
const require_logger_default = require('../logger/default.js');
const require_promises_retry = require('../promises/retry.js');
const require_fs_safe = require('../fs/safe.js');
const require_events_exit_handler = require('../events/exit/handler.js');

//#region src/process/lock-manager.ts
/**
* @file `ProcessLockManager` — the class that owns active locks, touch timers,
*   and the exit-handler registration. Provides `acquire` / `release` /
*   `withLock`. Co-located with the touch and staleness helpers because they
*   share private state (`activeLocks` + `touchTimers`).
*
*   ## Why directories instead of files?
*
*   This implementation uses `mkdir()` to create lock directories (not files)
*   because:
*
*   1. **Atomic guarantee**: `mkdir()` is guaranteed atomic across ALL
*      filesystems, including NFS. Only ONE process can successfully create the
*      directory. If it exists, `mkdir()` fails with EEXIST instantly with no
*      race conditions.
*   2. **File-based locking issues**:
*
*   - `writeFile()` with `flag: 'wx'` - atomicity can fail on NFS
*   - `open()` with `O_EXCL` - not guaranteed atomic on older NFS
*   - Traditional lockfiles - can have race conditions on network filesystems
*
*   3. **Simplicity**: No need to write/read file content, track PIDs, or manage
*      file descriptors. Just create/delete directory and check mtime.
*   4. **Historical precedent**: Well-known Unix locking pattern used by package
*      managers for decades. Git uses similar approach for `.git/index.lock`.
*
*   ## The mtime trick
*
*   We periodically update the lock directory's mtime (modification time) by
*   "touching" it to signal "I'm still actively working". This prevents other
*   processes from treating the lock as stale and removing it. **The lock
*   directory remains empty** - it's just a sentinel that signals "locked". The
*   mtime is the only data needed to track lock freshness.
*
*   ## npm npx compatibility
*
*   This implementation matches npm npx's concurrency.lock approach:
*
*   - Lock created via `mkdir(path.join(installDir, 'concurrency.lock'))`
*   - 5-second stale timeout (if mtime is older than 5s, lock is stale)
*   - 2-second touching interval (updates mtime every 2s to keep lock fresh)
*   - Automatic cleanup on process exit
*/
const logger = require_logger_default.getDefaultLogger();
/**
* Process lock manager with stale detection and exit cleanup. Provides
* cross-platform inter-process synchronization using file-system based locks.
*/
var ProcessLockManager = class {
	activeLocks = new require_primordials_map_set.SetCtor();
	touchTimers = new require_primordials_map_set.MapCtor();
	exitHandlerRegistered = false;
	/**
	* Ensure process exit handler is registered for cleanup. Registers a handler
	* that cleans up all active locks when the process exits.
	*/
	ensureExitHandler() {
		if (this.exitHandlerRegistered) return;
		/* @__PURE__ */ require_events_exit_handler.onExit(() => {
			/* c8 ignore start - Exit-handler body only fires on real
			process exit. Subprocess test infra would be needed. */
			for (const timer of this.touchTimers.values()) clearInterval(timer);
			this.touchTimers.clear();
			const fs = /* @__PURE__ */ require_node_fs.getNodeFs();
			for (const lockPath of this.activeLocks) try {
				if (fs.existsSync(lockPath)) require_fs_safe.safeDeleteSync(lockPath, { recursive: true });
			} catch {}
			/* c8 ignore stop */
		});
		this.exitHandlerRegistered = true;
	}
	/**
	* Touch a lock file to update its mtime. This prevents the lock from being
	* detected as stale during long operations.
	*
	* @param lockPath - Path to the lock directory.
	*/
	touchLock(lockPath) {
		try {
			const fs = /* @__PURE__ */ require_node_fs.getNodeFs();
			if (fs.existsSync(lockPath)) {
				const now = require_primordials_date.DateNow() / 1e3;
				fs.utimesSync(lockPath, now, now);
			}
		} catch (e) {
			logger.warn(`Failed to touch lock ${lockPath}: ${require_errors_message.errorMessage(e)}`);
		}
		/* c8 ignore stop */
	}
	/**
	* Start periodic touching of a lock file. Aligned with npm npx strategy to
	* prevent false stale detection.
	*
	* @param lockPath - Path to the lock directory.
	* @param intervalMs - Touch interval in milliseconds.
	*/
	startTouchTimer(lockPath, intervalMs) {
		if (intervalMs <= 0 || this.touchTimers.has(lockPath)) return;
		const timer = setInterval(() => {
			this.touchLock(lockPath);
		}, intervalMs);
		timer.unref();
		this.touchTimers.set(lockPath, timer);
	}
	/**
	* Stop periodic touching of a lock file.
	*
	* @param lockPath - Path to the lock directory.
	*/
	stopTouchTimer(lockPath) {
		const timer = this.touchTimers.get(lockPath);
		if (timer) {
			clearInterval(timer);
			this.touchTimers.delete(lockPath);
		}
	}
	/**
	* Check if a lock is stale based on mtime. Uses second-level granularity to
	* avoid APFS floating-point precision issues. Aligned with npm's npx locking
	* strategy.
	*
	* @param lockPath - Path to the lock directory.
	* @param staleMs - Stale timeout in milliseconds.
	*
	* @returns True if lock exists and is stale
	*/
	isStale(lockPath, staleMs) {
		try {
			const stats = (/* @__PURE__ */ require_node_fs.getNodeFs()).statSync(lockPath, { throwIfNoEntry: false });
			if (!stats) return false;
			return require_primordials_date.DateNow() - stats.mtime.getTime() > staleMs;
		} catch {
			return false;
		}
		/* c8 ignore stop */
	}
	/**
	* Acquire a lock using mkdir for atomic operation. Handles stale locks and
	* includes exit cleanup.
	*
	* This method attempts to create a lock directory atomically. If the lock
	* already exists, it checks if it's stale and removes it before retrying.
	* Uses exponential backoff with jitter for retry attempts.
	*
	* @example
	*   ;```typescript
	*   const release = await processLock.acquire('/tmp/my-lock')
	*   try {
	*     // Critical section
	*   } finally {
	*     release()
	*   }
	*   ```
	*
	* @param lockPath - Path to the lock directory.
	* @param options - Lock acquisition options.
	*
	* @returns Release function to unlock
	*
	* @throws Error if lock cannot be acquired after all retries
	*/
	async acquire(lockPath, options = {}) {
		const { baseDelayMs = 100, maxDelayMs = 1e3, retries = 3, staleMs = 5e3, touchIntervalMs = 2e3 } = options;
		this.ensureExitHandler();
		return await /* @__PURE__ */ require_promises_retry.pRetry(async () => {
			try {
				if (this.isStale(lockPath, staleMs)) {
					logger.log(`Removing stale lock: ${lockPath}`);
					try {
						require_fs_safe.safeDeleteSync(lockPath, { recursive: true });
					} catch {}
				}
				const fs = /* @__PURE__ */ require_node_fs.getNodeFs();
				const parent = (/* @__PURE__ */ require_node_path.getNodePath()).dirname(lockPath);
				if (parent && parent !== "." && parent !== lockPath) fs.mkdirSync(parent, { recursive: true });
				fs.mkdirSync(lockPath);
				this.activeLocks.add(lockPath);
				this.startTouchTimer(lockPath, touchIntervalMs);
				return () => this.release(lockPath);
			} catch (e) {
				const code = e.code;
				if (code === "EEXIST") {
					if (this.isStale(lockPath, staleMs)) throw new require_primordials_error.ErrorCtor(`Stale lock detected: ${lockPath}`);
					throw new require_primordials_error.ErrorCtor(`Lock already exists: ${lockPath}`);
				}
				if (code === "EACCES" || code === "EPERM") throw new require_primordials_error.ErrorCtor(`Permission denied creating lock: ${lockPath}. Check directory permissions or run with appropriate access.`, { cause: e });
				if (code === "EROFS") throw new require_primordials_error.ErrorCtor(`Cannot create lock on read-only filesystem: ${lockPath}`, { cause: e });
				if (code === "ENOTDIR") {
					const lastSlashIndex = require_primordials_math.MathMax(lockPath.lastIndexOf("/"), lockPath.lastIndexOf("\\"));
					const parentDir = lastSlashIndex === -1 ? "." : lockPath.slice(0, lastSlashIndex);
					throw new require_primordials_error.ErrorCtor(`Cannot create lock directory: ${lockPath}\nA path component is a file when it should be a directory.
Parent path: ${parentDir}\nTo resolve:
  1. Check if "${parentDir}" contains a file instead of a directory\n  2. Remove any conflicting files in the path
  3. Ensure the full parent directory structure exists`, { cause: e });
				}
				if (code === "ENOENT") {
					const lastSlashIndex = require_primordials_math.MathMax(lockPath.lastIndexOf("/"), lockPath.lastIndexOf("\\"));
					const parentDir = lastSlashIndex === -1 ? "." : lockPath.slice(0, lastSlashIndex);
					throw new require_primordials_error.ErrorCtor(`Cannot create lock directory: ${lockPath}\nParent directory does not exist: ${parentDir}\nTo resolve:
  1. Ensure the parent directory "${parentDir}" exists\n  2. Create the directory structure: mkdir -p "${parentDir}"\n  3. Check filesystem permissions allow directory creation`, { cause: e });
				}
				throw new require_primordials_error.ErrorCtor(`Failed to acquire lock: ${lockPath}`, { cause: e });
			}
		}, {
			retries,
			baseDelayMs,
			maxDelayMs,
			jitter: true
		});
	}
	/**
	* Release a lock and remove from tracking. Stops periodic touching and
	* removes the lock directory.
	*
	* @example
	*   ;```typescript
	*   processLock.release('/tmp/my-lock')
	*   ```
	*
	* @param lockPath - Path to the lock directory.
	*/
	release(lockPath) {
		this.stopTouchTimer(lockPath);
		try {
			if ((/* @__PURE__ */ require_node_fs.getNodeFs()).existsSync(lockPath)) require_fs_safe.safeDeleteSync(lockPath, { recursive: true });
			this.activeLocks.delete(lockPath);
		} catch (e) {
			logger.warn(`Failed to release lock ${lockPath}: ${require_errors_message.errorMessage(e)}`);
		}
	}
	/**
	* Execute a function with exclusive lock protection. Automatically handles
	* lock acquisition, execution, and cleanup.
	*
	* This is the recommended way to use process locks, as it guarantees cleanup
	* even if the callback throws an error.
	*
	* @example
	*   ;```typescript
	*   const result = await processLock.withLock('/tmp/my-lock', async () => {
	*     // Critical section
	*     return someValue
	*   })
	*   ```
	*
	* @param lockPath - Path to the lock directory.
	* @param fn - Function to execute while holding the lock.
	* @param options - Lock acquisition options.
	*
	* @returns Result of the callback function
	*
	* @throws Error from callback or lock acquisition failure
	*/
	async withLock(lockPath, fn, options) {
		const release = await this.acquire(lockPath, options);
		try {
			return await fn();
		} finally {
			release();
		}
	}
};

//#endregion
exports.ProcessLockManager = ProcessLockManager;