Skip to content

Commit c3c6a33

Browse files
authored
Merge pull request microsoft#182345 from microsoft/tyriar/utility_ptyhost__2
Adopt utility process for pty host on Electron
2 parents dc80eeb + 1b5e250 commit c3c6a33

File tree

9 files changed

+213
-102
lines changed

9 files changed

+213
-102
lines changed

src/vs/code/electron-main/app.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,14 +931,13 @@ export class CodeApplication extends Disposable {
931931
graceTime: LocalReconnectConstants.GraceTime,
932932
shortGraceTime: LocalReconnectConstants.ShortGraceTime,
933933
scrollback: this.configurationService.getValue<number>(TerminalSettingId.PersistentSessionScrollback) ?? 100
934-
}, this.environmentMainService);
934+
}, this.environmentMainService, this.lifecycleMainService, this.logService);
935935
const ptyHostService = new PtyHostService(
936936
ptyHostStarter,
937937
this.configurationService,
938938
this.logService,
939939
this.loggerService
940940
);
941-
ptyHostService.initialize();
942941
services.set(ILocalPtyService, ptyHostService);
943942

944943
// External terminal

src/vs/platform/terminal/common/terminal.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ export enum TerminalIpcChannels {
208208
* Communicates between the shared process and the pty host process.
209209
*/
210210
PtyHost = 'ptyHost',
211+
/**
212+
* Communicates between the renderer process and the pty host process.
213+
*/
214+
PtyHostWindow = 'ptyHostWindow',
211215
/**
212216
* Deals with logging from the pty host process.
213217
*/
@@ -270,7 +274,7 @@ export interface IPtyHostController {
270274
readonly onPtyHostResponsive?: Event<void>;
271275
readonly onPtyHostRequestResolveVariables?: Event<IRequestResolveVariablesEvent>;
272276

273-
restartPtyHost?(): Promise<void>;
277+
restartPtyHost?(): void;
274278
acceptPtyHostResolvedVariables?(requestId: number, resolved: string[]): Promise<void>;
275279
}
276280

src/vs/platform/terminal/electron-main/electronPtyHostStarter.ts

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,105 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
7+
import { parsePtyHostDebugPort } from 'vs/platform/environment/node/environmentService';
8+
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
9+
import { ILogService } from 'vs/platform/log/common/log';
10+
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
711
import { IReconnectConstants } from 'vs/platform/terminal/common/terminal';
8-
import { NodePtyHostStarter } from 'vs/platform/terminal/node/nodePtyHostStarter';
912
import { IPtyHostConnection, IPtyHostStarter } from 'vs/platform/terminal/node/ptyHost';
10-
// import { FileAccess } from 'vs/base/common/network';
11-
// import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
12-
// import { parsePtyHostDebugPort } from 'vs/platform/environment/node/environmentService';
13-
// import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess';
13+
import { UtilityProcess } from 'vs/platform/utilityProcess/electron-main/utilityProcess';
14+
import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
15+
import { IpcMainEvent } from 'electron';
16+
import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
17+
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
18+
import { Emitter } from 'vs/base/common/event';
1419

1520
export class ElectronPtyHostStarter implements IPtyHostStarter {
1621

17-
// private utilityProcess: UtilityProcess | undefined = undefined;
22+
private utilityProcess: UtilityProcess | undefined = undefined;
23+
24+
private readonly _onBeforeWindowConnection = new Emitter<void>();
25+
readonly onBeforeWindowConnection = this._onBeforeWindowConnection.event;
26+
private readonly _onWillShutdown = new Emitter<void>();
27+
readonly onWillShutdown = this._onWillShutdown.event;
1828

1929
constructor(
2030
private readonly _reconnectConstants: IReconnectConstants,
21-
@IEnvironmentService private readonly _environmentService: INativeEnvironmentService
31+
@IEnvironmentService private readonly _environmentService: INativeEnvironmentService,
32+
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
33+
@ILogService private readonly _logService: ILogService
2234
) {
35+
this._lifecycleMainService.onWillShutdown(() => this._onWillShutdown.fire());
36+
// Listen for new windows to establish connection directly to pty host
37+
validatedIpcMain.on('vscode:createPtyHostMessageChannel', (e, nonce) => this._onWindowConnection(e, nonce));
2338
}
2439

2540
start(lastPtyId: number): IPtyHostConnection {
26-
return new NodePtyHostStarter(this._reconnectConstants, this._environmentService).start(lastPtyId);
27-
28-
// console.log('use utility proc');
29-
30-
// // TODO: Convert to use utility process
31-
// const opts: IIPCOptions = {
32-
// serverName: 'Pty Host',
33-
// args: ['--type=ptyHost', '--logsPath', this._environmentService.logsHome.fsPath],
34-
// env: {
35-
// VSCODE_LAST_PTY_ID: lastPtyId,
36-
// VSCODE_PTY_REMOTE: this._isRemote,
37-
// VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain',
38-
// VSCODE_PIPE_LOGGING: 'true',
39-
// VSCODE_VERBOSE_LOGGING: 'true', // transmit console logs from server to client,
40-
// VSCODE_RECONNECT_GRACE_TIME: this._reconnectConstants.graceTime,
41-
// VSCODE_RECONNECT_SHORT_GRACE_TIME: this._reconnectConstants.shortGraceTime,
42-
// VSCODE_RECONNECT_SCROLLBACK: this._reconnectConstants.scrollback
43-
// }
44-
// };
45-
46-
// const ptyHostDebug = parsePtyHostDebugPort(this._environmentService.args, this._environmentService.isBuilt);
47-
// if (ptyHostDebug) {
48-
// if (ptyHostDebug.break && ptyHostDebug.port) {
49-
// opts.debugBrk = ptyHostDebug.port;
50-
// } else if (!ptyHostDebug.break && ptyHostDebug.port) {
51-
// opts.debug = ptyHostDebug.port;
52-
// }
53-
// }
54-
55-
// const client = new Client(FileAccess.asFileUri('bootstrap-fork').fsPath, opts);
56-
57-
// return {
58-
// client,
59-
// dispose: client.dispose,
60-
// onDidProcessExit: client.onDidProcessExit
61-
// };
41+
this.utilityProcess = new UtilityProcess(this._logService, NullTelemetryService, this._lifecycleMainService);
42+
43+
const inspectParams = parsePtyHostDebugPort(this._environmentService.args, this._environmentService.isBuilt);
44+
let execArgv: string[] | undefined = undefined;
45+
if (inspectParams) {
46+
execArgv = ['--nolazy'];
47+
if (inspectParams.break) {
48+
execArgv.push(`--inspect-brk=${inspectParams.port}`);
49+
} else if (!inspectParams.break) {
50+
execArgv.push(`--inspect=${inspectParams.port}`);
51+
}
52+
}
53+
54+
this.utilityProcess.start({
55+
type: 'ptyHost',
56+
entryPoint: 'vs/platform/terminal/node/ptyHostMain',
57+
payload: this._createPtyHostConfiguration(lastPtyId),
58+
execArgv
59+
});
60+
61+
const port = this.utilityProcess.connect();
62+
const client = new MessagePortClient(port, 'ptyHost');
63+
64+
const store = new DisposableStore();
65+
store.add(client);
66+
store.add(this.utilityProcess);
67+
store.add(toDisposable(() => {
68+
validatedIpcMain.removeHandler('vscode:createPtyHostMessageChannel');
69+
this.utilityProcess = undefined;
70+
}));
71+
72+
return {
73+
client,
74+
store,
75+
onDidProcessExit: this.utilityProcess.onExit
76+
};
77+
}
78+
79+
private _createPtyHostConfiguration(lastPtyId: number) {
80+
return {
81+
VSCODE_LAST_PTY_ID: lastPtyId,
82+
VSCODE_AMD_ENTRYPOINT: 'vs/platform/terminal/node/ptyHostMain',
83+
VSCODE_PIPE_LOGGING: 'true',
84+
VSCODE_VERBOSE_LOGGING: 'true', // transmit console logs from server to client,
85+
VSCODE_RECONNECT_GRACE_TIME: this._reconnectConstants.graceTime,
86+
VSCODE_RECONNECT_SHORT_GRACE_TIME: this._reconnectConstants.shortGraceTime,
87+
VSCODE_RECONNECT_SCROLLBACK: this._reconnectConstants.scrollback
88+
};
89+
}
90+
91+
private _onWindowConnection(e: IpcMainEvent, nonce: string) {
92+
this._onBeforeWindowConnection.fire();
93+
94+
const port = this.utilityProcess!.connect();
95+
96+
// Check back if the requesting window meanwhile closed
97+
// Since shared process is delayed on startup there is
98+
// a chance that the window close before the shared process
99+
// was ready for a connection.
100+
101+
if (e.sender.isDestroyed()) {
102+
port.close();
103+
return;
104+
}
105+
106+
e.sender.postMessage('vscode:createPtyHostMessageChannelResult', nonce, [port]);
62107
}
63108
}

src/vs/platform/terminal/node/nodePtyHostStarter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { DisposableStore } from 'vs/base/common/lifecycle';
67
import { FileAccess } from 'vs/base/common/network';
78
import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
89
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -43,9 +44,12 @@ export class NodePtyHostStarter implements IPtyHostStarter {
4344

4445
const client = new Client(FileAccess.asFileUri('bootstrap-fork').fsPath, opts);
4546

47+
const store = new DisposableStore();
48+
store.add(client);
49+
4650
return {
4751
client,
48-
dispose: client.dispose,
52+
store,
4953
onDidProcessExit: client.onDidProcessExit
5054
};
5155
}

src/vs/platform/terminal/node/ptyHost.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Event } from 'vs/base/common/event';
7-
import { IDisposable } from 'vs/base/common/lifecycle';
7+
import { DisposableStore } from 'vs/base/common/lifecycle';
88
import { IChannelClient } from 'vs/base/parts/ipc/common/ipc';
99

