diff --git a/build/ci/templates/test_phases.yml b/build/ci/templates/test_phases.yml index f7edee7583e9..0888acd8dc18 100644 --- a/build/ci/templates/test_phases.yml +++ b/build/ci/templates/test_phases.yml @@ -203,6 +203,7 @@ steps: python -m pip install -U pip python -m pip install numpy python -m pip install --upgrade -r ./build/functional-test-requirements.txt + python -m pip install --upgrade --pre ptvsd displayName: 'pip install functional requirements' condition: and(succeeded(), eq(variables['NeedsPythonFunctionalReqs'], 'true')) diff --git a/build/ci/vscode-python-nightly-ci.yaml b/build/ci/vscode-python-nightly-ci.yaml index 6cd29e185ae5..da556caeed3a 100644 --- a/build/ci/vscode-python-nightly-ci.yaml +++ b/build/ci/vscode-python-nightly-ci.yaml @@ -34,7 +34,7 @@ jobs: # ignorePythonVersions: "3.6,3.5" - job: 'Nightly' - + timeoutInMinutes: 90 strategy: matrix: # Each member of this list must contain these values: diff --git a/build/functional-test-requirements.txt b/build/functional-test-requirements.txt index d509ecc74a82..dce60486ae64 100644 --- a/build/functional-test-requirements.txt +++ b/build/functional-test-requirements.txt @@ -4,4 +4,4 @@ jupyter numpy matplotlib pandas -livelossplot \ No newline at end of file +livelossplot diff --git a/package-lock.json b/package-lock.json index 7396d6f5abc4..bc43bf2714c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1888,13 +1888,13 @@ }, "@types/json5": { "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "resolved": "http://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/loader-utils": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", "integrity": "sha512-euKGFr2oCB3ASBwG39CYJMR3N9T0nanVqXdiH7Zu/Nqddt6SmFRxytq/i2w9LQYNQekEtGBz+pE3qG6fQTNvRg==", "dev": true, "requires": { @@ -2015,7 +2015,7 @@ }, "@types/relateurl": { "version": "0.2.28", - "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", + "resolved": "http://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", "integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=", "dev": true }, @@ -3363,7 +3363,7 @@ }, "babel-plugin-syntax-jsx": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", "dev": true }, @@ -3774,7 +3774,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -3826,7 +3826,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -5333,7 +5333,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -5789,7 +5789,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -5802,7 +5802,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -6740,7 +6740,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -7708,7 +7708,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { @@ -9245,7 +9245,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -10280,7 +10280,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -12775,7 +12775,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -13021,9 +13021,9 @@ } }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -13344,7 +13344,7 @@ }, "moment": { "version": "2.21.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "resolved": "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz", "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, "monaco-editor": { @@ -14696,7 +14696,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -16957,7 +16957,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -17042,7 +17042,7 @@ }, "simple-html-tokenizer": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", "integrity": "sha1-BcLuxXn//+FFoDCsJs/qYbmA+r4=", "dev": true }, @@ -17689,7 +17689,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -19117,7 +19117,7 @@ "dependencies": { "@types/react": { "version": "0.14.57", - "resolved": "https://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", + "resolved": "http://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", "integrity": "sha1-GHioZU+v3R04G4RXKStkM0mMW2I=", "dev": true } @@ -19909,7 +19909,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -20739,7 +20739,7 @@ }, "webpack-node-externals": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", + "resolved": "http://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", "dev": true }, @@ -20971,7 +20971,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/src/client/datascience/jupyter/jupyterServer.ts b/src/client/datascience/jupyter/jupyterServer.ts index cc497a476b9e..03f68f046ff6 100644 --- a/src/client/datascience/jupyter/jupyterServer.ts +++ b/src/client/datascience/jupyter/jupyterServer.ts @@ -14,7 +14,7 @@ import { CancellationToken } from 'vscode-jsonrpc'; import { ILiveShareApi } from '../../common/application/types'; import { Cancellation, CancellationError } from '../../common/cancellation'; -import { traceInfo, traceWarning } from '../../common/logger'; +import { traceError, traceInfo, traceWarning } from '../../common/logger'; import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry, ILogger } from '../../common/types'; import { createDeferred, Deferred, waitForPromise } from '../../common/utils/async'; import * as localize from '../../common/utils/localize'; @@ -44,17 +44,21 @@ class CellSubscriber { private cellRef: ICell; private subscriber: Subscriber; private promiseComplete: (self: CellSubscriber) => void; - private startTime: number; + private _startTime: number; constructor(cell: ICell, subscriber: Subscriber, promiseComplete: (self: CellSubscriber) => void) { this.cellRef = cell; this.subscriber = subscriber; this.promiseComplete = promiseComplete; - this.startTime = Date.now(); + this._startTime = Date.now(); + } + + public get startTime(): number { + return this._startTime; } public isValid(sessionStartTime: number | undefined) { - return sessionStartTime && this.startTime > sessionStartTime; + return sessionStartTime && this.startTime >= sessionStartTime; } public next(sessionStartTime: number | undefined) { @@ -305,15 +309,19 @@ export class JupyterServerBase implements INotebookServer { // Update our start time so we don't keep sending responses this.sessionStartTime = Date.now(); + traceInfo('restartKernel - finishing cells that are outstanding'); // Complete all pending as an error. We're restarting this.finishUncompletedCells(); + traceInfo('restartKernel - restarting kernel'); // Restart our kernel await this.session.restart(timeoutMs); // Rerun our initial setup for the notebook this.ranInitialSetup = false; + traceInfo('restartKernel - initialSetup'); await this.initialNotebookSetup(); + traceInfo('restartKernel - initialSetup completed'); return; } @@ -531,6 +539,8 @@ export class JupyterServerBase implements INotebookServer { } } + traceError('No session during execute observable'); + // Can't run because no session return new Observable(subscriber => { subscriber.error(this.getDisposedError()); @@ -539,7 +549,7 @@ export class JupyterServerBase implements INotebookServer { } private generateRequest = (code: string, silent?: boolean): Kernel.IFuture | undefined => { - //this.logger.logInformation(`Executing code in jupyter : ${code}`) + //traceInfo(`Executing code in jupyter : ${code}`); try { const cellMatcher = new CellMatcher(this.configService.getSettings().datascience); return this.session ? this.session.requestExecute( @@ -563,6 +573,7 @@ export class JupyterServerBase implements INotebookServer { // Set up our initial plotting and imports private async initialNotebookSetup(cancelToken?: CancellationToken): Promise { if (this.ranInitialSetup) { + traceInfo(`Already ran setup for ${this.id}`); return; } this.ranInitialSetup = true; @@ -570,18 +581,21 @@ export class JupyterServerBase implements INotebookServer { try { // When we start our notebook initial, change to our workspace or user specified root directory if (this.launchInfo && this.launchInfo.workingDir && this.launchInfo.connectionInfo.localLaunch) { + traceInfo(`Changing directory for ${this.id}`); await this.changeDirectoryIfPossible(this.launchInfo.workingDir); } const settings = this.configService.getSettings().datascience; const matplobInit = !settings || settings.enablePlotViewer ? CodeSnippits.MatplotLibInitSvg : CodeSnippits.MatplotLibInitPng; + traceInfo(`Initialize matplotlib for ${this.id}`); // Force matplotlib to inline and save the default style. We'll use this later if we // get a request to update style await this.executeSilently( matplobInit, cancelToken ); + traceInfo(`Initial setup complete for ${this.id}`); } catch (e) { traceWarning(e); } @@ -640,6 +654,7 @@ export class JupyterServerBase implements INotebookServer { if (this.launchInfo && this.launchInfo.connectionInfo && this.launchInfo.connectionInfo.localProcExitCode) { // Not running, just exit const exitCode = this.launchInfo.connectionInfo.localProcExitCode; + traceError(`Jupyter crashed with code ${exitCode}`); subscriber.error(this.sessionStartTime, new Error(localize.DataScience.jupyterServerCrashed().format(exitCode.toString()))); subscriber.complete(this.sessionStartTime); } else { @@ -716,6 +731,10 @@ export class JupyterServerBase implements INotebookServer { } } } else { + const sessionDate = new Date(this.sessionStartTime!); + const cellDate = new Date(subscriber.startTime); + traceInfo(`Session start time is newer than cell : \r\n${sessionDate.toTimeString()}\r\n${cellDate.toTimeString()}`); + // Otherwise just set to an error this.handleInterrupted(subscriber.cell); subscriber.cell.state = CellState.error; diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index 7f44a6d60617..aee1df5e4522 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -39,13 +39,14 @@ export class JupyterSession implements IJupyterSession { private kernelSpec: IJupyterKernelSpec | undefined; private sessionManager: SessionManager | undefined; private session: Session.ISession | undefined; - private restartSessionPromise: Promise | undefined; + private restartSessionPromise: Promise | undefined; private contentsManager: ContentsManager | undefined; private notebookFiles: Contents.IModel[] = []; private onRestartedEvent: EventEmitter | undefined; private statusHandler: Slot | undefined; private connected: boolean = false; private jupyterPasswordConnect: IJupyterPasswordConnect; + private oldSessions: Session.ISession[] = []; constructor( connInfo: IConnection, @@ -87,44 +88,38 @@ export class JupyterSession implements IJupyterSession { return this.onRestartedEvent.event; } - public async waitForIdle(timeout: number): Promise { - if (this.session && this.session.kernel) { - // This function seems to cause CI builds to timeout randomly on - // different tests. Waiting for status to go idle doesn't seem to work and - // in the past, waiting on the ready promise doesn't work either. Check status with a maximum of 5 seconds - const startTime = Date.now(); - while (this.session && - this.session.kernel && - this.session.kernel.status !== 'idle' && - (Date.now() - startTime < timeout)) { - traceInfo(`Waiting for idle: ${this.session.kernel.status}`); - await sleep(100); - } - - // If we didn't make it out in ten seconds, indicate an error - if (!this.session || !this.session.kernel || this.session.kernel.status !== 'idle') { - throw new JupyterWaitForIdleError(localize.DataScience.jupyterLaunchTimedOut()); - } - } + public waitForIdle(timeout: number): Promise { + return this.waitForIdleOnSession(this.session, timeout); } public async restart(_timeout: number): Promise { // Just kill the current session and switch to the other if (this.restartSessionPromise && this.session && this.sessionManager && this.contentsManager) { + traceInfo(`Restarting ${this.session.kernel.id}`); + // Save old state for shutdown const oldSession = this.session; const oldStatusHandler = this.statusHandler; - // Just switch to the other session. + // Just switch to the other session. It should already be ready this.session = await this.restartSessionPromise; + if (!this.session) { + throw new Error(localize.DataScience.sessionDisposed()); + } + traceInfo(`Got new session ${this.session.kernel.id}`); // Rewire our status changed event. this.statusHandler = this.onStatusChanged.bind(this.onStatusChanged); this.session.statusChanged.connect(this.statusHandler); // After switching, start another in case we restart again. - this.restartSessionPromise = this.createSession(oldSession.serverSettings, this.contentsManager); - this.shutdownSession(oldSession, oldStatusHandler).ignoreErrors(); + this.restartSessionPromise = this.createRestartSession(oldSession.serverSettings, this.contentsManager); + traceInfo('Started new restart session'); + if (oldStatusHandler) { + oldSession.statusChanged.disconnect(oldStatusHandler); + } + // Don't shutdown old sessions yet. This seems to hang tests. + this.oldSessions.push(oldSession); } else { throw new Error(localize.DataScience.sessionDisposed()); } @@ -157,7 +152,7 @@ export class JupyterSession implements IJupyterSession { this.session = await this.createSession(serverSettings, this.contentsManager, cancelToken); // Start another session to handle restarts - this.restartSessionPromise = this.createSession(serverSettings, this.contentsManager, cancelToken); + this.restartSessionPromise = this.createRestartSession(serverSettings, this.contentsManager, cancelToken); // Listen for session status changes this.statusHandler = this.onStatusChanged.bind(this.onStatusChanged); @@ -171,6 +166,54 @@ export class JupyterSession implements IJupyterSession { return this.connected; } + private async waitForIdleOnSession(session: Session.ISession | undefined, timeout: number): Promise { + if (session && session.kernel) { + traceInfo(`Waiting for idle on: ${session.kernel.id}${session.kernel.status}`); + + // This function seems to cause CI builds to timeout randomly on + // different tests. Waiting for status to go idle doesn't seem to work and + // in the past, waiting on the ready promise doesn't work either. Check status with a maximum of 5 seconds + const startTime = Date.now(); + while (session && + session.kernel && + session.kernel.status !== 'idle' && + (Date.now() - startTime < timeout)) { + await sleep(100); + } + + traceInfo(`Finished waiting for idle on: ${session.kernel.id}${session.kernel.status}`); + + // If we didn't make it out in ten seconds, indicate an error + if (!session || !session.kernel || session.kernel.status !== 'idle') { + throw new JupyterWaitForIdleError(localize.DataScience.jupyterLaunchTimedOut()); + } + } + } + + private async createRestartSession(serverSettings: ServerConnection.ISettings, contentsManager: ContentsManager, cancelToken?: CancellationToken): Promise { + let result: Session.ISession | undefined; + let tryCount = 0; + // tslint:disable-next-line: no-any + let exception: any; + while (tryCount < 3) { + try { + result = await this.createSession(serverSettings, contentsManager, cancelToken); + await this.waitForIdleOnSession(result, 30000); + return result; + } catch (exc) { + traceInfo(`Error waiting for restart session: ${exc}`); + tryCount += 1; + if (result) { + // Cleanup later. + this.oldSessions.push(result); + } + result = undefined; + exception = exc; + } + } + throw exception; + } + private async createSession(serverSettings: ServerConnection.ISettings, contentsManager: ContentsManager, cancelToken?: CancellationToken): Promise { // Create a temporary notebook for this session. @@ -277,7 +320,9 @@ export class JupyterSession implements IJupyterSession { } private async shutdownSession(session: Session.ISession | undefined, statusHandler: Slot | undefined): Promise { - if (session) { + if (session && session.kernel) { + const kernelId = session.kernel.id; + traceInfo(`shutdownSession ${kernelId} - start`); try { if (statusHandler) { session.statusChanged.disconnect(statusHandler); @@ -288,23 +333,22 @@ export class JupyterSession implements IJupyterSession { // https://github.com/jupyterlab/jupyterlab/issues/4252 // tslint:disable:no-any if (isTestExecution()) { - if (session && session.kernel) { - const defaultKernel = session.kernel as any; - if (defaultKernel && defaultKernel._futures) { - const futures = defaultKernel._futures as Map; - if (futures) { - futures.forEach(f => { - if (f._status !== undefined) { - f._status |= 4; - } - }); - } + const defaultKernel = session.kernel as any; + if (defaultKernel && defaultKernel._futures) { + const futures = defaultKernel._futures as Map; + if (futures) { + futures.forEach(f => { + if (f._status !== undefined) { + f._status |= 4; + } + }); } } + await waitForPromise(session.shutdown(), 1000); + } else { + // Shutdown may fail if the process has been killed + await waitForPromise(session.shutdown(), 1000); } - - // Shutdown may fail if the process has been killed - await waitForPromise(session.shutdown(), 1000); } catch { noop(); } @@ -315,22 +359,30 @@ export class JupyterSession implements IJupyterSession { // Ignore, just trace. traceWarning(e); } + traceInfo(`shutdownSession ${kernelId} - shutdown complete`); } } //tslint:disable:cyclomatic-complexity private async shutdownSessionAndConnection(): Promise { if (this.contentsManager) { + traceInfo('ShutdownSessionAndConnection - dispose contents manager'); this.contentsManager.dispose(); this.contentsManager = undefined; } if (this.session || this.sessionManager) { try { + traceInfo('ShutdownSessionAndConnection - old sessions'); + await Promise.all(this.oldSessions.map(s => this.shutdownSession(s, undefined))); + traceInfo('ShutdownSessionAndConnection - current session'); await this.shutdownSession(this.session, this.statusHandler); + traceInfo('ShutdownSessionAndConnection - get restart session'); const restartSession = await this.restartSessionPromise; + traceInfo('ShutdownSessionAndConnection - shutdown restart session'); await this.shutdownSession(restartSession, undefined); if (this.sessionManager && !this.sessionManager.isDisposed) { + traceInfo('ShutdownSessionAndConnection - dispose session manager'); this.sessionManager.dispose(); } } catch { @@ -344,9 +396,11 @@ export class JupyterSession implements IJupyterSession { this.onRestartedEvent.dispose(); } if (this.connInfo) { + traceInfo('ShutdownSessionAndConnection - dispose conn info'); this.connInfo.dispose(); // This should kill the process that's running this.connInfo = undefined; } + traceInfo('ShutdownSessionAndConnection -- complete'); } } diff --git a/src/test/common/asyncDump.ts b/src/test/common/asyncDump.ts index 7236c16e4258..a974c5846a55 100644 --- a/src/test/common/asyncDump.ts +++ b/src/test/common/asyncDump.ts @@ -5,5 +5,5 @@ const log = require('why-is-node-running'); // Call this function to debug async hangs. It should print out stack traces of still running promises. export function asyncDump() { - log(); + log(); } diff --git a/src/test/datascience/liveshare.functional.test.tsx b/src/test/datascience/liveshare.functional.test.tsx index 7dbda32e380b..85ea8d6970cd 100644 --- a/src/test/datascience/liveshare.functional.test.tsx +++ b/src/test/datascience/liveshare.functional.test.tsx @@ -17,8 +17,15 @@ import { } from '../../client/common/application/types'; import { IFileSystem } from '../../client/common/platform/types'; import { Commands } from '../../client/datascience/constants'; -import { ICodeWatcher, IDataScienceCommandListener, IInteractiveWindow, IInteractiveWindowProvider, IJupyterExecution } from '../../client/datascience/types'; +import { + ICodeWatcher, + IDataScienceCommandListener, + IInteractiveWindow, + IInteractiveWindowProvider, + IJupyterExecution +} from '../../client/datascience/types'; import { MainPanel } from '../../datascience-ui/history-react/MainPanel'; +import { asyncDump } from '../common/asyncDump'; import { DataScienceIocContainer } from './dataScienceIocContainer'; import { createDocument } from './editor-integration/helpers'; import { addMockData, CellPosition, verifyHtmlOnCell } from './interactiveWindowTestHelpers'; @@ -54,6 +61,10 @@ suite('DataScience LiveShare tests', () => { lastErrorMessage = undefined; }); + suiteTeardown(() => { + asyncDump(); + }); + function createContainer(role: vsls.Role): DataScienceIocContainer { const result = new DataScienceIocContainer(); result.registerDataScienceTypes(); diff --git a/src/test/datascience/mockDebugService.ts b/src/test/datascience/mockDebugService.ts index 3036d82a3747..39e1771cc8f3 100644 --- a/src/test/datascience/mockDebugService.ts +++ b/src/test/datascience/mockDebugService.ts @@ -73,6 +73,7 @@ export class MockDebuggerService implements IDebugService, IDisposable { private sessionCustomEvent: EventEmitter = new EventEmitter(); private breakpointsChangedEvent: EventEmitter = new EventEmitter(); private _breakpoints: Breakpoint[] = []; + private _stoppedThreadId: number | undefined; constructor( @inject(IProtocolParser) private protocolParser: IProtocolParser ) { @@ -155,7 +156,7 @@ export class MockDebuggerService implements IDebugService, IDisposable { deferred.resolve(args as DebugProtocol.StackTraceResponse); }); await this.emitMessage('stackTrace', { - threadId: 1, + threadId: this._stoppedThreadId ? this._stoppedThreadId : 1, startFrame: 0, levels: 1 }); @@ -266,7 +267,11 @@ export class MockDebuggerService implements IDebugService, IDisposable { }); } - private onBreakpoint(_args: any): void { + private onBreakpoint(args: DebugProtocol.StoppedEvent): void { + // Save the current thread id. We use this in our stack trace request + this._stoppedThreadId = args.body.threadId; + + // Indicate we stopped at a breakpoint this.breakpointEmitter.fire(); } diff --git a/src/test/datascience/notebook.functional.test.ts b/src/test/datascience/notebook.functional.test.ts index c92dcc113007..bb66452cbbfd 100644 --- a/src/test/datascience/notebook.functional.test.ts +++ b/src/test/datascience/notebook.functional.test.ts @@ -486,7 +486,7 @@ suite('DataScience notebook tests', () => { await verifyError(server, 'a', `name 'a' is not defined`); } catch (exc) { - assert.ok(exc instanceof JupyterKernelPromiseFailedError, 'Restarting did not timeout correctly'); + assert.ok(exc instanceof JupyterKernelPromiseFailedError, `Restarting did not timeout correctly for ${exc}`); } });