diff --git a/src/kernels/jupyter/connection/jupyterConnection.ts b/src/kernels/jupyter/connection/jupyterConnection.ts index be92e35ef0b..a65d3ae930a 100644 --- a/src/kernels/jupyter/connection/jupyterConnection.ts +++ b/src/kernels/jupyter/connection/jupyterConnection.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { inject, injectable, optional } from 'inversify'; -import { noop } from '../../../platform/common/utils/misc'; import { RemoteJupyterServerUriProviderError } from '../../errors/remoteJupyterServerUriProviderError'; import { BaseError } from '../../../platform/errors/types'; import { createJupyterConnectionInfo, handleExpiredCertsError, handleSelfCertsError } from '../jupyterUtils'; @@ -127,7 +126,7 @@ export class JupyterConnection { } finally { connection.dispose(); if (sessionManager) { - sessionManager.dispose().catch(noop); + sessionManager.dispose(); } } } diff --git a/src/kernels/jupyter/finder/remoteKernelFinder.ts b/src/kernels/jupyter/finder/remoteKernelFinder.ts index a9bd0daf73f..dad319ac46b 100644 --- a/src/kernels/jupyter/finder/remoteKernelFinder.ts +++ b/src/kernels/jupyter/finder/remoteKernelFinder.ts @@ -31,6 +31,7 @@ import { IFileSystem } from '../../../platform/common/platform/types'; import { computeServerId, generateIdFromRemoteProvider } from '../jupyterUtils'; import { RemoteKernelSpecCacheFileName } from '../constants'; import { JupyterLabHelper } from '../session/jupyterLabHelper'; +import { disposeAsync } from '../../../platform/common/utils'; // Even after shutting down a kernel, the server API still returns the old information. // Re-query after 2 seconds to ensure we don't get stale information. @@ -327,7 +328,7 @@ export class RemoteKernelFinder extends ObservableDisposable implements IRemoteK const disposables: IAsyncDisposable[] = []; try { const sessionManager = JupyterLabHelper.create(connInfo.settings); - disposables.push(sessionManager); + disposables.push({ dispose: () => disposeAsync(sessionManager) }); // Get running and specs at the same time const [running, specs, sessions, serverId] = await Promise.all([ diff --git a/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts b/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts index ea23e695588..74d6644b780 100644 --- a/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts +++ b/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts @@ -8,7 +8,7 @@ import type { Session } from '@jupyterlab/services'; import { assert } from 'chai'; import { anything, instance, mock, verify, when } from 'ts-mockito'; import { getDisplayNameOrNameOfKernelConnection } from '../../helpers'; -import { Disposable, Uri } from 'vscode'; +import { Disposable, EventEmitter, Uri } from 'vscode'; import { CryptoUtils } from '../../../platform/common/crypto'; import { noop, sleep } from '../../../test/core'; import { @@ -124,7 +124,12 @@ suite(`Remote Kernel Finder`, () => { return Promise.resolve(d.toLowerCase()); }); jupyterSessionManager = mock(); - when(jupyterSessionManager.dispose()).thenResolve(); + const onDidDispose = new EventEmitter(); + disposables.push(onDidDispose); + when(jupyterSessionManager.onDidDispose).thenReturn(onDidDispose.event); + when(jupyterSessionManager.dispose()).thenCall(() => { + onDidDispose.fire(); + }); sinon.stub(JupyterLabHelper, 'create').callsFake(() => resolvableInstance(jupyterSessionManager)); when(fs.delete(anything())).thenResolve(); when(fs.createDirectory(uriEquals(globalStorageUri))).thenResolve(); diff --git a/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts b/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts index c0b3d668616..1dd57eea728 100644 --- a/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts +++ b/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts @@ -24,16 +24,17 @@ import { expandWorkingDir } from '../jupyterUtils'; import { noop } from '../../../platform/common/utils/misc'; import { getRootFolder } from '../../../platform/common/application/workspace.base'; import { computeWorkingDirectory } from '../../../platform/common/application/workspace.node'; +import { disposeAsync } from '../../../platform/common/utils'; +import { ObservableDisposable } from '../../../platform/common/utils/lifecycle'; /** * Jupyter server implementation that uses the JupyterExecutionBase class to launch Jupyter. */ @injectable() -export class JupyterServerHelper implements IJupyterServerHelper { +export class JupyterServerHelper extends ObservableDisposable implements IJupyterServerHelper { private usablePythonInterpreter: PythonEnvironment | undefined; private cache?: Promise; - private disposed: boolean = false; - private _disposed = false; + private _isDisposing = false; constructor( @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, @@ -44,6 +45,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { @optional() private readonly jupyterInterpreterService: IJupyterSubCommandExecutionService | undefined ) { + super(); this.disposableRegistry.push(this.interpreterService.onDidChangeInterpreter(() => this.onSettingsChanged())); this.disposableRegistry.push(this); @@ -57,21 +59,27 @@ export class JupyterServerHelper implements IJupyterServerHelper { this, this.disposableRegistry ); - asyncRegistry.push(this); + asyncRegistry.push({ dispose: () => disposeAsync(this) }); } - public async dispose(): Promise { - if (!this._disposed) { - this._disposed = true; - this.disposed = true; - - // Cleanup on dispose. We are going away permanently - await this.cache?.then((s) => s.dispose()).catch(noop); + public override dispose() { + if (!this._isDisposing) { + this._isDisposing = true; + + if (this.cache) { + // Cleanup on dispose. We are going away permanently + this.cache + .then((s) => s.dispose()) + .catch(noop) + .finally(() => super.dispose()); + } else { + super.dispose(); + } } } public async startServer(resource: Resource, cancelToken: CancellationToken): Promise { - if (this._disposed) { + if (this.isDisposed || this._isDisposing) { throw new Error('Notebook server is disposed'); } if (!this.cache) { @@ -103,7 +111,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { public async getUsableJupyterPython(cancelToken?: CancellationToken): Promise { // Only try to compute this once. - if (!this.usablePythonInterpreter && !this.disposed && this.jupyterInterpreterService) { + if (!this.usablePythonInterpreter && !this.isDisposed && !this._isDisposing && this.jupyterInterpreterService) { this.usablePythonInterpreter = await raceCancellationError( cancelToken, this.jupyterInterpreterService!.getSelectedInterpreter(cancelToken) @@ -121,7 +129,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { let tryCount = 1; const maxTries = Math.max(1, this.configuration.getSettings(undefined).jupyterLaunchRetries); let lastTryError: Error; - while (tryCount <= maxTries && !this.disposed) { + while (tryCount <= maxTries && !this.isDisposed && !this._isDisposing) { try { // Start or connect to the process connection = await this.startImpl(resource, cancelToken); @@ -140,7 +148,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { tryCount += 1; } else if (connection) { // If this is occurring during shutdown, don't worry about it. - if (this.disposed) { + if (this.isDisposed || this._isDisposing) { throw err; } throw err; diff --git a/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts b/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts index 49ebb062a1a..7e666593cde 100644 --- a/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts +++ b/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { CancellationError, CancellationToken, Disposable } from 'vscode'; +import { CancellationError, CancellationToken } from 'vscode'; import { Cancellation, raceCancellationError } from '../../../platform/common/cancellation'; import uuid from 'uuid/v4'; import * as urlPath from '../../../platform/vscode-path/resources'; @@ -45,6 +45,7 @@ import { getNameOfKernelConnection, jvscIdentifier } from '../../helpers'; import { waitForCondition } from '../../../platform/common/utils/async'; import { JupyterLabHelper } from './jupyterLabHelper'; import { JupyterSessionWrapper, getRemoteSessionOptions } from './jupyterSession'; +import { disposeAsync } from '../../../platform/common/utils'; @injectable() export class JupyterKernelSessionFactory implements IKernelSessionFactory { @@ -97,8 +98,8 @@ export class JupyterKernelSessionFactory implements IKernelSessionFactory { await raceCancellationError(options.token, this.validateLocalKernelDependencies(options)); const sessionManager = JupyterLabHelper.create(connection.settings); - this.asyncDisposables.push(sessionManager); - disposablesIfAnyErrors.push(new Disposable(() => sessionManager.dispose().catch(noop))); + this.asyncDisposables.push({ dispose: () => disposeAsync(sessionManager) }); + disposablesIfAnyErrors.push(sessionManager); await raceCancellationError(options.token, this.validateRemoteServer(options, sessionManager)); @@ -130,7 +131,7 @@ export class JupyterKernelSessionFactory implements IKernelSessionFactory { ); const disposed = session.disposed; const onDidDisposeSession = () => { - sessionManager.dispose().catch(noop); + sessionManager.dispose(); disposed.disconnect(onDidDisposeSession); }; this.asyncDisposables.push({ diff --git a/src/kernels/jupyter/session/jupyterLabHelper.ts b/src/kernels/jupyter/session/jupyterLabHelper.ts index 3d76e383ecb..8914ef84b42 100644 --- a/src/kernels/jupyter/session/jupyterLabHelper.ts +++ b/src/kernels/jupyter/session/jupyterLabHelper.ts @@ -20,21 +20,17 @@ import { JupyterKernelSpec } from '../jupyterKernelSpec'; import { createDeferred, raceTimeout } from '../../../platform/common/utils/async'; import { IJupyterKernel } from '../types'; import { sendTelemetryEvent, Telemetry } from '../../../telemetry'; -import { dispose } from '../../../platform/common/utils/lifecycle'; +import { ObservableDisposable, dispose } from '../../../platform/common/utils/lifecycle'; import { StopWatch } from '../../../platform/common/utils/stopWatch'; import type { ISpecModel } from '@jupyterlab/services/lib/kernelspec/kernelspec'; import { noop } from '../../../platform/common/utils/misc'; -export class JupyterLabHelper { +export class JupyterLabHelper extends ObservableDisposable { public sessionManager: SessionManager; public kernelSpecManager: KernelSpecManager; public kernelManager: KernelManager; public contentsManager: ContentsManager; private _jupyterlab?: typeof import('@jupyterlab/services'); - private disposed?: boolean; - public get isDisposed() { - return this.disposed === true; - } private get jupyterlab(): typeof import('@jupyterlab/services') { if (!this._jupyterlab) { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -43,6 +39,7 @@ export class JupyterLabHelper { return this._jupyterlab!; } private constructor(private readonly serverSettings: ServerConnection.ISettings) { + super(); this.kernelSpecManager = new this.jupyterlab.KernelSpecManager({ serverSettings: this.serverSettings }); this.kernelManager = new this.jupyterlab.KernelManager({ serverSettings: this.serverSettings }); this.sessionManager = new this.jupyterlab.SessionManager({ @@ -55,36 +52,41 @@ export class JupyterLabHelper { return new JupyterLabHelper(serverSettings); } - public async dispose() { - if (this.disposed) { + private _isDisposing = false; + public override dispose() { + if (this.isDisposed || this._isDisposing) { return; } - this.disposed = true; - logger.trace(`Disposing Jupyter Lab Helper`); - try { - if (this.contentsManager) { - logger.trace('SessionManager - dispose contents manager'); - this.contentsManager.dispose(); - } - if (this.sessionManager && !this.sessionManager.isDisposed) { - logger.trace('ShutdownSessionAndConnection - dispose session manager'); - // Make sure it finishes startup. - await raceTimeout(10_000, this.sessionManager.ready); + this._isDisposing = true; + (async () => { + logger.trace(`Disposing Jupyter Lab Helper`); + try { + if (this.contentsManager) { + logger.trace('SessionManager - dispose contents manager'); + this.contentsManager.dispose(); + } + if (this.sessionManager && !this.sessionManager.isDisposed) { + logger.trace('ShutdownSessionAndConnection - dispose session manager'); + // Make sure it finishes startup. + await raceTimeout(10_000, this.sessionManager.ready); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.sessionManager.dispose(); // Note, shutting down all will kill all kernels on the same connection. We don't want that. - } - if (!this.kernelManager?.isDisposed) { - this.kernelManager?.dispose(); - } - if (!this.kernelSpecManager?.isDisposed) { - this.kernelSpecManager?.dispose(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.sessionManager.dispose(); // Note, shutting down all will kill all kernels on the same connection. We don't want that. + } + if (!this.kernelManager?.isDisposed) { + this.kernelManager?.dispose(); + } + if (!this.kernelSpecManager?.isDisposed) { + this.kernelSpecManager?.dispose(); + } + } catch (e) { + logger.error(`Exception on Jupyter Lab Helper shutdown: `, e); + } finally { + logger.trace('Finished disposing Jupyter Lab Helper'); } - } catch (e) { - logger.error(`Exception on Jupyter Lab Helper shutdown: `, e); - } finally { - logger.trace('Finished disposing Jupyter Lab Helper'); - } + })() + .catch(noop) + .finally(() => super.dispose()); } public async getRunningSessions(): Promise { diff --git a/src/kernels/jupyter/types.ts b/src/kernels/jupyter/types.ts index 9701bb06b1c..ea2413a26e0 100644 --- a/src/kernels/jupyter/types.ts +++ b/src/kernels/jupyter/types.ts @@ -8,7 +8,7 @@ import type WebSocketIsomorphic from 'isomorphic-ws'; import { CancellationToken, Disposable, Event, type NotebookCellData } from 'vscode'; import { SemVer } from 'semver'; import { Uri } from 'vscode'; -import { IAsyncDisposable, IDisplayOptions, IDisposable, Resource } from '../../platform/common/types'; +import { IDisplayOptions, IDisposable, Resource } from '../../platform/common/types'; import { JupyterInstallError } from '../../platform/errors/jupyterInstallError'; import { PythonEnvironment } from '../../platform/pythonEnvironments/info'; import { @@ -42,7 +42,7 @@ export enum JupyterInterpreterDependencyResponse { } export const IJupyterServerHelper = Symbol('JupyterServerHelper'); -export interface IJupyterServerHelper extends IAsyncDisposable { +export interface IJupyterServerHelper { isJupyterServerSupported(cancelToken?: CancellationToken): Promise; startServer(resource: Resource, cancelToken?: CancellationToken): Promise; getUsableJupyterPython(cancelToken?: CancellationToken): Promise; diff --git a/src/kernels/raw/finder/pythonKernelInterruptDaemon.node.ts b/src/kernels/raw/finder/pythonKernelInterruptDaemon.node.ts index f09335b0361..0f3b7dc6b9d 100644 --- a/src/kernels/raw/finder/pythonKernelInterruptDaemon.node.ts +++ b/src/kernels/raw/finder/pythonKernelInterruptDaemon.node.ts @@ -5,7 +5,7 @@ import { logger } from '../../../platform/logging'; import { ObservableExecutionResult } from '../../../platform/common/process/types.node'; import { EnvironmentType, PythonEnvironment } from '../../../platform/pythonEnvironments/info'; import { inject, injectable } from 'inversify'; -import { IAsyncDisposable, IDisposableRegistry, IExtensionContext, Resource } from '../../../platform/common/types'; +import { IDisposableRegistry, IExtensionContext, Resource, type IDisposable } from '../../../platform/common/types'; import { createDeferred, Deferred } from '../../../platform/common/utils/async'; import { Disposable, Uri } from 'vscode'; import { EOL } from 'os'; @@ -57,7 +57,7 @@ type Command = | { command: 'INITIALIZE_INTERRUPT' } | { command: 'INTERRUPT'; handle: InterruptHandle } | { command: 'DISPOSE_INTERRUPT_HANDLE'; handle: InterruptHandle }; -export type Interrupter = IAsyncDisposable & { +export type Interrupter = IDisposable & { handle: InterruptHandle; interrupt: () => Promise; }; @@ -103,8 +103,8 @@ export class PythonKernelInterruptDaemon { interrupt: async () => { await this.sendCommand({ command: 'INTERRUPT', handle: interruptHandle }, pythonEnvironment, resource); }, - dispose: async () => { - await this.sendCommand( + dispose: () => { + void this.sendCommand( { command: 'DISPOSE_INTERRUPT_HANDLE', handle: interruptHandle }, pythonEnvironment, resource diff --git a/src/kernels/raw/launcher/kernelProcess.node.ts b/src/kernels/raw/launcher/kernelProcess.node.ts index 72189b86979..c4706f56163 100644 --- a/src/kernels/raw/launcher/kernelProcess.node.ts +++ b/src/kernels/raw/launcher/kernelProcess.node.ts @@ -332,9 +332,9 @@ export class KernelProcess extends ObservableDisposable implements IKernelProces } } - public override async dispose(): Promise { + public override dispose() { if (this._disposingPromise) { - return this._disposingPromise; + return; } if (this.isDisposed) { return; @@ -347,7 +347,7 @@ export class KernelProcess extends ObservableDisposable implements IKernelProces this.killChildProcesses(this._process?.pid).catch(noop) ); try { - this.interrupter?.dispose().catch(noop); + this.interrupter?.dispose(); this._process?.kill(); // NOSONAR if (!this.exitEventFired) { this.exitEvent.fire({ stderr: '' }); @@ -364,7 +364,7 @@ export class KernelProcess extends ObservableDisposable implements IKernelProces } logger.debug(`Disposed Kernel process ${pid}.`); })(); - super.dispose(); + void this._disposingPromise.finally(() => super.dispose()).catch(noop); } private async killChildProcesses(pid?: number) { diff --git a/src/kernels/raw/launcher/kernelProcess.node.unit.test.ts b/src/kernels/raw/launcher/kernelProcess.node.unit.test.ts index 0b00daa908f..c332e584d78 100644 --- a/src/kernels/raw/launcher/kernelProcess.node.unit.test.ts +++ b/src/kernels/raw/launcher/kernelProcess.node.unit.test.ts @@ -137,7 +137,7 @@ suite('kernel Process', () => { }); const interrupter = { handle: 1, - dispose: () => Promise.resolve(), + dispose: noop, interrupt: () => Promise.resolve() }; when(daemon.createInterrupter(anything(), anything())).thenResolve(interrupter); @@ -516,7 +516,7 @@ suite('Kernel Process', () => { when(settings.enablePythonKernelLogging).thenReturn(false); const interruptDaemon = mock(); when(interruptDaemon.createInterrupter(anything(), anything())).thenResolve({ - dispose: () => Promise.resolve(), + dispose: noop, interrupt: () => Promise.resolve(), handle: 1 }); diff --git a/src/kernels/raw/session/rawKernelConnection.node.ts b/src/kernels/raw/session/rawKernelConnection.node.ts index 4189cccc225..ec25538bc40 100644 --- a/src/kernels/raw/session/rawKernelConnection.node.ts +++ b/src/kernels/raw/session/rawKernelConnection.node.ts @@ -38,6 +38,7 @@ import { KernelSocketMap } from '../../kernelSocket'; import { getNotebookTelemetryTracker } from '../../telemetry/notebookTelemetry'; import { KernelProcessExitedError } from '../../errors/kernelProcessExitedError'; import { once } from '../../../platform/common/utils/functional'; +import { disposeAsync } from '../../../platform/common/utils'; let nonSerializingKernel: typeof import('@jupyterlab/services/lib/kernel/default'); @@ -136,7 +137,7 @@ export class RawKernelConnection implements Kernel.IKernelConnection { try { const oldKernelProcess = this.kernelProcess; this.kernelProcess = undefined; - oldKernelProcess?.dispose()?.catch(noop); + oldKernelProcess?.dispose(); swallowExceptions(() => this.socket?.dispose()); swallowExceptions(() => this.realKernel?.dispose()); // Try to start up our raw session, allow for cancellation or timeout @@ -205,7 +206,7 @@ export class RawKernelConnection implements Kernel.IKernelConnection { this.statusChanged.emit(this.status); } catch (error) { await Promise.all([ - this.kernelProcess?.dispose().catch(noop), + this.kernelProcess ? disposeAsync(this.kernelProcess) : Promise.resolve(), this.realKernel ?.shutdown() .catch((ex) => logger.warn(`Failed to shutdown kernel, ${this.kernelConnectionMetadata.id}`, ex)) @@ -283,7 +284,7 @@ export class RawKernelConnection implements Kernel.IKernelConnection { this.restartToken?.cancel(); this.restartToken?.dispose(); suppressShutdownErrors(this.realKernel); - await this.kernelProcess?.dispose().catch(noop); + await (this.kernelProcess ? disposeAsync(this.kernelProcess).catch(noop) : Promise.resolve()); this.socket.dispose(); this.stopHandlingKernelMessages(); this.isShuttingDown = false; diff --git a/src/kernels/raw/session/rawSessionConnection.node.unit.test.ts b/src/kernels/raw/session/rawSessionConnection.node.unit.test.ts index 476d8d82a64..30f0dd72391 100644 --- a/src/kernels/raw/session/rawSessionConnection.node.unit.test.ts +++ b/src/kernels/raw/session/rawSessionConnection.node.unit.test.ts @@ -47,6 +47,7 @@ suite('Raw Session & Raw Kernel Connection', () => { reason?: string | undefined; stderr: string; }>; + let onDidDispose: EventEmitter; const launchTimeout = 1_000; let disposables: IDisposable[] = []; let kernelConnectionMetadata: LocalKernelSpecConnectionMetadata; @@ -106,6 +107,7 @@ suite('Raw Session & Raw Kernel Connection', () => { }; function createKernelProcess() { const kernelProcess = mock(); + let disposed = false; when(kernelProcess.canInterrupt).thenReturn(true); when(kernelProcess.connection).thenReturn({ control_port: 1, @@ -118,8 +120,13 @@ suite('Raw Session & Raw Kernel Connection', () => { stdin_port: 5, transport: 'tcp' }); - when(kernelProcess.dispose()).thenResolve(); + when(kernelProcess.isDisposed).thenCall(() => disposed); + when(kernelProcess.dispose()).thenCall(() => { + disposed = true; + onDidDispose.fire(); + }); when(kernelProcess.exited).thenReturn(exitedEvent.event); + when(kernelProcess.onDidDispose).thenReturn(onDidDispose.event); when(kernelProcess.canInterrupt).thenReturn(true); when(kernelProcess.interrupt()).thenResolve(); when(kernelProcess.kernelConnectionMetadata).thenReturn(kernelConnectionMetadata); @@ -181,6 +188,9 @@ suite('Raw Session & Raw Kernel Connection', () => { reason?: string | undefined; stderr: string; }>(); + disposables.push(exitedEvent); + onDidDispose = new EventEmitter(); + disposables.push(onDidDispose); nonSerializingKernel.KernelConnection = OldKernelConnectionClass; const workspaceConfig = mock(); when(workspaceConfig.get(anything(), anything())).thenCall((_, defaultValue) => defaultValue); diff --git a/src/kernels/raw/types.ts b/src/kernels/raw/types.ts index 559a3d6ae6d..d9f2f2b47f0 100644 --- a/src/kernels/raw/types.ts +++ b/src/kernels/raw/types.ts @@ -2,13 +2,14 @@ // Licensed under the MIT License. import { CancellationToken, Event } from 'vscode'; -import { IAsyncDisposable, Resource } from '../../platform/common/types'; +import { Resource } from '../../platform/common/types'; import { IRawKernelSession, LocaLKernelSessionCreationOptions, LocalKernelSpecConnectionMetadata, PythonKernelConnectionMetadata } from '../types'; +import type { ObservableDisposable } from '../../platform/common/utils/lifecycle'; export const IKernelLauncher = Symbol('IKernelLauncher'); export interface IKernelLauncher { @@ -34,7 +35,7 @@ export interface IKernelConnection { kernel_name?: string; } -export interface IKernelProcess extends IAsyncDisposable { +export interface IKernelProcess extends ObservableDisposable { readonly pid?: number; readonly connection: Readonly; readonly kernelConnectionMetadata: Readonly; diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index ca493ad56fb..82fc38dd78e 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -103,6 +103,7 @@ import { NotebookCellExecutionState, notebookCellExecutions } from '../../../platform/notebooks/cellExecutionStateService'; +import { disposeAsync } from '../../../platform/common/utils'; // Running in Conda environments, things can be a little slower. export const defaultNotebookTestTimeout = 60_000; @@ -343,7 +344,9 @@ async function shutdownRemoteKernels() { // ignore } finally { cancelToken.dispose(); - await sessionManager?.dispose().catch(noop); + if (sessionManager) { + await disposeAsync(sessionManager); + } } } export const MockNotebookDocuments: NotebookDocument[] = [];