10-
export interface IPtyHostConnection extends IDisposable {
10+
export interface IPtyHostConnection {
1111
readonly client: IChannelClient;
12+
readonly store: DisposableStore;
1213
readonly onDidProcessExit: Event<{ code: number; signal: string }>;
1314
}
1415

1516
export interface IPtyHostStarter {
17+
onBeforeWindowConnection?: Event<void>;
18+
onWillShutdown?: Event<void>;
19+
1620
/**
1721
* Creates a pty host and connects to it.
1822
*

src/vs/platform/terminal/node/ptyHostMain.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import { HeartbeatService } from 'vs/platform/terminal/node/heartbeatService';
2121
import { PtyService } from 'vs/platform/terminal/node/ptyService';
2222
import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes';
2323

24+
const _isUtilityProcess = isUtilityProcess(process);
25+
2426
let server: ChildProcessServer<string> | UtilityProcessServer;
25-
if (isUtilityProcess(process)) {
27+
if (_isUtilityProcess) {
2628
server = new UtilityProcessServer();
2729
} else {
2830
server = new ChildProcessServer(TerminalIpcChannels.PtyHost);
@@ -53,7 +55,11 @@ delete process.env.VSCODE_RECONNECT_SHORT_GRACE_TIME;
5355
delete process.env.VSCODE_RECONNECT_SCROLLBACK;
5456

5557
const ptyService = new PtyService(lastPtyId, logService, productService, reconnectConstants);
56-
server.registerChannel(TerminalIpcChannels.PtyHost, ProxyChannel.fromService(ptyService));
58+
const ptyServiceChannel = ProxyChannel.fromService(ptyService);
59+
server.registerChannel(TerminalIpcChannels.PtyHost, ptyServiceChannel);
60+
if (_isUtilityProcess) {
61+
server.registerChannel(TerminalIpcChannels.PtyHostWindow, ptyServiceChannel);
62+
}
5763

5864
process.once('exit', () => {
5965
logService.dispose();

src/vs/platform/terminal/node/ptyHostService.ts

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,28 @@ let lastPtyId = 0;
3636
export class PtyHostService extends Disposable implements IPtyService {
3737
declare readonly _serviceBrand: undefined;
3838

39-
private _connection: IPtyHostConnection;
39+
private __connection?: IPtyHostConnection;
4040
// ProxyChannel is not used here because events get lost when forwarding across multiple proxies
41-
private _proxy: IPtyService;
41+
private __proxy?: IPtyService;
42+
43+
private get _connection(): IPtyHostConnection {
44+
this._ensurePtyHost();
45+
return this.__connection!;
46+
}
47+
private get _proxy(): IPtyService {
48+
this._ensurePtyHost();
49+
return this.__proxy!;
50+
}
51+
52+
private _ensurePtyHost() {
53+
if (!this.__connection) {
54+
[this.__connection, this.__proxy] = this._startPtyHost();
55+
}
56+
}
4257

4358
private readonly _shellEnv: Promise<typeof process.env>;
4459
private readonly _resolveVariablesRequestStore: RequestStore<string[], { workspaceId: string; originalText: string[] }>;
60+
private _wasQuitRequested = false;
4561
private _restartCount = 0;
4662
private _isResponsive = true;
4763
private _isDisposed = false;
@@ -90,20 +106,19 @@ export class PtyHostService extends Disposable implements IPtyService {
90106

91107
this._register(toDisposable(() => this._disposePtyHost()));
92108

109+
93110
this._resolveVariablesRequestStore = this._register(new RequestStore(undefined, this._logService));
94111
this._resolveVariablesRequestStore.onCreateRequest(this._onPtyHostRequestResolveVariables.fire, this._onPtyHostRequestResolveVariables);
95112

96-
[this._connection, this._proxy] = this._startPtyHost();
97-
98-
this._register(this._configurationService.onDidChangeConfiguration(async e => {
99-
if (e.affectsConfiguration(TerminalSettingId.IgnoreProcessNames)) {
100-
await this._refreshIgnoreProcessNames();
101-
}
102-
}));
103-
}
113+
// Force the pty host to start as the first window is starting if the starter has that
114+
// capability
115+
if (this._ptyHostStarter.onBeforeWindowConnection) {
116+
Event.once(this._ptyHostStarter.onBeforeWindowConnection)(() => this._ensurePtyHost());
117+
} else {
118+
this._ensurePtyHost();
119+
}
104120

105-
initialize(): void {
106-
this._refreshIgnoreProcessNames();
121+
this._ptyHostStarter.onWillShutdown?.(() => this._wasQuitRequested = true);
107122
}
108123

109124
private get _ignoreProcessNames(): string[] {
@@ -142,7 +157,7 @@ export class PtyHostService extends Disposable implements IPtyService {
142157
// Handle exit
143158
this._register(connection.onDidProcessExit(e => {
144159
this._onPtyHostExit.fire(e.code);
145-
if (!this._isDisposed) {
160+
if (!this._wasQuitRequested && !this._isDisposed) {
146161
if (this._restartCount <= Constants.MaxRestarts) {
147162
this._logService.error(`ptyHost terminated unexpectedly with code ${e.code}`);
148163
this._restartCount++;
@@ -169,6 +184,16 @@ export class PtyHostService extends Disposable implements IPtyService {
169184
this._register(new RemoteLoggerChannelClient(this._loggerService, client.getChannel(TerminalIpcChannels.Logger)));
170185
});
171186

187+
this.__connection = connection;
188+
this.__proxy = proxy;
189+
190+
this._register(this._configurationService.onDidChangeConfiguration(async e => {
191+
if (e.affectsConfiguration(TerminalSettingId.IgnoreProcessNames)) {
192+
await this._refreshIgnoreProcessNames();
193+
}
194+
}));
195+
this._refreshIgnoreProcessNames();
196+
172197
return [connection, proxy];
173198
}
174199

@@ -315,14 +340,14 @@ export class PtyHostService extends Disposable implements IPtyService {
315340
}
316341

317342
async restartPtyHost(): Promise<void> {
318-
this._isResponsive = true;
319343
this._disposePtyHost();
320-
[this._connection, this._proxy] = this._startPtyHost();
344+
this._isResponsive = true;
345+
this._startPtyHost();
321346
}
322347

323348
private _disposePtyHost(): void {
324349
this._proxy.shutdownAll?.();
325-
this._connection.dispose();
350+
this._connection.store.dispose();
326351
}
327352

328353
private _handleHeartbeat() {

src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export abstract class BaseTerminalBackend extends Disposable {
4444
let unresponsiveNotification: INotificationHandle | undefined;
4545
if (eventSource.onPtyHostStart) {
4646
this._register(eventSource.onPtyHostStart(() => {
47-
this._logService.info(`ptyHost restarted`);
4847
this._onPtyHostRestart.fire();
4948
unresponsiveNotification?.close();
5049
unresponsiveNotification = undefined;

0 commit comments

Comments
 (0)