Skip to content

Commit 0a01338

Browse files
committed
Deduplicate child process message dance
1 parent d969a5b commit 0a01338

File tree

1 file changed

+72
-40
lines changed

1 file changed

+72
-40
lines changed

src/node/vscode.ts

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class VscodeProvider {
1313
public readonly serverRootPath: string
1414
public readonly vsRootPath: string
1515
private _vscode?: Promise<cp.ChildProcess>
16+
private timeoutInterval = 10000 // 10s, matches VS Code's timeouts.
1617

1718
public constructor() {
1819
this.vsRootPath = path.resolve(rootPath, "lib/vscode")
@@ -52,33 +53,25 @@ export class VscodeProvider {
5253
const vscode = await this.fork()
5354

5455
logger.debug("setting up vs code...")
55-
return new Promise<ipc.WorkbenchOptions>((resolve, reject) => {
56-
const onExit = (code: number | null) => reject(new Error(`VS Code exited unexpectedly with code ${code}`))
57-
58-
vscode.once("message", (message: ipc.VscodeMessage) => {
59-
logger.debug("got message from vs code", field("message", message))
60-
vscode.off("error", reject)
61-
vscode.off("exit", onExit)
62-
return message.type === "options" && message.id === id
63-
? resolve(message.options)
64-
: reject(new Error("Unexpected response during initialization"))
65-
})
6656

67-
vscode.once("error", reject)
68-
vscode.once("exit", onExit)
69-
70-
this.send(
71-
{
72-
type: "init",
73-
id,
74-
options: {
75-
...options,
76-
startPath,
77-
},
57+
this.send(
58+
{
59+
type: "init",
60+
id,
61+
options: {
62+
...options,
63+
startPath,
7864
},
79-
vscode,
80-
)
65+
},
66+
vscode,
67+
)
68+
69+
const message = await this.onMessage<ipc.OptionsMessage>(vscode, (message): message is ipc.OptionsMessage => {
70+
// There can be parallel initializations so wait for the right ID.
71+
return message.type === "options" && message.id === id
8172
})
73+
74+
return message.options
8275
}
8376

8477
private fork(): Promise<cp.ChildProcess> {
@@ -88,32 +81,71 @@ export class VscodeProvider {
8881

8982
logger.debug("forking vs code...")
9083
const vscode = cp.fork(path.join(this.serverRootPath, "fork"))
91-
vscode.on("error", (error) => {
92-
logger.error(error.message)
84+
85+
const dispose = () => {
86+
vscode.removeAllListeners()
87+
vscode.kill()
9388
this._vscode = undefined
89+
}
90+
91+
vscode.on("error", (error: Error) => {
92+
logger.error(error.message)
93+
if (error.stack) {
94+
logger.debug(error.stack)
95+
}
96+
dispose()
9497
})
98+
9599
vscode.on("exit", (code) => {
96100
logger.error(`VS Code exited unexpectedly with code ${code}`)
97-
this._vscode = undefined
101+
dispose()
98102
})
99103

100-
this._vscode = new Promise((resolve, reject) => {
101-
const onExit = (code: number | null) => reject(new Error(`VS Code exited unexpectedly with code ${code}`))
104+
this._vscode = this.onMessage<ipc.ReadyMessage>(vscode, (message): message is ipc.ReadyMessage => {
105+
return message.type === "ready"
106+
}).then(() => vscode)
102107

103-
vscode.once("message", (message: ipc.VscodeMessage) => {
104-
logger.debug("got message from vs code", field("message", message))
105-
vscode.off("error", reject)
106-
vscode.off("exit", onExit)
107-
return message.type === "ready"
108-
? resolve(vscode)
109-
: reject(new Error("Unexpected response waiting for ready response"))
108+
return this._vscode
109+
}
110+
111+
/**
112+
* Listen to a single message from a process. Reject if the process errors,
113+
* exits, or times out.
114+
*
115+
* `fn` is a function that determines whether the message is the one we're
116+
* waiting for.
117+
*/
118+
private onMessage<T extends ipc.VscodeMessage>(
119+
proc: cp.ChildProcess,
120+
fn: (message: ipc.VscodeMessage) => message is T,
121+
): Promise<T> {
122+
return new Promise((resolve, _reject) => {
123+
const reject = (error: Error) => {
124+
clearTimeout(timeout)
125+
_reject(error)
126+
}
127+
128+
const onExit = (code: number | null) => {
129+
reject(new Error(`VS Code exited unexpectedly with code ${code}`))
130+
}
131+
132+
const timeout = setTimeout(() => {
133+
reject(new Error("timed out"))
134+
}, this.timeoutInterval)
135+
136+
proc.on("message", (message: ipc.VscodeMessage) => {
137+
logger.debug("got message from vscode", field("message", message))
138+
proc.off("error", reject)
139+
proc.off("exit", onExit)
140+
if (fn(message)) {
141+
clearTimeout(timeout)
142+
resolve(message)
143+
}
110144
})
111145

112-
vscode.once("error", reject)
113-
vscode.once("exit", onExit)
146+
proc.once("error", reject)
147+
proc.once("exit", onExit)
114148
})
115-
116-
return this._vscode
117149
}
118150

119151
/**

0 commit comments

Comments
 (0)