1
1
import { SendHandle } from 'child_process' ;
2
2
import { VSBuffer } from 'vs/base/common/buffer' ;
3
+ import { Emitter , Event } from 'vs/base/common/event' ;
3
4
import { FileAccess } from 'vs/base/common/network' ;
4
5
import { findFreePort } from 'vs/base/node/ports' ;
6
+ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc' ;
7
+ import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net' ;
5
8
import { IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp' ;
6
9
import { ILogService } from 'vs/platform/log/common/log' ;
7
10
import { DebugMessage , IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection' ;
8
11
import { AbstractConnection } from 'vs/server/connection/abstractConnection' ;
9
12
import { ServerProtocol } from 'vs/server/protocol' ;
10
13
import { IEnvironmentServerService } from 'vs/server/services/environmentService' ;
11
14
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions' ;
12
- import { ExtensionHost } from 'vs/workbench/services/extensions/node/extensionHost' ;
15
+ import { createMessageOfType , MessageType } from 'vs/workbench/services/extensions/common/extensionHostProtocol' ;
16
+ import { ExtensionHostKind , IExtensionHost } from 'vs/workbench/services/extensions/common/extensions' ;
17
+ import { ExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHost' ;
13
18
import { getCachedNlsConfiguration } from 'vs/workbench/services/extensions/node/nls' ;
14
19
15
20
/**
@@ -32,9 +37,19 @@ export interface ForkEnvironmentVariables {
32
37
33
38
/**
34
39
* This complements the client-side `PersistantConnection` in `RemoteExtensionHost`.
40
+ * @see `LocalProcessExtensionHost`
35
41
*/
36
- export class ExtensionHostConnection extends AbstractConnection {
37
- private clientProcess ?: ExtensionHost ;
42
+ export class ExtensionHostConnection extends AbstractConnection implements IExtensionHost {
43
+ public readonly kind = ExtensionHostKind . LocalProcess ;
44
+ public readonly remoteAuthority = null ;
45
+ public readonly lazyStart = false ;
46
+ private _terminating = false ;
47
+
48
+ private readonly _onExit : Emitter < [ number , string ] > = new Emitter < [ number , string ] > ( ) ;
49
+ public readonly onExit : Event < [ number , string ] > = this . _onExit . event ;
50
+ private _messageProtocol : Promise < PersistentProtocol > | null = null ;
51
+
52
+ private clientProcess ?: ExtensionHostProcess ;
38
53
39
54
/** @TODO Document usage. */
40
55
public readonly _isExtensionDevHost : boolean ;
@@ -81,6 +96,16 @@ export class ExtensionHostConnection extends AbstractConnection {
81
96
return port || 0 ;
82
97
}
83
98
99
+ /** @TODO implement. */
100
+ public getInspectPort ( ) : number | undefined {
101
+ return undefined ;
102
+ }
103
+
104
+ /** @TODO implement. */
105
+ public enableInspectPort ( ) : Promise < boolean > {
106
+ return Promise . resolve ( false ) ;
107
+ }
108
+
84
109
protected doDispose ( ) : void {
85
110
this . protocol . dispose ( ) ;
86
111
@@ -161,12 +186,27 @@ export class ExtensionHostConnection extends AbstractConnection {
161
186
} ;
162
187
}
163
188
189
+ private _onExtHostProcessExit ( code : number , signal : string ) : void {
190
+ this . dispose ( ) ;
191
+
192
+ if ( code !== 0 && signal !== 'SIGTERM' ) {
193
+ this . logService . error ( `${ this . logPrefix } Extension host exited with code: ${ code } and signal: ${ signal } .` ) ;
194
+ }
195
+
196
+ if ( this . _terminating ) {
197
+ // Expected termination path (we asked the process to terminate)
198
+ return ;
199
+ }
200
+
201
+ this . _onExit . fire ( [ code , signal ] ) ;
202
+ }
203
+
164
204
/**
165
205
* Creates an extension host child process.
166
206
* @remark this is very similar to `LocalProcessExtensionHost`
167
207
*/
168
- public spawn ( ) : Promise < void > {
169
- return new Promise ( async ( resolve , reject ) => {
208
+ public start ( ) : Promise < IMessagePassingProtocol > {
209
+ this . _messageProtocol = new Promise ( async ( resolve , reject ) => {
170
210
this . logService . debug ( this . logPrefix , '(Spawn 1/7)' , 'Sending client initial debug message.' ) ;
171
211
this . protocol . sendMessage ( this . debugMessage ) ;
172
212
@@ -178,14 +218,10 @@ export class ExtensionHostConnection extends AbstractConnection {
178
218
const clientOptions = await this . generateClientOptions ( ) ;
179
219
180
220
this . logService . debug ( this . logPrefix , '(Spawn 4/7)' , 'Starting extension host child process...' ) ;
181
- this . clientProcess = new ExtensionHost ( FileAccess . asFileUri ( 'bootstrap-fork' , require ) . fsPath , clientOptions ) ;
182
-
183
- this . clientProcess . onDidProcessExit ( ( { code, signal } ) => {
184
- this . dispose ( ) ;
221
+ this . clientProcess = new ExtensionHostProcess ( FileAccess . asFileUri ( 'bootstrap-fork' , require ) . fsPath , clientOptions ) ;
185
222
186
- if ( code !== 0 && signal !== 'SIGTERM' ) {
187
- this . logService . error ( `${ this . logPrefix } Extension host exited with code: ${ code } and signal: ${ signal } .` ) ;
188
- }
223
+ this . clientProcess . onDidProcessExit ( ( [ code , signal ] ) => {
224
+ this . _onExtHostProcessExit ( code , signal ) ;
189
225
} ) ;
190
226
191
227
this . clientProcess . onReady ( ( ) => {
@@ -195,11 +231,53 @@ export class ExtensionHostConnection extends AbstractConnection {
195
231
196
232
if ( messageSent ) {
197
233
this . logService . debug ( this . logPrefix , '(Spawn 7/7)' , 'Child process received init message!' ) ;
198
- return resolve ( ) ;
234
+ return resolve ( this . protocol ) ;
199
235
}
200
236
201
237
reject ( new Error ( 'Child process did not receive init message. Is their a backlog?' ) ) ;
202
238
} ) ;
203
239
} ) ;
240
+
241
+ return this . _messageProtocol ;
242
+ }
243
+
244
+ public terminate ( ) : void {
245
+ if ( this . _terminating ) {
246
+ return ;
247
+ }
248
+ this . _terminating = true ;
249
+
250
+ this . dispose ( ) ;
251
+
252
+ if ( ! this . _messageProtocol ) {
253
+ // .start() was not called
254
+ return ;
255
+ }
256
+
257
+ this . _messageProtocol . then ( ( protocol ) => {
258
+
259
+ // Send the extension host a request to terminate itself
260
+ // (graceful termination)
261
+ protocol . send ( createMessageOfType ( MessageType . Terminate ) ) ;
262
+
263
+ protocol . getSocket ( ) . dispose ( ) ;
264
+
265
+ protocol . dispose ( ) ;
266
+
267
+ // Give the extension host 10s, after which we will
268
+ // try to kill the process and release any resources
269
+ setTimeout ( ( ) => this . _cleanResources ( ) , 10 * 1000 ) ;
270
+
271
+ } , ( err ) => {
272
+ // Establishing a protocol with the extension host failed, so
273
+ // try to kill the process and release any resources.
274
+ this . _cleanResources ( ) ;
275
+ } ) ;
276
+ }
277
+
278
+ private _cleanResources ( ) : void {
279
+ if ( this . clientProcess ) {
280
+ this . clientProcess . dispose ( ) ;
281
+ }
204
282
}
205
283
}
0 commit comments