@@ -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 }  ) ; 
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 }  ) ; 
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