@@ -9,6 +9,8 @@ import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/commo
99import { findFreePort } from 'vs/base/node/ports' ;
1010import { ExtensionHost , IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp' ;
1111import { VSBuffer } from 'vs/base/common/buffer' ;
12+ import { SendHandle } from 'child_process' ;
13+ import { ConsoleLogger } from 'vs/platform/log/common/log' ;
1214
1315export interface ForkEnvironmentVariables {
1416 VSCODE_AMD_ENTRYPOINT : string ;
@@ -24,26 +26,28 @@ export interface ForkEnvironmentVariables {
2426 VSCODE_CODE_CACHE_PATH ?: string ;
2527}
2628
29+ /**
30+ * This complements the client-side `PersistantConnection` in `RemoteExtensionHost`.
31+ */
2732export class ExtensionHostConnection extends AbstractConnection {
2833 private clientProcess ?: ExtensionHost ;
2934
3035 /** @TODO Document usage. */
3136 public readonly _isExtensionDevHost : boolean ;
3237 public readonly _isExtensionDevDebug : boolean ;
33- public readonly _isExtensionDevDebugBrk : boolean ;
3438 public readonly _isExtensionDevTestFromCli : boolean ;
3539
3640 public constructor (
3741 protocol : ServerProtocol ,
42+ logService : ConsoleLogger ,
3843 private readonly startParams : IRemoteExtensionHostStartParams ,
3944 private readonly _environmentService : INativeEnvironmentService ,
4045 ) {
41- super ( protocol , 'exthost ') ;
46+ super ( protocol , logService , 'ExtensionHost ') ;
4247
4348 const devOpts = parseExtensionDevOptions ( this . _environmentService ) ;
4449 this . _isExtensionDevHost = devOpts . isExtensionDevHost ;
4550 this . _isExtensionDevDebug = devOpts . isExtensionDevDebug ;
46- this . _isExtensionDevDebugBrk = devOpts . isExtensionDevDebugBrk ;
4751 this . _isExtensionDevTestFromCli = devOpts . isExtensionDevTestFromCli ;
4852 }
4953
@@ -72,11 +76,6 @@ export class ExtensionHostConnection extends AbstractConnection {
7276 if ( port !== expected ) {
7377 console . warn ( `%c[Extension Host] %cProvided debugging port ${ expected } is not free, using ${ port } instead.` , 'color: blue' , 'color:' ) ;
7478 }
75- if ( this . _isExtensionDevDebugBrk ) {
76- console . warn ( `%c[Extension Host] %cSTOPPED on first line for debugging on port ${ port } ` , 'color: blue' , 'color:' ) ;
77- } else {
78- console . info ( `%c[Extension Host] %cdebugger listening on port ${ port } ` , 'color: blue' , 'color:' ) ;
79- }
8079 }
8180 }
8281
@@ -90,41 +89,44 @@ export class ExtensionHostConnection extends AbstractConnection {
9089 }
9190
9291 protected doReconnect ( reconnectionProtocol : ServerProtocol ) : void {
92+ this . logService . info ( this . logPrefix , '(Reconnect 1/4)' , 'Sending new protocol debug message...' ) ;
93+ reconnectionProtocol . sendMessage ( this . debugMessage ) ;
94+
95+ this . logService . info ( this . logPrefix , '(Reconnect 2/4)' , 'Swapping socket references...' ) ;
96+
9397 this . protocol . beginAcceptReconnection ( reconnectionProtocol . getSocket ( ) , reconnectionProtocol . readEntireBuffer ( ) ) ;
9498 this . protocol . endAcceptReconnection ( ) ;
9599
96- this . protocol . sendMessage ( this . debugMessage ) ;
100+ this . logService . info ( this . logPrefix , '(Reconnect 3/4)' , 'Pausing socket until we have a chance to forward its data.' ) ;
101+ const { initialDataChunk, sendHandle } = this . protocol . suspend ( ) ;
97102
98- const { initialDataChunk } = this . protocol ;
99-
100- // Pause reading on the socket until we have a chance to forward its data.
101- this . protocol . dispose ( ) ;
102- this . protocol . getSendHandle ( ) . pause ( ) ;
103- this . protocol . getSocket ( ) . drain ( ) ;
103+ const messageSent = this . sendInitMessage ( initialDataChunk , this . protocol . inflateBytes , sendHandle ) ;
104104
105+ if ( ! messageSent ) {
106+ new Error ( 'Child process did not receive init message. Is their a backlog?' ) ;
107+ }
105108
106- this . sendInitMessage ( initialDataChunk , this . protocol . inflateBytes ) ;
109+ this . logService . info ( this . logPrefix , '(Reconnect 4/4)' , 'Child process received init message!' ) ;
107110 }
108111
109112 /**
110- * Sends IPC socket to child process.
113+ * Sends IPC socket to client process.
114+ * @remark This is the complement of `extensionHostProcessSetup.ts#_createExtHostProtocol`
111115 */
112- private sendInitMessage ( initialDataChunk : VSBuffer , inflateBytes ?: VSBuffer ) : void {
113- this . logService . info ( this . logPrefix , 'Sending socket' ) ;
114-
115- const foo = this . protocol . getSendHandle ( ) ;
116-
117- if ( typeof foo === 'undefined' ) {
118- throw new Error ( 'SEND HANDLE IS UNDEFINED.' ) ;
116+ private sendInitMessage ( initialDataChunk : VSBuffer , inflateBytes : VSBuffer , sendHandle : SendHandle ) : boolean {
117+ if ( ! this . clientProcess ) {
118+ throw new Error ( `${ this . logPrefix } Client process is not set` ) ;
119119 }
120120
121- this . clientProcess ?. sendIPCMessage ( {
121+ this . logService . info ( this . logPrefix , 'Sending init message to client process...' ) ;
122+
123+ return this . clientProcess . sendIPCMessage ( {
122124 type : 'VSCODE_EXTHOST_IPC_SOCKET' ,
123125 initialDataChunk : Buffer . from ( initialDataChunk . buffer ) . toString ( 'base64' ) ,
124126 skipWebSocketFrames : this . protocol . skipWebSocketFrames ,
125127 permessageDeflate : this . protocol . getSocket ( ) . permessageDeflate ,
126128 inflateBytes : inflateBytes ? Buffer . from ( inflateBytes . buffer ) . toString ( 'base64' ) : '' ,
127- } , foo ) ;
129+ } , sendHandle ) ;
128130 }
129131
130132 private async generateClientOptions ( ) : Promise < IIPCOptions > {
@@ -135,8 +137,8 @@ export class ExtensionHostConnection extends AbstractConnection {
135137 return {
136138 serverName : 'Server Extension Host' ,
137139 freshExecArgv : true ,
138- debugBrk : this . _isExtensionDevDebugBrk ? portNumber : undefined ,
139- debug : this . _isExtensionDevDebugBrk ? undefined : portNumber ,
140+ debugBrk : this . startParams . break ? portNumber : undefined ,
141+ debug : this . startParams . break ? undefined : portNumber ,
140142 args : [ '--type=extensionHost' , '--skipWorkspaceStorageLock' ] ,
141143 env : < ForkEnvironmentVariables > {
142144 VSCODE_AMD_ENTRYPOINT : 'vs/workbench/services/extensions/node/extensionHostProcess' ,
@@ -156,39 +158,45 @@ export class ExtensionHostConnection extends AbstractConnection {
156158 } ;
157159 }
158160
159- public async spawn ( ) {
160- this . protocol . sendMessage ( this . debugMessage ) ;
161+ /**
162+ * Creates an extension host child process.
163+ * @remark this is very similar to `LocalProcessExtensionHost`
164+ */
165+ public spawn ( ) : Promise < void > {
166+ return new Promise ( async ( resolve , reject ) => {
167+ this . logService . info ( this . logPrefix , '(Spawn 1/7)' , 'Sending client initial debug message.' ) ;
168+ this . protocol . sendMessage ( this . debugMessage ) ;
161169
162- const { initialDataChunk } = this . protocol ;
170+ this . logService . info ( this . logPrefix , '(Spawn 2/7)' , 'Pausing socket until we have a chance to forward its data.' ) ;
163171
164- // Pause reading on the socket until we have a chance to forward its data.
165- this . protocol . dispose ( ) ;
166- this . protocol . getSendHandle ( ) . pause ( ) ;
167- this . protocol . getSocket ( ) . drain ( ) ;
172+ const { initialDataChunk, sendHandle } = this . protocol . suspend ( ) ;
168173
169- this . logService . debug ( this . logPrefix , 'Spawning extension host ...' ) ;
170- const clientOptions = await this . generateClientOptions ( ) ;
174+ this . logService . info ( this . logPrefix , '(Spawn 3/7)' , 'Generating IPC client options ...') ;
175+ const clientOptions = await this . generateClientOptions ( ) ;
171176
172- // Run Extension Host as fork of current process
173- this . clientProcess = new ExtensionHost ( FileAccess . asFileUri ( 'bootstrap-fork' , require ) . fsPath , clientOptions ) ;
177+ this . logService . info ( this . logPrefix , '(Spawn 4/7)' , 'Starting extension host child process...' ) ;
178+ this . clientProcess = new ExtensionHost ( FileAccess . asFileUri ( 'bootstrap-fork' , require ) . fsPath , clientOptions ) ;
174179
175- this . clientProcess . onDidProcessExit ( ( { code, signal } ) => {
176- this . dispose ( ) ;
180+ this . clientProcess . onDidProcessExit ( ( { code, signal } ) => {
181+ this . dispose ( ) ;
177182
178- if ( code !== 0 && signal !== 'SIGTERM' ) {
179- this . logService . error ( `[ ${ this . protocol . reconnectionToken } ] Extension host exited with code: ${ code } and signal: ${ signal } .` ) ;
180- }
181- } ) ;
183+ if ( code !== 0 && signal !== 'SIGTERM' ) {
184+ this . logService . error ( `${ this . logPrefix } Extension host exited with code: ${ code } and signal: ${ signal } .` ) ;
185+ }
186+ } ) ;
182187
183- this . clientProcess . onReady ( ( ) => {
184- this . logService . info ( this . logPrefix , 'Handshake completed ' ) ;
185- this . sendInitMessage ( initialDataChunk , this . protocol . inflateBytes ) ;
186- } ) ;
188+ this . clientProcess . onReady ( ( ) => {
189+ this . logService . info ( this . logPrefix , '(Spawn 5/7)' , 'Extension host is ready! ') ;
190+ this . logService . info ( this . logPrefix , '(Spawn 6/7)' , 'Sending init message to child process...' ) ;
191+ const messageSent = this . sendInitMessage ( initialDataChunk , this . protocol . inflateBytes , sendHandle ) ;
187192
188- this . logService . info ( this . logPrefix , 'Waiting for handshake...' ) ;
189- }
193+ if ( messageSent ) {
194+ this . logService . info ( this . logPrefix , '(Spawn 7/7)' , 'Child process received init message!' ) ;
195+ return resolve ( ) ;
196+ }
190197
191- private get logPrefix ( ) {
192- return `[${ this . protocol . reconnectionToken } ]` ;
198+ reject ( new Error ( 'Child process did not receive init message. Is their a backlog?' ) ) ;
199+ } ) ;
200+ } ) ;
193201 }
194202}
0 commit comments