From 076610605e4b13fe80a812e1349e31cad2b19e30 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 10 Aug 2022 16:35:01 +1000 Subject: [PATCH 1/3] Handle situation when remote connection is lost --- news/2 Fixes/10568.md | 2 + package.nls.json | 2 + src/kernels/common/baseJupyterSession.ts | 12 ++ .../kernelAutoReConnectFailedMonitor.ts | 111 ++++++++++++++++++ src/kernels/kernelAutoReConnectMonitor.ts | 92 +++++++++++++++ src/kernels/serviceRegistry.node.ts | 4 + src/kernels/serviceRegistry.web.ts | 4 + src/platform/common/utils/localize.ts | 7 ++ src/platform/errors/errorUtils.ts | 4 +- 9 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 news/2 Fixes/10568.md create mode 100644 src/kernels/kernelAutoReConnectFailedMonitor.ts create mode 100644 src/kernels/kernelAutoReConnectMonitor.ts diff --git a/news/2 Fixes/10568.md b/news/2 Fixes/10568.md new file mode 100644 index 00000000000..cd912961fb0 --- /dev/null +++ b/news/2 Fixes/10568.md @@ -0,0 +1,2 @@ +Handle situations when the connection to a remote Kernel is lost after executing a cell. +This also fixes the issue [Unable to interrupt a kernel when the remote kernel connection is lost #9828](https://github.com/microsoft/vscode-jupyter/issues/9828) diff --git a/package.nls.json b/package.nls.json index 0917a9d178d..9244268cd89 100644 --- a/package.nls.json +++ b/package.nls.json @@ -537,6 +537,7 @@ "DataScienceSurveyBanner.bannerLabelYes": "Yes, take survey now", "DataScienceSurveyBanner.bannerLabelNo": "No, thanks", "InteractiveShiftEnterBanner.bannerMessage": "Would you like to run code in the 'Interactive' window (an IPython console) for 'shift-enter'? Select 'No' to continue to run code in the Python Terminal. This can be changed later in settings.", + "DataScience.automaticallyReconnectingToAKernelProgressMessage": "Reconnecting to the kernel {0}", "DataScience.restartingKernelStatus": { "message": "Restarting Kernel {0}", "comment": ["{Locked='Kernel'}"] @@ -846,6 +847,7 @@ "message": "The kernel '{0}' died and is being automatically restarted by Jupyter. Click [here](https://aka.ms/vscodeJupyterKernelCrash) for more info. View Jupyter [log](command:jupyter.viewOutput) for further details.", "comment": ["{Locked='Kernel'}", "{Locked='command:jupyter.viewOutput'}", "{Locked='vscodeJupyterKernelCrash'}"] }, + "DataScience.kernelDisconnected": "Unable to connect to the kernel, please verify the Jupyter Server connection. View Jupyter [log](command:jupyter.viewOutput) for further details.", "DataScience.kernelCrashedDueToCodeInCurrentOrPreviousCell": { "message": "The Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click here for more info. View Jupyter [log](command:jupyter.viewOutput) for further details.", "comment": ["{Locked='Kernel'}", "{Locked='command:jupyter.viewOutput'}", "{Locked='vscodeJupyterKernelCrash'}"] diff --git a/src/kernels/common/baseJupyterSession.ts b/src/kernels/common/baseJupyterSession.ts index 4b03252d155..fc8b4610856 100644 --- a/src/kernels/common/baseJupyterSession.ts +++ b/src/kernels/common/baseJupyterSession.ts @@ -231,10 +231,14 @@ export abstract class BaseJupyterSession implements IBaseKernelConnectionSession // Rewire our status changed event. newSession.statusChanged.connect(this.statusHandler); + newSession.kernel.connectionStatusChanged.connect(this.onKernelConnectionStatusHandler, this); } traceInfo('Started new restart session'); if (oldStatusHandler && oldSession) { oldSession.statusChanged.disconnect(oldStatusHandler); + if (oldSession.kernel) { + oldSession.kernel.connectionStatusChanged.disconnect(this.onKernelConnectionStatusHandler, this); + } } this.shutdownSession(oldSession, undefined, false).ignoreErrors(); } @@ -410,6 +414,7 @@ export abstract class BaseJupyterSession implements IBaseKernelConnectionSession } if (this.statusHandler) { oldSession.statusChanged.disconnect(this.statusHandler); + oldSession.kernel?.connectionStatusChanged.disconnect(this.onKernelConnectionStatusHandler, this); } } this._session = session; @@ -420,6 +425,7 @@ export abstract class BaseJupyterSession implements IBaseKernelConnectionSession // Listen for session status changes session.statusChanged.connect(this.statusHandler); + session.kernel?.connectionStatusChanged.connect(this.onKernelConnectionStatusHandler, this); if (session.kernelSocketInformation.socket?.onAnyMessage) { // These messages are sent directly to the kernel bypassing the Jupyter lab npm libraries. // As a result, we don't get any notification that messages were sent (on the anymessage signal). @@ -568,6 +574,12 @@ export abstract class BaseJupyterSession implements IBaseKernelConnectionSession return 'unknown'; } + private onKernelConnectionStatusHandler(_: unknown, kernelConnection: Kernel.ConnectionStatus) { + if (kernelConnection === 'disconnected') { + const status = this.getServerStatus(); + this.onStatusChangedEvent.fire(status); + } + } private onStatusChanged(_s: Session.ISessionConnection) { const status = this.getServerStatus(); traceInfoIfCI(`Server Status = ${status}`); diff --git a/src/kernels/kernelAutoReConnectFailedMonitor.ts b/src/kernels/kernelAutoReConnectFailedMonitor.ts new file mode 100644 index 00000000000..561f2da65f3 --- /dev/null +++ b/src/kernels/kernelAutoReConnectFailedMonitor.ts @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import type { Kernel } from '@jupyterlab/services'; +import { inject, injectable } from 'inversify'; +import { NotebookCell } from 'vscode'; +import { IExtensionSyncActivationService } from '../platform/activation/types'; +import { IDisposableRegistry } from '../platform/common/types'; +import { DataScience } from '../platform/common/utils/localize'; +import { Telemetry } from '../telemetry'; +import { endCellAndDisplayErrorsInCell } from './execution/helpers'; +import { getDisplayNameOrNameOfKernelConnection } from './helpers'; +import { sendKernelTelemetryEvent } from './telemetry/sendKernelTelemetryEvent'; +import { IKernel, IKernelProvider, isLocalConnection } from './types'; + +/** + * In the case of Jupyter kernels, when a kernel dies Jupyter will automatically restart that kernel. + * In such a case we need to display a little progress indicator so user is aware of the fact that the kernel is restarting. + */ +@injectable() +export class kernelAutoReConnectFailedMonitor implements IExtensionSyncActivationService { + private kernelsStartedSuccessfully = new WeakSet(); + private kernelConnectionToKernelMapping = new WeakMap(); + private kernelsRestarting = new WeakSet(); + private kernelReconnectProgress = new WeakSet(); + private lastExecutedCellPerKernel = new WeakMap(); + + constructor( + @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, + @inject(IKernelProvider) private kernelProvider: IKernelProvider + ) {} + public activate(): void { + this.kernelProvider.onDidStartKernel(this.onDidStartKernel, this, this.disposableRegistry); + this.disposableRegistry.push( + this.kernelProvider.onDidDisposeKernel((kernel) => { + this.kernelReconnectProgress.delete(kernel); + }, this) + ); + this.disposableRegistry.push( + this.kernelProvider.onDidRestartKernel((kernel) => { + this.kernelReconnectProgress.delete(kernel); + }, this) + ); + } + private onDidStartKernel(kernel: IKernel) { + if (!this.kernelsStartedSuccessfully.has(kernel)) { + kernel.onPreExecute( + (cell) => this.lastExecutedCellPerKernel.set(kernel, cell), + this, + this.disposableRegistry + ); + + if (!kernel.session?.kernel) { + return; + } + this.kernelsStartedSuccessfully.add(kernel); + this.kernelConnectionToKernelMapping.set(kernel.session?.kernel, kernel); + kernel.session?.kernel?.connectionStatusChanged.connect(this.onKernelStatusChanged, this); + kernel.addEventHook(async (e) => { + if (e === 'willRestart') { + this.kernelReconnectProgress.delete(kernel); + this.kernelsRestarting.add(kernel); + } + }); + kernel.onRestarted(() => this.kernelsRestarting.delete(kernel)); + } + } + private onKernelStatusChanged(connection: Kernel.IKernelConnection, connectionStatus: Kernel.ConnectionStatus) { + const kernel = this.kernelConnectionToKernelMapping.get(connection); + if (!kernel) { + return; + } + if (this.kernelsRestarting.has(kernel)) { + return; + } + switch (connectionStatus) { + case 'connected': { + this.kernelReconnectProgress.delete(kernel); + return; + } + case 'disconnected': { + if (this.kernelReconnectProgress.has(kernel)) { + this.kernelReconnectProgress.delete(kernel); + this.onKernelDisconnected(kernel)?.ignoreErrors(); + } + return; + } + case 'connecting': + this.kernelReconnectProgress.add(kernel); + return; + default: + return; + } + } + private async onKernelDisconnected(kernel: IKernel) { + const lastExecutedCell = this.lastExecutedCellPerKernel.get(kernel); + sendKernelTelemetryEvent(kernel.resourceUri, Telemetry.KernelCrash); + if (!lastExecutedCell) { + return; + } + + const message = isLocalConnection(kernel.kernelConnectionMetadata) + ? DataScience.kernelDisconnected() + : DataScience.remoteJupyterConnectionFailedWithServer().format( + getDisplayNameOrNameOfKernelConnection(kernel.kernelConnectionMetadata) + ); + await endCellAndDisplayErrorsInCell(lastExecutedCell, kernel.controller, message, false); + + // Given the fact that we know the kernel connection is dead, dispose the kernel to clean everything. + await kernel.dispose(); + } +} diff --git a/src/kernels/kernelAutoReConnectMonitor.ts b/src/kernels/kernelAutoReConnectMonitor.ts new file mode 100644 index 00000000000..13be5b4744b --- /dev/null +++ b/src/kernels/kernelAutoReConnectMonitor.ts @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import type { Kernel } from '@jupyterlab/services'; +import { inject, injectable } from 'inversify'; +import { Disposable, ProgressLocation } from 'vscode'; +import { IExtensionSyncActivationService } from '../platform/activation/types'; +import { IApplicationShell } from '../platform/common/application/types'; +import { IDisposable, IDisposableRegistry } from '../platform/common/types'; +import { createDeferred } from '../platform/common/utils/async'; +import { DataScience } from '../platform/common/utils/localize'; +import { noop } from '../platform/common/utils/misc'; +import { IStatusProvider } from '../platform/progress/types'; +import { getDisplayNameOrNameOfKernelConnection } from './helpers'; +import { IKernel, IKernelProvider } from './types'; + +/** + * In the case of Jupyter kernels, when a kernel dies Jupyter will automatically restart that kernel. + * In such a case we need to display a little progress indicator so user is aware of the fact that the kernel is restarting. + */ +@injectable() +export class KernelAutoReconnectMonitor implements IExtensionSyncActivationService { + private kernelsStartedSuccessfully = new WeakSet(); + private kernelConnectionToKernelMapping = new WeakMap(); + private kernelsRestarting = new WeakSet(); + private kernelReconnectProgress = new WeakMap(); + + constructor( + @inject(IStatusProvider) private appShell: IApplicationShell, + @inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry, + @inject(IKernelProvider) private kernelProvider: IKernelProvider + ) {} + public activate(): void { + this.kernelProvider.onDidStartKernel(this.onDidStartKernel, this, this.disposableRegistry); + this.disposableRegistry.push( + this.kernelProvider.onDidDisposeKernel((kernel) => { + this.kernelReconnectProgress.get(kernel)?.dispose(); + this.kernelReconnectProgress.delete(kernel); + }, this) + ); + this.disposableRegistry.push( + this.kernelProvider.onDidRestartKernel((kernel) => { + this.kernelReconnectProgress.get(kernel)?.dispose(); + this.kernelReconnectProgress.delete(kernel); + }, this) + ); + } + private onDidStartKernel(kernel: IKernel) { + if (!this.kernelsStartedSuccessfully.has(kernel)) { + if (!kernel.session?.kernel) { + return; + } + this.kernelsStartedSuccessfully.add(kernel); + this.kernelConnectionToKernelMapping.set(kernel.session?.kernel, kernel); + kernel.session?.kernel?.connectionStatusChanged.connect(this.onKernelStatusChanged, this); + kernel.addEventHook(async (e) => { + if (e === 'willRestart') { + this.kernelReconnectProgress.get(kernel)?.dispose(); + this.kernelReconnectProgress.delete(kernel); + this.kernelsRestarting.add(kernel); + } + }); + kernel.onRestarted(() => this.kernelsRestarting.delete(kernel)); + } + } + private onKernelStatusChanged(connection: Kernel.IKernelConnection, connectionStatus: Kernel.ConnectionStatus) { + const kernel = this.kernelConnectionToKernelMapping.get(connection); + if (!kernel) { + return; + } + if (this.kernelsRestarting.has(kernel)) { + return; + } + if (this.kernelReconnectProgress.has(kernel)) { + if (connectionStatus !== 'connected') { + this.kernelReconnectProgress.get(kernel)?.dispose(); + this.kernelReconnectProgress.delete(kernel); + } + return; + } + const deferred = createDeferred(); + const message = DataScience.automaticallyReconnectingToAKernelProgressMessage().format( + getDisplayNameOrNameOfKernelConnection(kernel.kernelConnectionMetadata) + ); + this.appShell + .withProgress({ location: ProgressLocation.Notification, title: message }, async () => deferred.promise) + .then(noop, noop); + + const disposable = new Disposable(() => deferred.resolve()); + this.kernelReconnectProgress.set(kernel, disposable); + this.disposableRegistry.push(disposable); + } +} diff --git a/src/kernels/serviceRegistry.node.ts b/src/kernels/serviceRegistry.node.ts index be3fdf4f0ab..741008a9e4b 100644 --- a/src/kernels/serviceRegistry.node.ts +++ b/src/kernels/serviceRegistry.node.ts @@ -42,6 +42,8 @@ import { Activation } from './activation.node'; import { PortAttributesProviders } from './port/portAttributeProvider.node'; import { ServerPreload } from './jupyter/launcher/serverPreload.node'; import { KernelStartupCodeProvider } from './kernelStartupCodeProvider.node'; +import { kernelAutoReConnectFailedMonitor } from './kernelAutoReConnectFailedMonitor'; +import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor'; export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) { serviceManager.addSingleton(IExtensionSingleActivationService, Activation); @@ -87,6 +89,8 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea ); serviceManager.addSingleton(IKernelDependencyService, KernelDependencyService); serviceManager.addSingleton(IExtensionSyncActivationService, KernelCrashMonitor); + serviceManager.addSingleton(IExtensionSyncActivationService, kernelAutoReConnectFailedMonitor); + serviceManager.addSingleton(IExtensionSyncActivationService, KernelAutoReconnectMonitor); serviceManager.addSingleton( IExtensionSyncActivationService, KernelAutoRestartMonitor diff --git a/src/kernels/serviceRegistry.web.ts b/src/kernels/serviceRegistry.web.ts index e1099553ac7..878e9fabfc0 100644 --- a/src/kernels/serviceRegistry.web.ts +++ b/src/kernels/serviceRegistry.web.ts @@ -22,6 +22,8 @@ import { KernelVariables } from './variables/kernelVariables'; import { JupyterVariables } from './variables/jupyterVariables'; import { PythonVariablesRequester } from './variables/pythonVariableRequester'; import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; +import { kernelAutoReConnectFailedMonitor } from './kernelAutoReConnectFailedMonitor'; +import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor'; @injectable() class RawNotebookSupportedService implements IRawNotebookSupportedService { @@ -59,6 +61,8 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea serviceManager.addSingleton(IJupyterVariables, KernelVariables, Identifiers.KERNEL_VARIABLES); serviceManager.addSingleton(IExtensionSyncActivationService, KernelCrashMonitor); + serviceManager.addSingleton(IExtensionSyncActivationService, kernelAutoReConnectFailedMonitor); + serviceManager.addSingleton(IExtensionSyncActivationService, KernelAutoReconnectMonitor); serviceManager.addSingleton(IKernelProvider, KernelProvider); serviceManager.addSingleton(IThirdPartyKernelProvider, ThirdPartyKernelProvider); serviceManager.addSingleton( diff --git a/src/platform/common/utils/localize.ts b/src/platform/common/utils/localize.ts index 5b51268bcb8..f025fd1cfba 100644 --- a/src/platform/common/utils/localize.ts +++ b/src/platform/common/utils/localize.ts @@ -463,6 +463,8 @@ export namespace DataScience { export const restartKernelMessageYes = () => localize('DataScience.restartKernelMessageYes', 'Restart'); export const restartKernelMessageDontAskAgain = () => localize('DataScience.restartKernelMessageDontAskAgain', "Don't Ask Again"); + export const automaticallyReconnectingToAKernelProgressMessage = () => + localize('DataScience.automaticallyReconnectingToAKernelProgressMessage', 'Reconnecting to the kernel {0}'); export const restartingKernelStatus = () => localize('DataScience.restartingKernelStatus', 'Restarting Kernel {0}'); export const restartingKernelFailed = () => localize( @@ -567,6 +569,11 @@ export namespace DataScience { }, "The Kernel crashed while executing code in the the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click here for more info. View Jupyter [log](command:jupyter.viewOutput) for further details." ); + export const kernelDisconnected = () => + localize( + 'DataScience.kernelDisconnected', + 'Unable to connect to the kernel, please verify the Jupyter Server connection. View Jupyter [log](command:jupyter.viewOutput) for further details.' + ); export const cannotRunCellKernelIsDead = () => localize( { key: 'DataScience.cannotRunCellKernelIsDead', comment: ['{Locked="kernel"}'] }, diff --git a/src/platform/errors/errorUtils.ts b/src/platform/errors/errorUtils.ts index 540e8b6f0cf..63581ad742e 100644 --- a/src/platform/errors/errorUtils.ts +++ b/src/platform/errors/errorUtils.ts @@ -665,9 +665,7 @@ export function createOutputWithErrorMessageForDisplay(errorMessage: string) { // Ensure all lines are colored red as errors (except for lines containing hyperlinks). const stack = errorMessage .splitLines({ removeEmptyEntries: false, trim: false }) - .map((line) => { - return line.includes(' `\u001b[1;31m${line}`) .join('\n'); return new NotebookCellOutput([ NotebookCellOutputItem.error({ From 75096af03761acb3c54b8f4c2d3dd2f2d4c1af0e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 10 Aug 2022 17:02:26 +1000 Subject: [PATCH 2/3] Misc --- src/kernels/serviceRegistry.node.ts | 10 ++++++++-- src/kernels/serviceRegistry.web.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/kernels/serviceRegistry.node.ts b/src/kernels/serviceRegistry.node.ts index 741008a9e4b..988d3b4331f 100644 --- a/src/kernels/serviceRegistry.node.ts +++ b/src/kernels/serviceRegistry.node.ts @@ -89,8 +89,14 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea ); serviceManager.addSingleton(IKernelDependencyService, KernelDependencyService); serviceManager.addSingleton(IExtensionSyncActivationService, KernelCrashMonitor); - serviceManager.addSingleton(IExtensionSyncActivationService, kernelAutoReConnectFailedMonitor); - serviceManager.addSingleton(IExtensionSyncActivationService, KernelAutoReconnectMonitor); + serviceManager.addSingleton( + IExtensionSyncActivationService, + kernelAutoReConnectFailedMonitor + ); + serviceManager.addSingleton( + IExtensionSyncActivationService, + KernelAutoReconnectMonitor + ); serviceManager.addSingleton( IExtensionSyncActivationService, KernelAutoRestartMonitor diff --git a/src/kernels/serviceRegistry.web.ts b/src/kernels/serviceRegistry.web.ts index 878e9fabfc0..9eebf2d7d22 100644 --- a/src/kernels/serviceRegistry.web.ts +++ b/src/kernels/serviceRegistry.web.ts @@ -61,8 +61,14 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea serviceManager.addSingleton(IJupyterVariables, KernelVariables, Identifiers.KERNEL_VARIABLES); serviceManager.addSingleton(IExtensionSyncActivationService, KernelCrashMonitor); - serviceManager.addSingleton(IExtensionSyncActivationService, kernelAutoReConnectFailedMonitor); - serviceManager.addSingleton(IExtensionSyncActivationService, KernelAutoReconnectMonitor); + serviceManager.addSingleton( + IExtensionSyncActivationService, + kernelAutoReConnectFailedMonitor + ); + serviceManager.addSingleton( + IExtensionSyncActivationService, + KernelAutoReconnectMonitor + ); serviceManager.addSingleton(IKernelProvider, KernelProvider); serviceManager.addSingleton(IThirdPartyKernelProvider, ThirdPartyKernelProvider); serviceManager.addSingleton( From 5d92d4051702e85bb0941065e3d21ee943ebdd2b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 11 Aug 2022 03:29:52 +1000 Subject: [PATCH 3/3] fixes --- src/kernels/common/baseJupyterSession.ts | 3 ++- .../kernelAutoReConnectFailedMonitor.ts | 2 +- src/kernels/serviceRegistry.node.ts | 4 ++-- src/kernels/serviceRegistry.web.ts | 4 ++-- .../jupyter/jupyterSession.unit.test.ts | 23 +++++++++++++++---- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/kernels/common/baseJupyterSession.ts b/src/kernels/common/baseJupyterSession.ts index fc8b4610856..c17a48c703c 100644 --- a/src/kernels/common/baseJupyterSession.ts +++ b/src/kernels/common/baseJupyterSession.ts @@ -52,7 +52,8 @@ export function suppressShutdownErrors(realKernel: any) { const defaultKernel = realKernel as any; // NOSONAR if (defaultKernel && defaultKernel._futures) { const futures = defaultKernel._futures as Map; // NOSONAR - if (futures) { + if (futures.forEach) { + // Requires for unit tests when things are mocked. futures.forEach((f) => { if (f._status !== undefined) { f._status |= 4; diff --git a/src/kernels/kernelAutoReConnectFailedMonitor.ts b/src/kernels/kernelAutoReConnectFailedMonitor.ts index 561f2da65f3..3ba26c98c1c 100644 --- a/src/kernels/kernelAutoReConnectFailedMonitor.ts +++ b/src/kernels/kernelAutoReConnectFailedMonitor.ts @@ -17,7 +17,7 @@ import { IKernel, IKernelProvider, isLocalConnection } from './types'; * In such a case we need to display a little progress indicator so user is aware of the fact that the kernel is restarting. */ @injectable() -export class kernelAutoReConnectFailedMonitor implements IExtensionSyncActivationService { +export class KernelAutoReConnectFailedMonitor implements IExtensionSyncActivationService { private kernelsStartedSuccessfully = new WeakSet(); private kernelConnectionToKernelMapping = new WeakMap(); private kernelsRestarting = new WeakSet(); diff --git a/src/kernels/serviceRegistry.node.ts b/src/kernels/serviceRegistry.node.ts index 988d3b4331f..e6cacc1aee4 100644 --- a/src/kernels/serviceRegistry.node.ts +++ b/src/kernels/serviceRegistry.node.ts @@ -42,7 +42,7 @@ import { Activation } from './activation.node'; import { PortAttributesProviders } from './port/portAttributeProvider.node'; import { ServerPreload } from './jupyter/launcher/serverPreload.node'; import { KernelStartupCodeProvider } from './kernelStartupCodeProvider.node'; -import { kernelAutoReConnectFailedMonitor } from './kernelAutoReConnectFailedMonitor'; +import { KernelAutoReConnectFailedMonitor } from './kernelAutoReConnectFailedMonitor'; import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor'; export function registerTypes(serviceManager: IServiceManager, isDevMode: boolean) { @@ -91,7 +91,7 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea serviceManager.addSingleton(IExtensionSyncActivationService, KernelCrashMonitor); serviceManager.addSingleton( IExtensionSyncActivationService, - kernelAutoReConnectFailedMonitor + KernelAutoReConnectFailedMonitor ); serviceManager.addSingleton( IExtensionSyncActivationService, diff --git a/src/kernels/serviceRegistry.web.ts b/src/kernels/serviceRegistry.web.ts index 9eebf2d7d22..7cedb1fed00 100644 --- a/src/kernels/serviceRegistry.web.ts +++ b/src/kernels/serviceRegistry.web.ts @@ -22,7 +22,7 @@ import { KernelVariables } from './variables/kernelVariables'; import { JupyterVariables } from './variables/jupyterVariables'; import { PythonVariablesRequester } from './variables/pythonVariableRequester'; import { CellOutputDisplayIdTracker } from './execution/cellDisplayIdTracker'; -import { kernelAutoReConnectFailedMonitor } from './kernelAutoReConnectFailedMonitor'; +import { KernelAutoReConnectFailedMonitor } from './kernelAutoReConnectFailedMonitor'; import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor'; @injectable() @@ -63,7 +63,7 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea serviceManager.addSingleton(IExtensionSyncActivationService, KernelCrashMonitor); serviceManager.addSingleton( IExtensionSyncActivationService, - kernelAutoReConnectFailedMonitor + KernelAutoReConnectFailedMonitor ); serviceManager.addSingleton( IExtensionSyncActivationService, diff --git a/src/test/datascience/jupyter/jupyterSession.unit.test.ts b/src/test/datascience/jupyter/jupyterSession.unit.test.ts index 13888c2cc76..5ff53e35702 100644 --- a/src/test/datascience/jupyter/jupyterSession.unit.test.ts +++ b/src/test/datascience/jupyter/jupyterSession.unit.test.ts @@ -10,7 +10,6 @@ import { Session, SessionManager } from '@jupyterlab/services'; -import { KernelConnection } from '@jupyterlab/services/lib/kernel/default'; import { SessionConnection } from '@jupyterlab/services/lib/session/default'; import { ISignal } from '@lumino/signaling'; import { assert } from 'chai'; @@ -80,7 +79,9 @@ suite('DataScience - JupyterSession', () => { kernel: { status: 'idle', restart: () => (restartCount = restartCount + 1), - registerCommTarget: noop + registerCommTarget: noop, + statusChanged: instance(mock>()), + connectionStatusChanged: instance(mock>()) }, shutdown: () => Promise.resolve(), isRemoteSession: false @@ -101,7 +102,7 @@ suite('DataScience - JupyterSession', () => { } }; session = mock(); - kernel = mock(KernelConnection); + kernel = mock(); when(session.kernel).thenReturn(instance(kernel)); statusChangedSignal = mock>(); const sessionDisposed = new Signal(instance(session)); @@ -116,6 +117,10 @@ suite('DataScience - JupyterSession', () => { when(session.kernel).thenReturn(instance(kernel)); when(session.isDisposed).thenReturn(false); when(kernel.status).thenReturn('idle'); + when(kernel.statusChanged).thenReturn(instance(mock>())); + when(kernel.connectionStatusChanged).thenReturn( + instance(mock>()) + ); when(connection.rootDirectory).thenReturn(Uri.file('')); when(connection.localLaunch).thenReturn(false); const channel = new MockOutputChannel('JUPYTER'); @@ -338,7 +343,7 @@ suite('DataScience - JupyterSession', () => { newSession = mock(SessionConnection); sessionDisposed = new Signal(instance(newSession)); when(newSession.disposed).thenReturn(sessionDisposed); - newKernelConnection = mock(KernelConnection); + newKernelConnection = mock(); newStatusChangedSignal = mock>(); newKernelChangedSignal = mock>(); const newIoPubSignal = @@ -357,6 +362,12 @@ suite('DataScience - JupyterSession', () => { when(newKernelConnection.clientId).thenReturn('restartClientId'); when(newKernelConnection.status).thenReturn('idle'); when(newSession.kernel).thenReturn(instance(newKernelConnection)); + when(newKernelConnection.statusChanged).thenReturn( + instance(mock>()) + ); + when(newKernelConnection.connectionStatusChanged).thenReturn( + instance(mock>()) + ); when(sessionManager.startNew(anything(), anything())).thenCall(() => { newSessionCreated.resolve(); return Promise.resolve(instance(newSession)); @@ -440,11 +451,13 @@ suite('DataScience - JupyterSession', () => { suite('Switching kernels', () => { setup(async () => { remoteSession = mock(); - remoteKernel = mock(KernelConnection); + remoteKernel = mock(); remoteSessionInstance = instance(remoteSession); remoteSessionInstance.isRemoteSession = false; when(remoteSession.kernel).thenReturn(instance(remoteKernel)); when(remoteKernel.registerCommTarget(anything(), anything())).thenReturn(); + const connectionStatusChanged = mock>(); + when(remoteKernel.connectionStatusChanged).thenReturn(instance(connectionStatusChanged)); when(sessionManager.startNew(anything(), anything())).thenCall(() => { return Promise.resolve(instance(remoteSession)); });