1 line
28 KiB
Plaintext
1 line
28 KiB
Plaintext
{"version":3,"file":"LockFile.js","sourceRoot":"","sources":["../src/LockFile.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport * as path from 'path';\nimport * as child_process from 'child_process';\nimport { FileSystem } from './FileSystem';\nimport { FileWriter } from './FileWriter';\nimport { Async } from './Async';\n\n/**\n * http://man7.org/linux/man-pages/man5/proc.5.html\n * (22) starttime %llu\n * The time the process started after system boot. In kernels before Linux 2.6, this value was\n * expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks (divide by\n * sysconf(_SC_CLK_TCK)).\n * The format for this field was %lu before Linux 2.6.\n */\nconst procStatStartTimePos: number = 22;\n\n/**\n * Parses the process start time from the contents of a linux /proc/[pid]/stat file.\n * @param stat - The contents of a linux /proc/[pid]/stat file.\n * @returns The process start time in jiffies, or undefined if stat has an unexpected format.\n */\nexport function getProcessStartTimeFromProcStat(stat: string): string | undefined {\n // Parse the value at position procStatStartTimePos.\n // We cannot just split stat on spaces, because value 2 may contain spaces.\n // For example, when running the following Shell commands:\n // > cp \"$(which bash)\" ./'bash 2)('\n // > ./'bash 2)(' -c 'OWNPID=$BASHPID;cat /proc/$OWNPID/stat'\n // 59389 (bash 2)() S 59358 59389 59358 34818 59389 4202496 329 0 0 0 0 0 0 0 20 0 1 0\n // > rm -rf ./'bash 2)('\n // The output shows a stat file such that value 2 contains spaces.\n // To still umambiguously parse such output we assume no values after the second ends with a right parenthesis...\n\n // trimRight to remove the trailing line terminator.\n let values: string[] = stat.trimRight().split(' ');\n let i: number = values.length - 1;\n while (\n i >= 0 &&\n // charAt returns an empty string if the index is out of bounds.\n values[i].charAt(values[i].length - 1) !== ')'\n ) {\n i -= 1;\n }\n // i is the index of the last part of the second value (but i need not be 1).\n if (i < 1) {\n // Format of stat has changed.\n return undefined;\n }\n const value2: string = values.slice(1, i + 1).join(' ');\n values = [values[0], value2].concat(values.slice(i + 1));\n if (values.length < procStatStartTimePos) {\n // Older version of linux, or non-standard configuration of linux.\n return undefined;\n }\n const startTimeJiffies: string = values[procStatStartTimePos - 1];\n // In theory, the representations of start time returned by `cat /proc/[pid]/stat` and `ps -o lstart` can change\n // while the system is running, but we assume this does not happen.\n // So the caller can safely use this value as part of a unique process id (on the machine, without comparing\n // accross reboots).\n return startTimeJiffies;\n}\n\n/**\n * Helper function that is exported for unit tests only.\n * Returns undefined if the process doesn't exist with that pid.\n */\nexport function getProcessStartTime(pid: number): string | undefined {\n const pidString: string = pid.toString();\n if (pid < 0 || pidString.indexOf('e') >= 0 || pidString.indexOf('E') >= 0) {\n throw new Error(`\"pid\" is negative or too large`);\n }\n let args: string[];\n if (process.platform === 'darwin') {\n args = [`-p ${pidString}`, '-o lstart'];\n } else if (process.platform === 'linux') {\n args = ['-p', pidString, '-o', 'lstart'];\n } else {\n throw new Error(`Unsupported system: ${process.platform}`);\n }\n\n const psResult: child_process.SpawnSyncReturns<string> = child_process.spawnSync('ps', args, {\n encoding: 'utf8'\n });\n const psStdout: string = psResult.stdout;\n\n // If no process with PID pid exists then the exit code is non-zero on linux but stdout is not empty.\n // But if no process exists we do not want to fall back on /proc/*/stat to determine the process\n // start time, so we we additionally test for !psStdout. NOTE: !psStdout evaluates to true if\n // zero bytes are written to stdout.\n if (psResult.status !== 0 && !psStdout && process.platform === 'linux') {\n // Try to read /proc/[pid]/stat and get the value at position procStatStartTimePos.\n let stat: undefined | string;\n try {\n stat = FileSystem.readFile(`/proc/${pidString}/stat`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n // Either no process with PID pid exists, or this version/configuration of linux is non-standard.\n // We assume the former.\n return undefined;\n }\n if (stat !== undefined) {\n const startTimeJiffies: string | undefined = getProcessStartTimeFromProcStat(stat);\n if (startTimeJiffies === undefined) {\n throw new Error(\n `Could not retrieve the start time of process ${pidString} from the OS because the ` +\n `contents of /proc/${pidString}/stat have an unexpected format`\n );\n }\n return startTimeJiffies;\n }\n }\n\n // there was an error executing ps (zero bytes were written to stdout).\n if (!psStdout) {\n throw new Error(`Unexpected output from \"ps\" command`);\n }\n\n const psSplit: string[] = psStdout.split('\\n');\n\n // successfuly able to run \"ps\", but no process was found\n if (psSplit[1] === '') {\n return undefined;\n }\n\n if (psSplit[1]) {\n const trimmed: string = psSplit[1].trim();\n if (trimmed.length > 10) {\n return trimmed;\n }\n }\n\n throw new Error(`Unexpected output from the \"ps\" command`);\n}\n\n/**\n * The `LockFile` implements a file-based mutex for synchronizing access to a shared resource\n * between multiple Node.js processes. It is not recommended for synchronization solely within\n * a single Node.js process.\n * @remarks\n * The implementation works on Windows, Mac, and Linux without requiring any native helpers.\n * On non-Windows systems, the algorithm requires access to the `ps` shell command. On Linux,\n * it requires access the `/proc/${pidString}/stat` filesystem.\n * @public\n */\nexport class LockFile {\n private static _getStartTime: (pid: number) => string | undefined = getProcessStartTime;\n\n private _fileWriter: FileWriter | undefined;\n private _filePath: string;\n private _dirtyWhenAcquired: boolean;\n\n private constructor(fileWriter: FileWriter | undefined, filePath: string, dirtyWhenAcquired: boolean) {\n this._fileWriter = fileWriter;\n this._filePath = filePath;\n this._dirtyWhenAcquired = dirtyWhenAcquired;\n }\n\n /**\n * Returns the path of the lockfile that will be created when a lock is successfully acquired.\n * @param resourceFolder - The folder where the lock file will be created\n * @param resourceName - An alphanumeric name that describes the resource being locked. This will become\n * the filename of the temporary file created to manage the lock.\n * @param pid - The PID for the current Node.js process (`process.pid`), which is used by the locking algorithm.\n */\n public static getLockFilePath(\n resourceFolder: string,\n resourceName: string,\n pid: number = process.pid\n ): string {\n if (!resourceName.match(/^[a-zA-Z0-9][a-zA-Z0-9-.]+[a-zA-Z0-9]$/)) {\n throw new Error(\n `The resource name \"${resourceName}\" is invalid.` +\n ` It must be an alphanumberic string with only \"-\" or \".\" It must start with an alphanumeric character.`\n );\n }\n\n if (process.platform === 'win32') {\n return path.join(path.resolve(resourceFolder), `${resourceName}.lock`);\n } else if (process.platform === 'linux' || process.platform === 'darwin') {\n return path.join(path.resolve(resourceFolder), `${resourceName}#${pid}.lock`);\n }\n\n throw new Error(`File locking not implemented for platform: \"${process.platform}\"`);\n }\n\n /**\n * Attempts to create a lockfile with the given filePath.\n * @param resourceFolder - The folder where the lock file will be created\n * @param resourceName - An alphanumeric name that describes the resource being locked. This will become\n * the filename of the temporary file created to manage the lock.\n * @returns If successful, returns a `LockFile` instance. If unable to get a lock, returns `undefined`.\n */\n public static tryAcquire(resourceFolder: string, resourceName: string): LockFile | undefined {\n FileSystem.ensureFolder(resourceFolder);\n if (process.platform === 'win32') {\n return LockFile._tryAcquireWindows(resourceFolder, resourceName);\n } else if (process.platform === 'linux' || process.platform === 'darwin') {\n return LockFile._tryAcquireMacOrLinux(resourceFolder, resourceName);\n }\n throw new Error(`File locking not implemented for platform: \"${process.platform}\"`);\n }\n\n /**\n * Attempts to create the lockfile. Will continue to loop at every 100ms until the lock becomes available\n * or the maxWaitMs is surpassed.\n *\n * @remarks\n * This function is subject to starvation, whereby it does not ensure that the process that has been\n * waiting the longest to acquire the lock will get it first. This means that a process could theoretically\n * wait for the lock forever, while other processes skipped it in line and acquired the lock first.\n *\n * @param resourceFolder - The folder where the lock file will be created\n * @param resourceName - An alphanumeric name that describes the resource being locked. This will become\n * the filename of the temporary file created to manage the lock.\n * @param maxWaitMs - The maximum number of milliseconds to wait for the lock before reporting an error\n */\n public static acquire(resourceFolder: string, resourceName: string, maxWaitMs?: number): Promise<LockFile> {\n const interval: number = 100;\n const startTime: number = Date.now();\n\n const retryLoop: () => Promise<LockFile> = async () => {\n const lock: LockFile | undefined = LockFile.tryAcquire(resourceFolder, resourceName);\n if (lock) {\n return lock;\n }\n if (maxWaitMs && Date.now() > startTime + maxWaitMs) {\n throw new Error(`Exceeded maximum wait time to acquire lock for resource \"${resourceName}\"`);\n }\n\n await Async.sleep(interval);\n return retryLoop();\n };\n\n return retryLoop();\n }\n\n /**\n * Attempts to acquire the lock on a Linux or OSX machine\n */\n private static _tryAcquireMacOrLinux(resourceFolder: string, resourceName: string): LockFile | undefined {\n let dirtyWhenAcquired: boolean = false;\n\n // get the current process' pid\n const pid: number = process.pid;\n const startTime: string | undefined = LockFile._getStartTime(pid);\n\n if (!startTime) {\n throw new Error(`Unable to calculate start time for current process.`);\n }\n\n const pidLockFilePath: string = LockFile.getLockFilePath(resourceFolder, resourceName);\n let lockFileHandle: FileWriter | undefined;\n\n let lockFile: LockFile;\n\n try {\n // open in write mode since if this file exists, it cannot be from the current process\n // TODO: This will malfunction if the same process tries to acquire two locks on the same file.\n // We should ideally maintain a dictionary of normalized acquired filenames\n lockFileHandle = FileWriter.open(pidLockFilePath);\n lockFileHandle.write(startTime);\n\n const currentBirthTimeMs: number = FileSystem.getStatistics(pidLockFilePath).birthtime.getTime();\n\n let smallestBirthTimeMs: number = currentBirthTimeMs;\n let smallestBirthTimePid: string = pid.toString();\n\n // now, scan the directory for all lockfiles\n const files: string[] = FileSystem.readFolderItemNames(resourceFolder);\n\n // look for anything ending with # then numbers and \".lock\"\n const lockFileRegExp: RegExp = /^(.+)#([0-9]+)\\.lock$/;\n\n let match: RegExpMatchArray | null;\n let otherPid: string;\n for (const fileInFolder of files) {\n if (\n (match = fileInFolder.match(lockFileRegExp)) &&\n match[1] === resourceName &&\n (otherPid = match[2]) !== pid.toString()\n ) {\n // we found at least one lockfile hanging around that isn't ours\n const fileInFolderPath: string = path.join(resourceFolder, fileInFolder);\n dirtyWhenAcquired = true;\n\n // console.log(`FOUND OTHER LOCKFILE: ${otherPid}`);\n\n const otherPidCurrentStartTime: string | undefined = LockFile._getStartTime(parseInt(otherPid, 10));\n\n let otherPidOldStartTime: string | undefined;\n let otherBirthtimeMs: number | undefined;\n try {\n otherPidOldStartTime = FileSystem.readFile(fileInFolderPath);\n // check the timestamp of the file\n otherBirthtimeMs = FileSystem.getStatistics(fileInFolderPath).birthtime.getTime();\n } catch (err) {\n // this means the file is probably deleted already\n }\n\n // if the otherPidOldStartTime is invalid, then we should look at the timestamp,\n // if this file was created after us, ignore it\n // if it was created within 1 second before us, then it could be good, so we\n // will conservatively fail\n // otherwise it is an old lock file and will be deleted\n if (otherPidOldStartTime === '' && otherBirthtimeMs !== undefined) {\n if (otherBirthtimeMs > currentBirthTimeMs) {\n // ignore this file, he will be unable to get the lock since this process\n // will hold it\n // console.log(`Ignoring lock for pid ${otherPid} because its lockfile is newer than ours.`);\n continue;\n } else if (\n otherBirthtimeMs - currentBirthTimeMs < 0 && // it was created before us AND\n otherBirthtimeMs - currentBirthTimeMs > -1000\n ) {\n // it was created less than a second before\n\n // conservatively be unable to keep the lock\n return undefined;\n }\n }\n\n // console.log(`Other pid ${otherPid} lockfile has start time: \"${otherPidOldStartTime}\"`);\n // console.log(`Other pid ${otherPid} actually has start time: \"${otherPidCurrentStartTime}\"`);\n\n // this means the process is no longer executing, delete the file\n if (!otherPidCurrentStartTime || otherPidOldStartTime !== otherPidCurrentStartTime) {\n // console.log(`Other pid ${otherPid} is no longer executing!`);\n FileSystem.deleteFile(fileInFolderPath);\n continue;\n }\n\n // console.log(`Pid ${otherPid} lockfile has birth time: ${otherBirthtimeMs}`);\n // console.log(`Pid ${pid} lockfile has birth time: ${currentBirthTimeMs}`);\n // this is a lockfile pointing at something valid\n if (otherBirthtimeMs !== undefined) {\n // the other lock file was created before the current earliest lock file\n // or the other lock file was created at the same exact time, but has earlier pid\n\n // note that it is acceptable to do a direct comparison of the PIDs in this case\n // since we are establishing a consistent order to apply to the lock files in all\n // execution instances.\n\n // it doesn't matter that the PIDs roll over, we've already\n // established that these processes all started at the same time, so we just\n // need to get all instances of the lock test to agree which one won.\n if (\n otherBirthtimeMs < smallestBirthTimeMs ||\n (otherBirthtimeMs === smallestBirthTimeMs && otherPid < smallestBirthTimePid)\n ) {\n smallestBirthTimeMs = otherBirthtimeMs;\n smallestBirthTimePid = otherPid;\n }\n }\n }\n }\n\n if (smallestBirthTimePid !== pid.toString()) {\n // we do not have the lock\n return undefined;\n }\n\n // we have the lock!\n lockFile = new LockFile(lockFileHandle, pidLockFilePath, dirtyWhenAcquired);\n lockFileHandle = undefined; // we have handed the descriptor off to the instance\n } finally {\n if (lockFileHandle) {\n // ensure our lock is closed\n lockFileHandle.close();\n FileSystem.deleteFile(pidLockFilePath);\n }\n }\n return lockFile;\n }\n\n /**\n * Attempts to acquire the lock using Windows\n * This algorithm is much simpler since we can rely on the operating system\n */\n private static _tryAcquireWindows(resourceFolder: string, resourceName: string): LockFile | undefined {\n const lockFilePath: string = LockFile.getLockFilePath(resourceFolder, resourceName);\n let dirtyWhenAcquired: boolean = false;\n\n let fileHandle: FileWriter | undefined;\n let lockFile: LockFile;\n\n try {\n if (FileSystem.exists(lockFilePath)) {\n dirtyWhenAcquired = true;\n\n // If the lockfile is held by an process with an exclusive lock, then removing it will\n // silently fail. OpenSync() below will then fail and we will be unable to create a lock.\n\n // Otherwise, the lockfile is sitting on disk, but nothing is holding it, implying that\n // the last process to hold it died.\n FileSystem.deleteFile(lockFilePath);\n }\n\n try {\n // Attempt to open an exclusive lockfile\n fileHandle = FileWriter.open(lockFilePath, { exclusive: true });\n } catch (error) {\n // we tried to delete the lock, but something else is holding it,\n // (probably an active process), therefore we are unable to create a lock\n return undefined;\n }\n\n // Ensure we can hand off the file descriptor to the lockfile\n lockFile = new LockFile(fileHandle, lockFilePath, dirtyWhenAcquired);\n fileHandle = undefined;\n } finally {\n if (fileHandle) {\n fileHandle.close();\n }\n }\n\n return lockFile;\n }\n\n /**\n * Unlocks a file and optionally removes it from disk.\n * This can only be called once.\n *\n * @param deleteFile - Whether to delete the lockfile from disk. Defaults to true.\n */\n public release(deleteFile: boolean = true): void {\n if (this.isReleased) {\n throw new Error(`The lock for file \"${path.basename(this._filePath)}\" has already been released.`);\n }\n\n this._fileWriter!.close();\n if (deleteFile) {\n FileSystem.deleteFile(this._filePath);\n }\n this._fileWriter = undefined;\n }\n\n /**\n * Returns the initial state of the lock.\n * This can be used to detect if the previous process was terminated before releasing the resource.\n */\n public get dirtyWhenAcquired(): boolean {\n return this._dirtyWhenAcquired;\n }\n\n /**\n * Returns the absolute path to the lockfile\n */\n public get filePath(): string {\n return this._filePath;\n }\n\n /**\n * Returns true if this lock is currently being held.\n */\n public get isReleased(): boolean {\n return this._fileWriter === undefined;\n }\n}\n"]} |