forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.ts
95 lines (83 loc) · 3.75 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ChildProcess, ChildProcessByStdio, fork} from 'child_process';
import {Readable, Writable} from 'stream';
import {AbsoluteFsPath, CachedFileSystem, FileSystem} from '../../../../src/ngtsc/file_system';
import {Logger, LogLevel} from '../../logging/logger';
import {getLockFilePath, LockFile} from '../lock_file';
import {removeLockFile} from './util';
/// <reference types="node" />
/**
* This `LockFile` implementation uses a child-process to remove the lock file when the main process
* exits (for whatever reason).
*
* There are a few milliseconds between the child-process being forked and it registering its
* `disconnect` event, which is responsible for tidying up the lock-file in the event that the main
* process exits unexpectedly.
*
* We eagerly create the unlocker child-process so that it maximizes the time before the lock-file
* is actually written, which makes it very unlikely that the unlocker would not be ready in the
* case that the developer hits Ctrl-C or closes the terminal within a fraction of a second of the
* lock-file being created.
*
* The worst case scenario is that ngcc is killed too quickly and leaves behind an orphaned
* lock-file. In which case the next ngcc run will display a helpful error message about deleting
* the lock-file.
*/
export class LockFileWithChildProcess implements LockFile {
path: AbsoluteFsPath;
private unlocker: ChildProcess|null;
constructor(protected fs: FileSystem, protected logger: Logger) {
this.path = getLockFilePath(fs);
this.unlocker = this.createUnlocker(this.path);
}
write(): void {
if (this.unlocker === null) {
// In case we already disconnected the previous unlocker child-process, perhaps by calling
// `remove()`. Normally the LockFile should only be used once per instance.
this.unlocker = this.createUnlocker(this.path);
}
this.logger.debug(`Attemping to write lock-file at ${this.path} with PID ${process.pid}`);
// To avoid race conditions, check for existence of the lock-file by trying to create it.
// This will throw an error if the file already exists.
this.fs.writeFile(this.path, process.pid.toString(), /* exclusive */ true);
this.logger.debug(`Written lock-file at ${this.path} with PID ${process.pid}`);
}
read(): string {
try {
if (this.fs instanceof CachedFileSystem) {
// The lock-file file is "volatile", it might be changed by an external process,
// so we must not rely upon the cached value when reading it.
this.fs.invalidateCaches(this.path);
}
return this.fs.readFile(this.path);
} catch {
return '{unknown}';
}
}
remove() {
removeLockFile(this.fs, this.logger, this.path, process.pid.toString());
if (this.unlocker !== null) {
// If there is an unlocker child-process then disconnect from it so that it can exit itself.
this.unlocker.disconnect();
this.unlocker = null;
}
}
protected createUnlocker(path: AbsoluteFsPath): ChildProcess|null {
this.logger.debug('Forking unlocker child-process');
const logLevel =
this.logger.level !== undefined ? this.logger.level.toString() : LogLevel.info.toString();
const unlocker = fork(this.fs.resolve(__dirname, './unlocker.js'), [path, logLevel], {
detached: true,
stdio: 'pipe',
}) as ChildProcessByStdio<Writable, Readable, Readable>;
unlocker.stdout.on('data', data => process.stdout.write(data));
unlocker.stderr.on('data', data => process.stderr.write(data));
return unlocker;
}
}