@@ -9,6 +9,8 @@ import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/commo
9
9
import { findFreePort } from 'vs/base/node/ports' ;
10
10
import { ExtensionHost , IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp' ;
11
11
import { VSBuffer } from 'vs/base/common/buffer' ;
12
+ import { SendHandle } from 'child_process' ;
13
+ import { ConsoleLogger } from 'vs/platform/log/common/log' ;
12
14
13
15
export interface ForkEnvironmentVariables {
14
16
VSCODE_AMD_ENTRYPOINT : string ;
@@ -24,26 +26,28 @@ export interface ForkEnvironmentVariables {
24
26
VSCODE_CODE_CACHE_PATH ?: string ;
25
27
}
26
28
29
+ /**
30
+ * This complements the client-side `PersistantConnection` in `RemoteExtensionHost`.
31
+ */
27
32
export class ExtensionHostConnection extends AbstractConnection {
28
33
private clientProcess ?: ExtensionHost ;
29
34
30
35
/** @TODO Document usage. */
31
36
public readonly _isExtensionDevHost : boolean ;
32
37
public readonly _isExtensionDevDebug : boolean ;
33
- public readonly _isExtensionDevDebugBrk : boolean ;
34
38
public readonly _isExtensionDevTestFromCli : boolean ;
35
39
36
40
public constructor (
37
41
protocol : ServerProtocol ,
42
+ logService : ConsoleLogger ,
38
43
private readonly startParams : IRemoteExtensionHostStartParams ,
39
44
private readonly _environmentService : INativeEnvironmentService ,
40
45
) {
41
- super ( protocol , 'exthost ') ;
46
+ super ( protocol , logService , 'ExtensionHost ') ;
42
47
43
48
const devOpts = parseExtensionDevOptions ( this . _environmentService ) ;
44
49
this . _isExtensionDevHost = devOpts . isExtensionDevHost ;
45
50
this . _isExtensionDevDebug = devOpts . isExtensionDevDebug ;
46
- this . _isExtensionDevDebugBrk = devOpts . isExtensionDevDebugBrk ;
47
51
this . _isExtensionDevTestFromCli = devOpts . isExtensionDevTestFromCli ;
48
52
}
49
53
@@ -72,11 +76,6 @@ export class ExtensionHostConnection extends AbstractConnection {
72
76
if ( port !== expected ) {
73
77
console . warn ( `%c[Extension Host] %cProvided debugging port ${ expected } is not free, using ${ port } instead.` , 'color: blue' , 'color:' ) ;
74
78
}
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
- }
80
79
}
81
80
}
82
81
@@ -90,41 +89,44 @@ export class ExtensionHostConnection extends AbstractConnection {
90
89
}
91
90
92
91
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
+
93
97
this . protocol . beginAcceptReconnection ( reconnectionProtocol . getSocket ( ) , reconnectionProtocol . readEntireBuffer ( ) ) ;
94
98
this . protocol . endAcceptReconnection ( ) ;
95
99
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 ( ) ;
97
102
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 ) ;
104
104
105
+ if ( ! messageSent ) {
106
+ new Error ( 'Child process did not receive init message. Is their a backlog?' ) ;
107
+ }
105
108
106
- this . sendInitMessage ( initialDataChunk , this . protocol . inflateBytes ) ;
109
+ this . logService . info ( this . logPrefix , '(Reconnect 4/4)' , 'Child process received init message!' ) ;
107
110
}
108
111
109
112
/**
110
- * Sends IPC socket to child process.
113
+ * Sends IPC socket to client process.
114
+ * @remark This is the complement of `extensionHostProcessSetup.ts#_createExtHostProtocol`
111
115
*/
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` ) ;
119
119
}
120
120
121
- this . clientProcess ?. sendIPCMessage ( {
121
+ this . logService . info ( this . logPrefix , 'Sending init message to client process...' ) ;
122
+
123
+ return this . clientProcess . sendIPCMessage ( {
122
124
type : 'VSCODE_EXTHOST_IPC_SOCKET' ,
123
125
initialDataChunk : Buffer . from ( initialDataChunk . buffer ) . toString ( 'base64' ) ,
124
126
skipWebSocketFrames : this . protocol . skipWebSocketFrames ,
125
127
permessageDeflate : this . protocol . getSocket ( ) . permessageDeflate ,
126
128
inflateBytes : inflateBytes ? Buffer . from ( inflateBytes . buffer ) . toString ( 'base64' ) : '' ,
127
- } , foo ) ;
129
+ } , sendHandle ) ;
128
130
}
129
131
130
132
private async generateClientOptions ( ) : Promise < IIPCOptions > {
@@ -135,8 +137,8 @@ export class ExtensionHostConnection extends AbstractConnection {
135
137
return {
136
138
serverName : 'Server Extension Host' ,
137
139
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 ,
140
142
args : [ '--type=extensionHost' , '--skipWorkspaceStorageLock' ] ,
141
143
env : < ForkEnvironmentVariables > {
142
144
VSCODE_AMD_ENTRYPOINT : 'vs/workbench/services/extensions/node/extensionHostProcess' ,
@@ -156,39 +158,45 @@ export class ExtensionHostConnection extends AbstractConnection {
156
158
} ;
157
159
}
158
160
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 ) ;
161
169
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.' ) ;
163
171
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 ( ) ;
168
173
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 ( ) ;
171
176
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 ) ;
174
179
175
- this . clientProcess . onDidProcessExit ( ( { code, signal } ) => {
176
- this . dispose ( ) ;
180
+ this . clientProcess . onDidProcessExit ( ( { code, signal } ) => {
181
+ this . dispose ( ) ;
177
182
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
+ } ) ;
182
187
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 ) ;
187
192
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
+ }
190
197
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
+ } ) ;
193
201
}
194
202
}
0 commit comments