Skip to content

Commit 43faa56

Browse files
client: Support detached for all possible methods
All different methods that can support detached (i.e the server will not terminate when the parent does) will now do so. - A new field has been added in for the ForkOptions so that forked servers can now be detached. - Only MessageTransport and StreamInfo will remain the same. I have not looked in to how those would support properly detaching. Signed-off-by: nkomonen-amazon <nkomonen@amazon.com>
1 parent 7106c4d commit 43faa56

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

client/src/node/main.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export interface ForkOptions {
8282
env?: any;
8383
encoding?: string;
8484
execArgv?: string[];
85+
detached?: boolean;
8586
}
8687

8788
export interface NodeModule {
@@ -130,6 +131,7 @@ export class LanguageClient extends BaseLanguageClient {
130131
private readonly _serverOptions: ServerOptions;
131132
private readonly _forceDebug: boolean;
132133
private _serverProcess: ChildProcess | undefined;
134+
/** Use {@link _setDetached} to set the value unless necessary */
133135
private _isDetached: boolean | undefined;
134136
private _isInDebugMode: boolean;
135137

@@ -285,20 +287,22 @@ export class LanguageClient extends BaseLanguageClient {
285287
if (Is.func(server)) {
286288
return server().then((result) => {
287289
if (MessageTransports.is(result)) {
288-
this._isDetached = !!result.detached;
290+
this._setDetached(!!result.detached);
289291
return result;
290292
} else if (StreamInfo.is(result)) {
291-
this._isDetached = !!result.detached;
293+
this._setDetached(!!result.detached);
292294
return { reader: new StreamMessageReader(result.reader), writer: new StreamMessageWriter(result.writer) };
293295
} else {
294296
let cp: ChildProcess;
297+
let isDetached;
295298
if (ChildProcessInfo.is(result)) {
296299
cp = result.process;
297-
this._isDetached = result.detached;
300+
isDetached = result.detached;
298301
} else {
299302
cp = result;
300-
this._isDetached = false;
303+
isDetached = false;
301304
}
305+
this._setDetached(isDetached, cp);
302306
cp.stderr!.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
303307
return { reader: new StreamMessageReader(cp.stdout!), writer: new StreamMessageWriter(cp.stdin!) };
304308
}
@@ -348,13 +352,17 @@ export class LanguageClient extends BaseLanguageClient {
348352
} else if (Transport.isSocket(transport)) {
349353
args.push(`--socket=${transport.port}`);
350354
}
355+
if (node.options?.detached) {
356+
args.push('--detached');
357+
}
351358
args.push(`--clientProcessId=${process.pid.toString()}`);
352359
if (transport === TransportKind.ipc || transport === TransportKind.stdio) {
353360
const serverProcess = cp.spawn(runtime, args, execOptions);
354361
if (!serverProcess || !serverProcess.pid) {
355362
return handleChildProcessStartError(serverProcess, `Launching server using runtime ${runtime} failed.`);
356363
}
357364
this._serverProcess = serverProcess;
365+
this._setDetached(!!node.options?.detached, serverProcess);
358366
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
359367
if (transport === TransportKind.ipc) {
360368
serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
@@ -369,6 +377,7 @@ export class LanguageClient extends BaseLanguageClient {
369377
return handleChildProcessStartError(process, `Launching server using runtime ${runtime} failed.`);
370378
}
371379
this._serverProcess = process;
380+
this._setDetached(!!node.options?.detached, process);
372381
process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
373382
process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
374383
return transport.onConnected().then((protocol) => {
@@ -382,6 +391,7 @@ export class LanguageClient extends BaseLanguageClient {
382391
return handleChildProcessStartError(process, `Launching server using runtime ${runtime} failed.`);
383392
}
384393
this._serverProcess = process;
394+
this._setDetached(!!node.options?.detached, process);
385395
process.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
386396
process.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
387397
return transport.onConnected().then((protocol) => {
@@ -404,6 +414,9 @@ export class LanguageClient extends BaseLanguageClient {
404414
args.push(`--socket=${transport.port}`);
405415
}
406416
args.push(`--clientProcessId=${process.pid.toString()}`);
417+
if (node.options?.detached) {
418+
args.push('--detached');
419+
}
407420
const options: cp.ForkOptions = node.options ?? Object.create(null);
408421
options.env = getEnvironment(options.env, true);
409422
options.execArgv = options.execArgv || [];
@@ -413,6 +426,7 @@ export class LanguageClient extends BaseLanguageClient {
413426
const sp = cp.fork(node.module, args || [], options);
414427
assertStdio(sp);
415428
this._serverProcess = sp;
429+
this._setDetached(!!options.detached, sp);
416430
sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
417431
if (transport === TransportKind.ipc) {
418432
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
@@ -425,6 +439,7 @@ export class LanguageClient extends BaseLanguageClient {
425439
const sp = cp.fork(node.module, args || [], options);
426440
assertStdio(sp);
427441
this._serverProcess = sp;
442+
this._setDetached(!!options.detached, sp);
428443
sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
429444
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
430445
transport.onConnected().then((protocol) => {
@@ -436,6 +451,7 @@ export class LanguageClient extends BaseLanguageClient {
436451
const sp = cp.fork(node.module, args || [], options);
437452
assertStdio(sp);
438453
this._serverProcess = sp;
454+
this._setDetached(!!options.detached, sp);
439455
sp.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
440456
sp.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
441457
transport.onConnected().then((protocol) => {
@@ -460,6 +476,9 @@ export class LanguageClient extends BaseLanguageClient {
460476
} else if (transport === TransportKind.ipc) {
461477
throw new Error(`Transport kind ipc is not support for command executable`);
462478
}
479+
if (command.options?.detached) {
480+
args.push('--detached');
481+
}
463482
const options = Object.assign({}, command.options);
464483
options.cwd = options.cwd || serverWorkingDir;
465484
if (transport === undefined || transport === TransportKind.stdio) {
@@ -469,7 +488,7 @@ export class LanguageClient extends BaseLanguageClient {
469488
}
470489
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
471490
this._serverProcess = serverProcess;
472-
this._isDetached = !!options.detached;
491+
this._setDetached(!!options.detached, serverProcess);
473492
return Promise.resolve({ reader: new StreamMessageReader(serverProcess.stdout), writer: new StreamMessageWriter(serverProcess.stdin) });
474493
} else if (transport === TransportKind.pipe) {
475494
return createClientPipeTransport(pipeName!).then((transport) => {
@@ -478,7 +497,7 @@ export class LanguageClient extends BaseLanguageClient {
478497
return handleChildProcessStartError(serverProcess, `Launching server using command ${command.command} failed.`);
479498
}
480499
this._serverProcess = serverProcess;
481-
this._isDetached = !!options.detached;
500+
this._setDetached(!!options.detached, serverProcess);
482501
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
483502
serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
484503
return transport.onConnected().then((protocol) => {
@@ -492,7 +511,7 @@ export class LanguageClient extends BaseLanguageClient {
492511
return handleChildProcessStartError(serverProcess, `Launching server using command ${command.command} failed.`);
493512
}
494513
this._serverProcess = serverProcess;
495-
this._isDetached = !!options.detached;
514+
this._setDetached(!!options.detached, serverProcess);
496515
serverProcess.stderr.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
497516
serverProcess.stdout.on('data', data => this.outputChannel.append(Is.string(data) ? data : data.toString(encoding)));
498517
return transport.onConnected().then((protocol) => {
@@ -568,6 +587,21 @@ export class LanguageClient extends BaseLanguageClient {
568587
return Promise.resolve(undefined);
569588
}
570589

590+
/**
591+
* Setter that should be used for {@link _isDetached}. It performs additional
592+
* actions based on the value set for detached.
593+
*/
594+
private _setDetached(isDetached: boolean, serverProcess?: cp.ChildProcess) {
595+
if (!isDetached) {
596+
this._isDetached = false;
597+
return;
598+
}
599+
600+
this._isDetached = true;
601+
// Ensures the parent can exit even if the child (server) is still running
602+
serverProcess?.unref();
603+
}
604+
571605
}
572606

573607
export class SettingMonitor {

0 commit comments

Comments
 (0)