@@ -39,13 +39,14 @@ export class JupyterSession implements IJupyterSession {
3939 private kernelSpec : IJupyterKernelSpec | undefined ;
4040 private sessionManager : SessionManager | undefined ;
4141 private session : Session . ISession | undefined ;
42- private restartSessionPromise : Promise < Session . ISession > | undefined ;
42+ private restartSessionPromise : Promise < Session . ISession | undefined > | undefined ;
4343 private contentsManager : ContentsManager | undefined ;
4444 private notebookFiles : Contents . IModel [ ] = [ ] ;
4545 private onRestartedEvent : EventEmitter < void > | undefined ;
4646 private statusHandler : Slot < Session . ISession , Kernel . Status > | undefined ;
4747 private connected : boolean = false ;
4848 private jupyterPasswordConnect : IJupyterPasswordConnect ;
49+ private oldSessions : Session . ISession [ ] = [ ] ;
4950
5051 constructor (
5152 connInfo : IConnection ,
@@ -87,44 +88,38 @@ export class JupyterSession implements IJupyterSession {
8788 return this . onRestartedEvent . event ;
8889 }
8990
90- public async waitForIdle ( timeout : number ) : Promise < void > {
91- if ( this . session && this . session . kernel ) {
92- // This function seems to cause CI builds to timeout randomly on
93- // different tests. Waiting for status to go idle doesn't seem to work and
94- // in the past, waiting on the ready promise doesn't work either. Check status with a maximum of 5 seconds
95- const startTime = Date . now ( ) ;
96- while ( this . session &&
97- this . session . kernel &&
98- this . session . kernel . status !== 'idle' &&
99- ( Date . now ( ) - startTime < timeout ) ) {
100- traceInfo ( `Waiting for idle: ${ this . session . kernel . status } ` ) ;
101- await sleep ( 100 ) ;
102- }
103-
104- // If we didn't make it out in ten seconds, indicate an error
105- if ( ! this . session || ! this . session . kernel || this . session . kernel . status !== 'idle' ) {
106- throw new JupyterWaitForIdleError ( localize . DataScience . jupyterLaunchTimedOut ( ) ) ;
107- }
108- }
91+ public waitForIdle ( timeout : number ) : Promise < void > {
92+ return this . waitForIdleOnSession ( this . session , timeout ) ;
10993 }
11094
11195 public async restart ( _timeout : number ) : Promise < void > {
11296 // Just kill the current session and switch to the other
11397 if ( this . restartSessionPromise && this . session && this . sessionManager && this . contentsManager ) {
98+ traceInfo ( `Restarting ${ this . session . kernel . id } ` ) ;
99+
114100 // Save old state for shutdown
115101 const oldSession = this . session ;
116102 const oldStatusHandler = this . statusHandler ;
117103
118- // Just switch to the other session.
104+ // Just switch to the other session. It should already be ready
119105 this . session = await this . restartSessionPromise ;
106+ if ( ! this . session ) {
107+ throw new Error ( localize . DataScience . sessionDisposed ( ) ) ;
108+ }
109+ traceInfo ( `Got new session ${ this . session . kernel . id } ` ) ;
120110
121111 // Rewire our status changed event.
122112 this . statusHandler = this . onStatusChanged . bind ( this . onStatusChanged ) ;
123113 this . session . statusChanged . connect ( this . statusHandler ) ;
124114
125115 // After switching, start another in case we restart again.
126- this . restartSessionPromise = this . createSession ( oldSession . serverSettings , this . contentsManager ) ;
127- this . shutdownSession ( oldSession , oldStatusHandler ) . ignoreErrors ( ) ;
116+ this . restartSessionPromise = this . createRestartSession ( oldSession . serverSettings , this . contentsManager ) ;
117+ traceInfo ( 'Started new restart session' ) ;
118+ if ( oldStatusHandler ) {
119+ oldSession . statusChanged . disconnect ( oldStatusHandler ) ;
120+ }
121+ // Don't shutdown old sessions yet. This seems to hang tests.
122+ this . oldSessions . push ( oldSession ) ;
128123 } else {
129124 throw new Error ( localize . DataScience . sessionDisposed ( ) ) ;
130125 }
@@ -157,7 +152,7 @@ export class JupyterSession implements IJupyterSession {
157152 this . session = await this . createSession ( serverSettings , this . contentsManager , cancelToken ) ;
158153
159154 // Start another session to handle restarts
160- this . restartSessionPromise = this . createSession ( serverSettings , this . contentsManager , cancelToken ) ;
155+ this . restartSessionPromise = this . createRestartSession ( serverSettings , this . contentsManager , cancelToken ) ;
161156
162157 // Listen for session status changes
163158 this . statusHandler = this . onStatusChanged . bind ( this . onStatusChanged ) ;
@@ -171,6 +166,54 @@ export class JupyterSession implements IJupyterSession {
171166 return this . connected ;
172167 }
173168
169+ private async waitForIdleOnSession ( session : Session . ISession | undefined , timeout : number ) : Promise < void > {
170+ if ( session && session . kernel ) {
171+ traceInfo ( `Waiting for idle on: ${ session . kernel . id } ${ session . kernel . status } ` ) ;
172+
173+ // This function seems to cause CI builds to timeout randomly on
174+ // different tests. Waiting for status to go idle doesn't seem to work and
175+ // in the past, waiting on the ready promise doesn't work either. Check status with a maximum of 5 seconds
176+ const startTime = Date . now ( ) ;
177+ while ( session &&
178+ session . kernel &&
179+ session . kernel . status !== 'idle' &&
180+ ( Date . now ( ) - startTime < timeout ) ) {
181+ await sleep ( 100 ) ;
182+ }
183+
184+ traceInfo ( `Finished waiting for idle on: ${ session . kernel . id } ${ session . kernel . status } ` ) ;
185+
186+ // If we didn't make it out in ten seconds, indicate an error
187+ if ( ! session || ! session . kernel || session . kernel . status !== 'idle' ) {
188+ throw new JupyterWaitForIdleError ( localize . DataScience . jupyterLaunchTimedOut ( ) ) ;
189+ }
190+ }
191+ }
192+
193+ private async createRestartSession ( serverSettings : ServerConnection . ISettings , contentsManager : ContentsManager , cancelToken ?: CancellationToken ) : Promise < Session . ISession > {
194+ let result : Session . ISession | undefined ;
195+ let tryCount = 0 ;
196+ // tslint:disable-next-line: no-any
197+ let exception : any ;
198+ while ( tryCount < 3 ) {
199+ try {
200+ result = await this . createSession ( serverSettings , contentsManager , cancelToken ) ;
201+ await this . waitForIdleOnSession ( result , 30000 ) ;
202+ return result ;
203+ } catch ( exc ) {
204+ traceInfo ( `Error waiting for restart session: ${ exc } ` ) ;
205+ tryCount += 1 ;
206+ if ( result ) {
207+ // Cleanup later.
208+ this . oldSessions . push ( result ) ;
209+ }
210+ result = undefined ;
211+ exception = exc ;
212+ }
213+ }
214+ throw exception ;
215+ }
216+
174217 private async createSession ( serverSettings : ServerConnection . ISettings , contentsManager : ContentsManager , cancelToken ?: CancellationToken ) : Promise < Session . ISession > {
175218
176219 // Create a temporary notebook for this session.
@@ -277,7 +320,9 @@ export class JupyterSession implements IJupyterSession {
277320 }
278321
279322 private async shutdownSession ( session : Session . ISession | undefined , statusHandler : Slot < Session . ISession , Kernel . Status > | undefined ) : Promise < void > {
280- if ( session ) {
323+ if ( session && session . kernel ) {
324+ const kernelId = session . kernel . id ;
325+ traceInfo ( `shutdownSession ${ kernelId } - start` ) ;
281326 try {
282327 if ( statusHandler ) {
283328 session . statusChanged . disconnect ( statusHandler ) ;
@@ -288,23 +333,22 @@ export class JupyterSession implements IJupyterSession {
288333 // https://github.com/jupyterlab/jupyterlab/issues/4252
289334 // tslint:disable:no-any
290335 if ( isTestExecution ( ) ) {
291- if ( session && session . kernel ) {
292- const defaultKernel = session . kernel as any ;
293- if ( defaultKernel && defaultKernel . _futures ) {
294- const futures = defaultKernel . _futures as Map < any , any > ;
295- if ( futures ) {
296- futures . forEach ( f => {
297- if ( f . _status !== undefined ) {
298- f . _status |= 4 ;
299- }
300- } ) ;
301- }
336+ const defaultKernel = session . kernel as any ;
337+ if ( defaultKernel && defaultKernel . _futures ) {
338+ const futures = defaultKernel . _futures as Map < any , any > ;
339+ if ( futures ) {
340+ futures . forEach ( f => {
341+ if ( f . _status !== undefined ) {
342+ f . _status |= 4 ;
343+ }
344+ } ) ;
302345 }
303346 }
347+ await waitForPromise ( session . shutdown ( ) , 1000 ) ;
348+ } else {
349+ // Shutdown may fail if the process has been killed
350+ await waitForPromise ( session . shutdown ( ) , 1000 ) ;
304351 }
305-
306- // Shutdown may fail if the process has been killed
307- await waitForPromise ( session . shutdown ( ) , 1000 ) ;
308352 } catch {
309353 noop ( ) ;
310354 }
@@ -315,22 +359,30 @@ export class JupyterSession implements IJupyterSession {
315359 // Ignore, just trace.
316360 traceWarning ( e ) ;
317361 }
362+ traceInfo ( `shutdownSession ${ kernelId } - shutdown complete` ) ;
318363 }
319364 }
320365
321366 //tslint:disable:cyclomatic-complexity
322367 private async shutdownSessionAndConnection ( ) : Promise < void > {
323368 if ( this . contentsManager ) {
369+ traceInfo ( 'ShutdownSessionAndConnection - dispose contents manager' ) ;
324370 this . contentsManager . dispose ( ) ;
325371 this . contentsManager = undefined ;
326372 }
327373 if ( this . session || this . sessionManager ) {
328374 try {
375+ traceInfo ( 'ShutdownSessionAndConnection - old sessions' ) ;
376+ await Promise . all ( this . oldSessions . map ( s => this . shutdownSession ( s , undefined ) ) ) ;
377+ traceInfo ( 'ShutdownSessionAndConnection - current session' ) ;
329378 await this . shutdownSession ( this . session , this . statusHandler ) ;
379+ traceInfo ( 'ShutdownSessionAndConnection - get restart session' ) ;
330380 const restartSession = await this . restartSessionPromise ;
381+ traceInfo ( 'ShutdownSessionAndConnection - shutdown restart session' ) ;
331382 await this . shutdownSession ( restartSession , undefined ) ;
332383
333384 if ( this . sessionManager && ! this . sessionManager . isDisposed ) {
385+ traceInfo ( 'ShutdownSessionAndConnection - dispose session manager' ) ;
334386 this . sessionManager . dispose ( ) ;
335387 }
336388 } catch {
@@ -344,9 +396,11 @@ export class JupyterSession implements IJupyterSession {
344396 this . onRestartedEvent . dispose ( ) ;
345397 }
346398 if ( this . connInfo ) {
399+ traceInfo ( 'ShutdownSessionAndConnection - dispose conn info' ) ;
347400 this . connInfo . dispose ( ) ; // This should kill the process that's running
348401 this . connInfo = undefined ;
349402 }
403+ traceInfo ( 'ShutdownSessionAndConnection -- complete' ) ;
350404 }
351405
352406}
0 commit comments