@@ -35,6 +35,7 @@ import {
3535} from '../types' ;
3636import { JupyterConnection , JupyterServerInfo } from './jupyterConnection' ;
3737import { JupyterKernelSpec } from './jupyterKernelSpec' ;
38+ import { JupyterWaitForIdleError } from './jupyterWaitForIdleError' ;
3839
3940enum ModuleExistsResult {
4041 NotFound ,
@@ -124,66 +125,65 @@ export class JupyterExecutionBase implements IJupyterExecution {
124125 return this . isNotebookSupported ( cancelToken ) ;
125126 }
126127
128+ //tslint:disable:cyclomatic-complexity
127129 public connectToNotebookServer ( options ?: INotebookServerOptions , cancelToken ?: CancellationToken ) : Promise < INotebookServer | undefined > {
128130 // Return nothing if we cancel
129131 return Cancellation . race ( async ( ) => {
130- let connection : IConnection ;
131- let kernelSpec : IJupyterKernelSpec | undefined ;
132-
133- // If our uri is undefined or if it's set to local launch we need to launch a server locally
134- if ( ! options || ! options . uri ) {
135- const launchResults = await this . startNotebookServer ( options && options . useDefaultConfig ? true : false , cancelToken ) ;
136- if ( launchResults ) {
137- connection = launchResults . connection ;
138- kernelSpec = launchResults . kernelSpec ;
139- } else {
140- // Throw a cancellation error if we were canceled.
141- Cancellation . throwIfCanceled ( cancelToken ) ;
142-
143- // Otherwise we can't connect
144- throw new Error ( localize . DataScience . jupyterNotebookFailure ( ) . format ( '' ) ) ;
145- }
146- } else {
147- // If we have a URI spec up a connection info for it
148- connection = this . createRemoteConnectionInfo ( options . uri ) ;
149- kernelSpec = undefined ;
150- }
151-
152- try {
153- // If we don't have a kernel spec yet, check using our current connection
154- if ( ! kernelSpec ) {
155- kernelSpec = await this . getMatchingKernelSpec ( connection , cancelToken ) ;
156- }
157-
158- // If still not found, log an error (this seems possible for some people, so use the default)
159- if ( ! kernelSpec ) {
160- this . logger . logError ( localize . DataScience . jupyterKernelSpecNotFound ( ) ) ;
161- }
162-
163- // Try to connect to our jupyter process
164- const result = this . serviceContainer . get < INotebookServer > ( INotebookServer ) ;
165- const info = await this . interpreterService . getActiveInterpreter ( ) ;
166- // Populate the launch info that we are starting our server with
167- const launchInfo : INotebookServerLaunchInfo = {
168- connectionInfo : connection ,
169- currentInterpreter : info ,
170- kernelSpec : kernelSpec ,
171- usingDarkTheme : options && options . usingDarkTheme ? options . usingDarkTheme : false ,
172- workingDir : options ? options . workingDir : undefined ,
173- uri : options ? options . uri : undefined ,
174- purpose : options ? options . purpose : uuid ( )
175- } ;
176- await result . connect ( launchInfo , cancelToken ) ;
177- sendTelemetryEvent ( launchInfo . uri ? Telemetry . ConnectRemoteJupyter : Telemetry . ConnectLocalJupyter ) ;
178- return result ;
179- } catch ( err ) {
180- // Something else went wrong
181- if ( options && options . uri ) {
182- sendTelemetryEvent ( Telemetry . ConnectRemoteFailedJupyter ) ;
183- throw new Error ( localize . DataScience . jupyterNotebookRemoteConnectFailed ( ) . format ( connection . baseUrl , err ) ) ;
184- } else {
185- sendTelemetryEvent ( Telemetry . ConnectFailedJupyter ) ;
186- throw new Error ( localize . DataScience . jupyterNotebookConnectFailed ( ) . format ( connection . baseUrl , err ) ) ;
132+ let result : INotebookServer | undefined ;
133+ let startInfo : { connection : IConnection ; kernelSpec : IJupyterKernelSpec | undefined } | undefined ;
134+ traceInfo ( `Connecting to ${ options ? options . purpose : 'unknown type of' } server` ) ;
135+ const interpreter = await this . interpreterService . getActiveInterpreter ( ) ;
136+
137+ // Try to connect to our jupyter process. Give it at most 2 tries.
138+ let tryCount = 0 ;
139+ while ( tryCount < 2 ) {
140+ try {
141+ // Start or connect to the process
142+ startInfo = await this . startOrConnect ( options , cancelToken ) ;
143+
144+ // Create a server that we will then attempt to connect to.
145+ result = this . serviceContainer . get < INotebookServer > ( INotebookServer ) ;
146+
147+ // Populate the launch info that we are starting our server with
148+ const launchInfo : INotebookServerLaunchInfo = {
149+ connectionInfo : startInfo . connection ,
150+ currentInterpreter : interpreter ,
151+ kernelSpec : startInfo . kernelSpec ,
152+ usingDarkTheme : options && options . usingDarkTheme ? options . usingDarkTheme : false ,
153+ workingDir : options ? options . workingDir : undefined ,
154+ uri : options ? options . uri : undefined ,
155+ purpose : options ? options . purpose : uuid ( )
156+ } ;
157+
158+ traceInfo ( `Connecting to process for ${ options ? options . purpose : 'unknown type of' } server` ) ;
159+ await result . connect ( launchInfo , cancelToken ) ;
160+ traceInfo ( `Connection complete for ${ options ? options . purpose : 'unknown type of' } server` ) ;
161+
162+ sendTelemetryEvent ( launchInfo . uri ? Telemetry . ConnectRemoteJupyter : Telemetry . ConnectLocalJupyter ) ;
163+ return result ;
164+ } catch ( err ) {
165+ // Cleanup after ourselves. server may be running partially.
166+ if ( result ) {
167+ traceInfo ( 'Killing server because of error' ) ;
168+ await result . dispose ( ) ;
169+ }
170+ if ( err instanceof JupyterWaitForIdleError && tryCount < 2 ) {
171+ // Special case. This sometimes happens where jupyter doesn't ever connect. Cleanup after
172+ // ourselves and propagate the failure outwards.
173+ traceInfo ( 'Retry because of wait for idle problem.' ) ;
174+ tryCount += 1 ;
175+ } else if ( startInfo ) {
176+ // Something else went wrong
177+ if ( options && options . uri ) {
178+ sendTelemetryEvent ( Telemetry . ConnectRemoteFailedJupyter ) ;
179+ throw new Error ( localize . DataScience . jupyterNotebookRemoteConnectFailed ( ) . format ( startInfo . connection . baseUrl , err ) ) ;
180+ } else {
181+ sendTelemetryEvent ( Telemetry . ConnectFailedJupyter ) ;
182+ throw new Error ( localize . DataScience . jupyterNotebookConnectFailed ( ) . format ( startInfo . connection . baseUrl , err ) ) ;
183+ }
184+ } else {
185+ throw err ;
186+ }
187187 }
188188 }
189189 } , cancelToken ) ;
@@ -258,6 +258,45 @@ export class JupyterExecutionBase implements IJupyterExecution {
258258 }
259259 }
260260
261+ private async startOrConnect ( options ?: INotebookServerOptions , cancelToken ?: CancellationToken ) : Promise < { connection : IConnection ; kernelSpec : IJupyterKernelSpec | undefined } > {
262+ let connection : IConnection | undefined ;
263+ let kernelSpec : IJupyterKernelSpec | undefined ;
264+
265+ // If our uri is undefined or if it's set to local launch we need to launch a server locally
266+ if ( ! options || ! options . uri ) {
267+ traceInfo ( `Launching ${ options ? options . purpose : 'unknown type of' } server` ) ;
268+ const launchResults = await this . startNotebookServer ( options && options . useDefaultConfig ? true : false , cancelToken ) ;
269+ if ( launchResults ) {
270+ connection = launchResults . connection ;
271+ kernelSpec = launchResults . kernelSpec ;
272+ } else {
273+ // Throw a cancellation error if we were canceled.
274+ Cancellation . throwIfCanceled ( cancelToken ) ;
275+
276+ // Otherwise we can't connect
277+ throw new Error ( localize . DataScience . jupyterNotebookFailure ( ) . format ( '' ) ) ;
278+ }
279+ } else {
280+ // If we have a URI spec up a connection info for it
281+ connection = this . createRemoteConnectionInfo ( options . uri ) ;
282+ kernelSpec = undefined ;
283+ }
284+
285+ // If we don't have a kernel spec yet, check using our current connection
286+ if ( ! kernelSpec && connection . localLaunch ) {
287+ traceInfo ( `Getting kernel specs for ${ options ? options . purpose : 'unknown type of' } server` ) ;
288+ kernelSpec = await this . getMatchingKernelSpec ( connection , cancelToken ) ;
289+ }
290+
291+ // If still not found, log an error (this seems possible for some people, so use the default)
292+ if ( ! kernelSpec && connection . localLaunch ) {
293+ this . logger . logError ( localize . DataScience . jupyterKernelSpecNotFound ( ) ) ;
294+ }
295+
296+ // Return the data we found.
297+ return { connection, kernelSpec} ;
298+ }
299+
261300 private createRemoteConnectionInfo = ( uri : string ) : IConnection => {
262301 let url : URL ;
263302 try {
0 commit comments