Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Commit d38ea9f

Browse files
Merge pull request #8 from AtomLinter/node-path-tester-tests
Add NodePathTester tests
2 parents 56fa375 + 02a4f7d commit d38ea9f

File tree

8 files changed

+272
-116
lines changed

8 files changed

+272
-116
lines changed

lib/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const Config = {
9191
if (!configChanged(newConfig, this._currentConfig)) { return; }
9292
if (this._currentConfig) {
9393
for (let handler of this.handlers) {
94-
handler(newConfig, this._currentConfig || {});
94+
handler(newConfig, this._currentConfig);
9595
}
9696
}
9797
this._currentConfig = newConfig;

lib/job-manager.js

Lines changed: 73 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,63 +28,97 @@ class InvalidWorkerError extends Error {
2828
}
2929
}
3030

31+
// A class that handles creating, maintaining, and communicating with the
32+
// worker that we spawn to perform linting.
3133
class JobManager {
3234
constructor () {
3335
this.handlersForJobs = new Map();
3436
this.worker = null;
3537
this.workerPath = Path.join(__dirname, 'worker.js');
36-
37-
this.createWorker();
3838
}
3939

4040
dispose () {
4141
this.killWorker();
42+
this.worker = null;
43+
this._workerPromise = null;
44+
this.handlersForJobs = null;
4245
}
4346

47+
// Resolves when the worker is spawned and ready to process messages. Rejects
48+
// if the worker errors during startup.
4449
createWorker () {
50+
// When reloading an existing project with X tabs open, this method will be
51+
// called X times in a very short span of time. They all need to wait for
52+
// the _same_ worker to spawn instead of each trying to spawn their own.
53+
if (this._workerPromise) {
54+
// The worker is already in the process of being created.
55+
return this._workerPromise;
56+
}
57+
4558
let nodeBin = Config.get('nodeBin');
46-
console.debug('JobManager creating worker at:', nodeBin, this.workerPath);
59+
console.debug('JobManager creating worker at:', nodeBin);
4760
this.killWorker();
4861

49-
// We should not try to start the worker without testing the value we have
50-
// for `nodeBin`.
62+
// We choose to do a sync test here because this method is much easier to
63+
// reason about without an `await` keyword introducing side effects.
5164
//
52-
// When the setting is changed after initialization, we test it
53-
// asynchronously before calling `createWorker` again. In those cases,
54-
// `testSync` just looks up the result of that test so we don't duplicate
55-
// effort.
65+
// In practice, this will result in only one brief call to `execSync` when
66+
// a project window is created/reloaded; subsequent calls with the same
67+
// `nodeBin` argument will reuse the earlier value.
5668
//
57-
// But on startup, we don't want to defer creation of this worker while we
58-
// perform an async test of `nodeBin`. So in that one scenario, `testSync`
59-
// will do an `execSync` on this value to perform a sanity check. Like the
60-
// async version, we remember this result, so further calls to `testSync`
61-
// with the same value won't block while we run a shell command.
62-
//
63-
// TODO: See if there's a way to use the async test logic on startup
64-
// without putting us in async/promise hell.
65-
if (!NodePathTester.testSync(nodeBin)) {
66-
console.error('Invalid nodeBin!');
69+
// When `nodeBin` is changed in the middle of a session, we validate the
70+
// new value asynchronously _before_ we reach this method, and `testSync`
71+
// merely looks up the async validation's result.
72+
let isValid = NodePathTester.testSync(nodeBin);
73+
if (!isValid) {
6774
this.worker = false;
68-
return false;
75+
throw new InvalidWorkerError();
6976
}
7077

71-
this.worker = spawn(nodeBin, [this.workerPath]);
78+
let promise = new Promise((resolve, reject) => {
79+
this.worker = spawn(nodeBin, [this.workerPath]);
80+
81+
// Reject this promise if the worker fails to spawn.
82+
this.worker.on('error', reject);
83+
84+
this.worker.stdout
85+
.pipe(ndjson.parse())
86+
.on('data', (obj) => {
87+
// We could listen for the `spawn` event to know when the worker is
88+
// ready, but that event wasn't added until Node v14.17. Instead,
89+
// we'll just have the worker emit a `ready` message.
90+
if (obj.type === 'ready') {
91+
resolve();
92+
} else {
93+
this.receiveMessage(obj);
94+
}
95+
});
96+
97+
// Even unanticipated runtime errors will get sent as newline-delimited
98+
// JSON.
99+
this.worker.stderr
100+
.pipe(ndjson.parse())
101+
.on('data', this.receiveError.bind(this));
102+
103+
this.worker.on('close', () => {
104+
if (this.worker.killed === false) {
105+
this.createWorker();
106+
}
107+
});
108+
});
72109

73-
this.worker.stdout
74-
.pipe(ndjson.parse())
75-
.on('data', this.receiveMessage.bind(this));
110+
this._workerPromise = promise;
111+
this._workerPromise
112+
.then(() => this._workerPromise = null)
113+
.catch(() => this._workerPromise = null);
76114

77-
// Even unanticipated runtime errors will get sent as newline-delimited
78-
// JSON.
79-
this.worker.stderr
80-
.pipe(ndjson.parse())
81-
.on('data', this.receiveError.bind(this));
115+
return promise;
116+
}
82117

83-
this.worker.on('close', () => {
84-
if (this.worker.killed === false) {
85-
this.createWorker();
86-
}
87-
});
118+
suspend () {
119+
console.warn('Suspending worker');
120+
this.killWorker();
121+
this.worker = null;
88122
}
89123

90124
killWorker () {
@@ -136,20 +170,14 @@ class JobManager {
136170
}
137171

138172
async send (bundle) {
173+
if (!this.worker) {
174+
console.warn('Creating worker');
175+
await this.createWorker();
176+
}
177+
139178
let key = generateKey();
140179
bundle.key = key;
141180
console.debug('JobManager#send:', bundle);
142-
try {
143-
this.ensureWorker();
144-
} catch (err) {
145-
if (this.worker === false) {
146-
// `false` means we intentionally refused to create a worker because
147-
// `nodeBin` was invalid.
148-
throw new InvalidWorkerError();
149-
} else {
150-
throw err;
151-
}
152-
}
153181

154182
return new Promise((resolve, reject) => {
155183
this.handlersForJobs.set(key, [resolve, reject]);

0 commit comments

Comments
 (0)