perf(ngcc): allow immediately reporting a stale lock file (#37250)

Currently, if an ngcc process is killed in a manner that it doesn't clean
up its lock file (or is killed too quickly) the compiler reports that it
is waiting on the PID of a process that doesn't exist, and that it will
wait up to a maximum of N seconds. This PR updates the locking code to
additionally check if the process exists, and if it does not it will
immediately bail out, and print the location of the lock file so a user
may clean it up.

PR Close #37250
This commit is contained in:
Terence D. Honles
2020-05-21 21:25:00 -07:00
committed by Matias Niemelä
parent 8d82cdfc77
commit 930d204d83
2 changed files with 104 additions and 10 deletions

View File

@ -56,22 +56,68 @@ export class AsyncLocker {
pid = newPid;
}
if (attempts === 0) {
this.logger.info(
// Check to see if the process identified by the PID is still running. Because the
// process *should* clean up after itself, we only check for a stale lock file when the
// PID changes and only once. This may mean you have to wait if the process is killed
// after the first check and isn't given the chance to clean up after itself.
if (!this.isProcessRunning(pid)) {
// try to re-lock one last time in case there was a race condition checking the process.
try {
return this.lockFile.write();
} catch (e2) {
if (e2.code !== 'EEXIST') {
throw e2;
}
}
// finally check that the lock was held by the same process this whole time.
const finalPid = this.lockFile.read();
if (finalPid === pid) {
throw new TimeoutError(this.lockFileMessage(
`Lock found, but no process with PID ${pid} seems to be running.`));
} else {
// attempts is still 0, but adjust the PID so the message below is correct.
pid = finalPid;
}
}
this.logger.info(this.lockFileMessage(
`Another process, with id ${pid}, is currently running ngcc.\n` +
`Waiting up to ${this.retryDelay * this.retryAttempts / 1000}s for it to finish.\n` +
`(If you are sure no ngcc process is running then you should delete the lock-file at ${
this.lockFile.path}.)`);
`Waiting up to ${this.retryDelay * this.retryAttempts / 1000}s for it to finish.`));
}
// The file is still locked by another process so wait for a bit and retry
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
}
}
// If we fall out of the loop then we ran out of rety attempts
throw new TimeoutError(
`Timed out waiting ${
this.retryAttempts * this.retryDelay /
1000}s for another ngcc process, with id ${pid}, to complete.\n` +
`(If you are sure no ngcc process is running then you should delete the lock-file at ${
this.lockFile.path}.)`);
throw new TimeoutError(this.lockFileMessage(`Timed out waiting ${
this.retryAttempts * this.retryDelay /
1000}s for another ngcc process, with id ${pid}, to complete.`));
}
protected isProcessRunning(pid: string): boolean {
// let the normal logic run if this is not called with a valid PID
if (isNaN(+pid)) {
this.logger.debug(`Cannot check if invalid PID "${pid}" is running, a number is expected.`);
return true;
}
try {
process.kill(+pid, 0);
return true;
} catch (e) {
// If the process doesn't exist ESRCH will be thrown, if the error is not that, throw it.
if (e.code !== 'ESRCH') {
throw e;
}
return false;
}
}
private lockFileMessage(message: string): string {
return message +
`\n(If you are sure no ngcc process is running then you should delete the lock-file at ${
this.lockFile.path}.)`;
}
}