Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove IAsyncDisposable from KernelProcess #15661

Merged
merged 2 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/kernels/jupyter/connection/jupyterConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -127,7 +126,7 @@ export class JupyterConnection {
} finally {
connection.dispose();
if (sessionManager) {
sessionManager.dispose().catch(noop);
sessionManager.dispose();
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/kernels/jupyter/finder/remoteKernelFinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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([
Expand Down
9 changes: 7 additions & 2 deletions src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -124,7 +124,12 @@ suite(`Remote Kernel Finder`, () => {
return Promise.resolve(d.toLowerCase());
});
jupyterSessionManager = mock<JupyterLabHelper>();
when(jupyterSessionManager.dispose()).thenResolve();
const onDidDispose = new EventEmitter<void>();
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();
Expand Down
38 changes: 23 additions & 15 deletions src/kernels/jupyter/launcher/jupyterServerHelper.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IJupyterConnection>;
private disposed: boolean = false;
private _disposed = false;
private _isDisposing = false;
constructor(
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
@inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry,
Expand All @@ -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);

Expand All @@ -57,21 +59,27 @@ export class JupyterServerHelper implements IJupyterServerHelper {
this,
this.disposableRegistry
);
asyncRegistry.push(this);
asyncRegistry.push({ dispose: () => disposeAsync(this) });
}

public async dispose(): Promise<void> {
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<IJupyterConnection> {
if (this._disposed) {
if (this.isDisposed || this._isDisposing) {
throw new Error('Notebook server is disposed');
}
if (!this.cache) {
Expand Down Expand Up @@ -103,7 +111,7 @@ export class JupyterServerHelper implements IJupyterServerHelper {

public async getUsableJupyterPython(cancelToken?: CancellationToken): Promise<PythonEnvironment | undefined> {
// 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)
Expand All @@ -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);
Expand All @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions src/kernels/jupyter/session/jupyterKernelSessionFactory.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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));

Expand Down Expand Up @@ -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({
Expand Down
66 changes: 34 additions & 32 deletions src/kernels/jupyter/session/jupyterLabHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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({
Expand All @@ -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<Session.IModel[]> {
Expand Down
4 changes: 2 additions & 2 deletions src/kernels/jupyter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -42,7 +42,7 @@ export enum JupyterInterpreterDependencyResponse {
}

export const IJupyterServerHelper = Symbol('JupyterServerHelper');
export interface IJupyterServerHelper extends IAsyncDisposable {
export interface IJupyterServerHelper {
isJupyterServerSupported(cancelToken?: CancellationToken): Promise<boolean>;
startServer(resource: Resource, cancelToken?: CancellationToken): Promise<IJupyterConnection>;
getUsableJupyterPython(cancelToken?: CancellationToken): Promise<PythonEnvironment | undefined>;
Expand Down
8 changes: 4 additions & 4 deletions src/kernels/raw/finder/pythonKernelInterruptDaemon.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<void>;
};
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/kernels/raw/launcher/kernelProcess.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,9 @@ export class KernelProcess extends ObservableDisposable implements IKernelProces
}
}

public override async dispose(): Promise<void> {
public override dispose() {
if (this._disposingPromise) {
return this._disposingPromise;
return;
}
if (this.isDisposed) {
return;
Expand All @@ -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: '' });
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions src/kernels/raw/launcher/kernelProcess.node.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -516,7 +516,7 @@ suite('Kernel Process', () => {
when(settings.enablePythonKernelLogging).thenReturn(false);
const interruptDaemon = mock<PythonKernelInterruptDaemon>();
when(interruptDaemon.createInterrupter(anything(), anything())).thenResolve({
dispose: () => Promise.resolve(),
dispose: noop,
interrupt: () => Promise.resolve(),
handle: 1
});
Expand Down
Loading
Loading