@@ -132,7 +132,13 @@ class ExtensionHostProcessManager {
132132 PIPE_LOGGING : 'true' ,
133133 VERBOSE_LOGGING : true ,
134134 VSCODE_WINDOW_ID : String ( this . windowService . getWindowId ( ) )
135- } )
135+ } ) ,
136+ // We only detach the extension host on windows. Linux and Mac orphan by default
137+ // and detach under Linux and Mac create another process group.
138+ // We detach because we have noticed that when the renderer exits, its child processes
139+ // (i.e. extension host) is taken down in a brutal fashion by the OS
140+ detached : ! ! isWindows ,
141+ onExtensionHostMessage
136142 } ;
137143
138144 // Help in case we fail to start it
@@ -145,20 +151,16 @@ class ExtensionHostProcessManager {
145151 }
146152
147153 // Initialize extension host process with hand shakes
148- this . initializeExtensionHostProcess = new TPromise < ChildProcess > ( ( c , e ) => {
154+ this . initializeExtensionHostProcess = this . doInitializeExtensionHostProcess ( opts ) ;
155+ }
149156
157+ private doInitializeExtensionHostProcess ( opts : any ) : TPromise < ChildProcess > {
158+ return new TPromise < ChildProcess > ( ( c , e ) => {
150159 // Resolve additional execution args (e.g. debug)
151- return this . resolveDebugPort ( this . environmentService . debugExtensionHost . port , port => {
160+ this . resolveDebugPort ( this . environmentService . debugExtensionHost . port ) . then ( port => {
152161 if ( port ) {
153162 opts . execArgv = [ '--nolazy' , ( this . isExtensionDevelopmentDebugging ? '--debug-brk=' : '--debug=' ) + port ] ;
154163 }
155- // We only detach the extension host on windows. Linux and Mac orphan by default
156- // and detach under Linux and Mac create another process group.
157- if ( isWindows ) {
158- // We detach because we have noticed that when the renderer exits, its child processes
159- // (i.e. extension host) is taken down in a brutal fashion by the OS
160- opts . detached = true ;
161- }
162164
163165 // Run Extension Host as fork of current process
164166 this . extensionHostProcessHandle = fork ( URI . parse ( require . toUrl ( 'bootstrap' ) ) . fsPath , [ '--type=extensionHost' ] , opts ) ;
@@ -172,159 +174,165 @@ class ExtensionHostProcessManager {
172174 }
173175
174176 // Messages from Extension host
175- this . extensionHostProcessHandle . on ( 'message' , ( msg ) => {
176-
177- // 1) Host is ready to receive messages, initialize it
178- if ( msg === 'ready' ) {
179- if ( this . initializeTimer ) {
180- window . clearTimeout ( this . initializeTimer ) ;
181- }
182-
183- let initPayload = stringify ( {
184- parentPid : process . pid ,
185- environment : {
186- appSettingsHome : this . environmentService . appSettingsHome ,
187- disableExtensions : this . environmentService . disableExtensions ,
188- userExtensionsHome : this . environmentService . extensionsPath ,
189- extensionDevelopmentPath : this . environmentService . extensionDevelopmentPath ,
190- extensionTestsPath : this . environmentService . extensionTestsPath
191- } ,
192- contextService : {
193- workspace : this . contextService . getWorkspace ( )
194- }
195- } ) ;
196-
197- this . extensionHostProcessHandle . send ( initPayload ) ;
198- }
199-
200- // 2) Host is initialized
201- else if ( msg === 'initialized' ) {
202- this . unsentMessages . forEach ( m => this . postMessage ( m ) ) ;
203- this . unsentMessages = [ ] ;
204-
205- this . extensionHostProcessReady = true ;
177+ this . extensionHostProcessHandle . on ( 'message' , msg => {
178+ if ( this . onMessaage ( msg , opts . onExtensionHostMessage ) ) {
206179 c ( this . extensionHostProcessHandle ) ;
207180 }
208-
209- // Support logging from extension host
210- else if ( msg && ( < ILogEntry > msg ) . type === '__$console' ) {
211- let logEntry : ILogEntry = msg ;
212-
213- let args = [ ] ;
214- try {
215- let parsed = JSON . parse ( logEntry . arguments ) ;
216- args . push ( ...Object . getOwnPropertyNames ( parsed ) . map ( o => parsed [ o ] ) ) ;
217- } catch ( error ) {
218- args . push ( logEntry . arguments ) ;
219- }
220-
221- // If the first argument is a string, check for % which indicates that the message
222- // uses substitution for variables. In this case, we cannot just inject our colored
223- // [Extension Host] to the front because it breaks substitution.
224- let consoleArgs = [ ] ;
225- if ( typeof args [ 0 ] === 'string' && args [ 0 ] . indexOf ( '%' ) >= 0 ) {
226- consoleArgs = [ `%c[Extension Host]%c ${ args [ 0 ] } ` , 'color: blue' , 'color: black' , ...args . slice ( 1 ) ] ;
227- } else {
228- consoleArgs = [ '%c[Extension Host]' , 'color: blue' , ...args ] ;
229- }
230-
231- // Send to local console unless we run tests from cli
232- if ( ! this . isExtensionDevelopmentTestFromCli ) {
233- console [ logEntry . severity ] . apply ( console , consoleArgs ) ;
234- }
235-
236- // Log on main side if running tests from cli
237- if ( this . isExtensionDevelopmentTestFromCli ) {
238- ipc . send ( 'vscode:log' , logEntry ) ;
239- }
240-
241- // Broadcast to other windows if we are in development mode
242- else if ( ! this . environmentService . isBuilt || this . isExtensionDevelopmentHost ) {
243- this . windowService . broadcast ( {
244- channel : EXTENSION_LOG_BROADCAST_CHANNEL ,
245- payload : logEntry
246- } , this . environmentService . extensionDevelopmentPath /* target */ ) ;
247- }
248- }
249-
250- // Any other message goes to the callback
251- else {
252- onExtensionHostMessage ( msg ) ;
253- }
254181 } ) ;
255182
256183 // Lifecycle
257184 let onExit = ( ) => this . terminate ( ) ;
258185 process . once ( 'exit' , onExit ) ;
259-
260- this . extensionHostProcessHandle . on ( 'error' , ( err ) => {
261- let errorMessage = toErrorMessage ( err ) ;
262- if ( errorMessage === this . lastExtensionHostError ) {
263- return ; // prevent error spam
264- }
265-
266- this . lastExtensionHostError = errorMessage ;
267-
268- this . messageService . show ( Severity . Error , nls . localize ( 'extensionHostProcess.error' , "Error from the extension host: {0}" , errorMessage ) ) ;
269- } ) ;
270-
271- this . extensionHostProcessHandle . on ( 'exit' , ( code : any , signal : any ) => {
272- process . removeListener ( 'exit' , onExit ) ;
273-
274- if ( ! this . terminating ) {
275-
276- // Unexpected termination
277- if ( ! this . isExtensionDevelopmentHost ) {
278- this . messageService . show ( Severity . Error , {
279- message : nls . localize ( 'extensionHostProcess.crash' , "Extension host terminated unexpectedly. Please reload the window to recover." ) ,
280- actions : [ this . instantiationService . createInstance ( ReloadWindowAction , ReloadWindowAction . ID , ReloadWindowAction . LABEL ) ]
281- } ) ;
282- console . error ( 'Extension host terminated unexpectedly. Code: ' , code , ' Signal: ' , signal ) ;
283- }
284-
285- // Expected development extension termination: When the extension host goes down we also shutdown the window
286- else if ( ! this . isExtensionDevelopmentTestFromCli ) {
287- this . windowService . getWindow ( ) . close ( ) ;
288- }
289-
290- // When CLI testing make sure to exit with proper exit code
291- else {
292- ipc . send ( 'vscode:exit' , code ) ;
293- }
294- }
295- } ) ;
186+ this . extensionHostProcessHandle . on ( 'error' , ( err ) => this . onError ( err ) ) ;
187+ this . extensionHostProcessHandle . on ( 'exit' , ( code : any , signal : any ) => this . onExit ( code , signal , onExit ) ) ;
296188 } ) ;
297189 } , ( ) => this . terminate ( ) ) ;
298190 }
299191
300- private resolveDebugPort ( extensionHostPort : number , clb : ( port : number ) => void ) : void {
301-
302- // Check for a free debugging port
303- if ( typeof extensionHostPort === 'number' ) {
304- return findFreePort ( extensionHostPort , 10 /* try 10 ports */ , 5000 /* try up to 5 seconds */ , ( port ) => {
192+ private resolveDebugPort ( extensionHostPort : number ) : TPromise < number > {
193+ if ( typeof extensionHostPort !== 'number' ) {
194+ return TPromise . wrap ( void 0 ) ;
195+ }
196+ return new TPromise < number > ( ( c , e ) => {
197+ findFreePort ( extensionHostPort , 10 /* try 10 ports */ , 5000 /* try up to 5 seconds */ , ( port ) => {
305198 if ( ! port ) {
306199 console . warn ( '%c[Extension Host] %cCould not find a free port for debugging' , 'color: blue' , 'color: black' ) ;
307-
308- return clb ( void 0 ) ;
200+ c ( void 0 ) ;
309201 }
310-
311202 if ( port !== extensionHostPort ) {
312203 console . warn ( '%c[Extension Host] %cProvided debugging port ' + extensionHostPort + ' is not free, using ' + port + ' instead.' , 'color: blue' , 'color: black' ) ;
313204 }
314-
315205 if ( this . isExtensionDevelopmentDebugging ) {
316206 console . warn ( '%c[Extension Host] %cSTOPPED on first line for debugging on port ' + port , 'color: blue' , 'color: black' ) ;
317207 } else {
318208 console . info ( '%c[Extension Host] %cdebugger listening on port ' + port , 'color: blue' , 'color: black' ) ;
319209 }
320-
321- return clb ( port ) ;
210+ return c ( port ) ;
322211 } ) ;
212+ } ) ;
213+ }
214+
215+ // @return `true` if ready
216+ private onMessaage ( msg : any , onExtensionHostMessage : ( msg : any ) => void ) : boolean {
217+ // 1) Host is ready to receive messages, initialize it
218+ if ( msg === 'ready' ) {
219+ this . initializeExtensionHost ( ) ;
220+ return false ;
221+ }
222+
223+ // 2) Host is initialized
224+ if ( msg === 'initialized' ) {
225+ this . unsentMessages . forEach ( m => this . postMessage ( m ) ) ;
226+ this . unsentMessages = [ ] ;
227+ this . extensionHostProcessReady = true ;
228+ return true ;
229+ }
230+
231+ // Support logging from extension host
232+ if ( msg && ( < ILogEntry > msg ) . type === '__$console' ) {
233+ this . logExtensionHostMessage ( < ILogEntry > msg ) ;
234+ return false ;
235+ }
236+
237+ // Any other message goes to the callback
238+ onExtensionHostMessage ( msg ) ;
239+ return false ;
240+ }
241+
242+ private initializeExtensionHost ( ) {
243+ if ( this . initializeTimer ) {
244+ window . clearTimeout ( this . initializeTimer ) ;
323245 }
324246
325- // Nothing to do here
326- else {
327- return clb ( void 0 ) ;
247+ let initPayload = stringify ( {
248+ parentPid : process . pid ,
249+ environment : {
250+ appSettingsHome : this . environmentService . appSettingsHome ,
251+ disableExtensions : this . environmentService . disableExtensions ,
252+ userExtensionsHome : this . environmentService . extensionsPath ,
253+ extensionDevelopmentPath : this . environmentService . extensionDevelopmentPath ,
254+ extensionTestsPath : this . environmentService . extensionTestsPath
255+ } ,
256+ contextService : {
257+ workspace : this . contextService . getWorkspace ( )
258+ }
259+ } ) ;
260+
261+ this . extensionHostProcessHandle . send ( initPayload ) ;
262+ }
263+
264+ private logExtensionHostMessage ( logEntry : ILogEntry ) {
265+ let args = [ ] ;
266+ try {
267+ let parsed = JSON . parse ( logEntry . arguments ) ;
268+ args . push ( ...Object . getOwnPropertyNames ( parsed ) . map ( o => parsed [ o ] ) ) ;
269+ } catch ( error ) {
270+ args . push ( logEntry . arguments ) ;
271+ }
272+
273+ // If the first argument is a string, check for % which indicates that the message
274+ // uses substitution for variables. In this case, we cannot just inject our colored
275+ // [Extension Host] to the front because it breaks substitution.
276+ let consoleArgs = [ ] ;
277+ if ( typeof args [ 0 ] === 'string' && args [ 0 ] . indexOf ( '%' ) >= 0 ) {
278+ consoleArgs = [ `%c[Extension Host]%c ${ args [ 0 ] } ` , 'color: blue' , 'color: black' , ...args . slice ( 1 ) ] ;
279+ } else {
280+ consoleArgs = [ '%c[Extension Host]' , 'color: blue' , ...args ] ;
281+ }
282+
283+ // Send to local console unless we run tests from cli
284+ if ( ! this . isExtensionDevelopmentTestFromCli ) {
285+ console [ logEntry . severity ] . apply ( console , consoleArgs ) ;
286+ }
287+
288+ // Log on main side if running tests from cli
289+ if ( this . isExtensionDevelopmentTestFromCli ) {
290+ ipc . send ( 'vscode:log' , logEntry ) ;
291+ }
292+
293+ // Broadcast to other windows if we are in development mode
294+ else if ( ! this . environmentService . isBuilt || this . isExtensionDevelopmentHost ) {
295+ this . windowService . broadcast ( {
296+ channel : EXTENSION_LOG_BROADCAST_CHANNEL ,
297+ payload : logEntry
298+ } , this . environmentService . extensionDevelopmentPath /* target */ ) ;
299+ }
300+ }
301+
302+ private onError ( err : any ) : void {
303+ let errorMessage = toErrorMessage ( err ) ;
304+ if ( errorMessage === this . lastExtensionHostError ) {
305+ return ; // prevent error spam
306+ }
307+
308+ this . lastExtensionHostError = errorMessage ;
309+
310+ this . messageService . show ( Severity . Error , nls . localize ( 'extensionHostProcess.error' , "Error from the extension host: {0}" , errorMessage ) ) ;
311+ }
312+
313+ private onExit ( code : any , signal : any , onProcessExit : any ) : void {
314+ process . removeListener ( 'exit' , onProcessExit ) ;
315+
316+ if ( ! this . terminating ) {
317+
318+ // Unexpected termination
319+ if ( ! this . isExtensionDevelopmentHost ) {
320+ this . messageService . show ( Severity . Error , {
321+ message : nls . localize ( 'extensionHostProcess.crash' , "Extension host terminated unexpectedly. Please reload the window to recover." ) ,
322+ actions : [ this . instantiationService . createInstance ( ReloadWindowAction , ReloadWindowAction . ID , ReloadWindowAction . LABEL ) ]
323+ } ) ;
324+ console . error ( 'Extension host terminated unexpectedly. Code: ' , code , ' Signal: ' , signal ) ;
325+ }
326+
327+ // Expected development extension termination: When the extension host goes down we also shutdown the window
328+ else if ( ! this . isExtensionDevelopmentTestFromCli ) {
329+ this . windowService . getWindow ( ) . close ( ) ;
330+ }
331+
332+ // When CLI testing make sure to exit with proper exit code
333+ else {
334+ ipc . send ( 'vscode:exit' , code ) ;
335+ }
328336 }
329337 }
330338
0 commit comments