diff --git a/src/extension.node.ts b/src/extension.node.ts index 823e092bc9e..ae5630af947 100644 --- a/src/extension.node.ts +++ b/src/extension.node.ts @@ -95,7 +95,6 @@ import { IInterpreterPackages } from './platform/interpreter/types'; import { homedir, platform, arch, userInfo } from 'os'; import { getUserHomeDir } from './platform/common/utils/platform.node'; import { homePath } from './platform/common/platform/fs-paths.node'; -import { removeOldCachedItems } from './platform/common/cache'; durations.codeLoadingTime = stopWatch.elapsedTime; @@ -111,7 +110,6 @@ let activatedServiceContainer: IServiceContainer | undefined; export async function activate(context: IExtensionContext): Promise { context.subscriptions.push({ dispose: () => (Exiting.isExiting = true) }); try { - removeOldCachedItems(context.globalState).then(noop, noop); let api: IExtensionApi; let ready: Promise; let serviceContainer: IServiceContainer; diff --git a/src/extension.web.ts b/src/extension.web.ts index 8b59a5b5b2f..b1343d6a781 100644 --- a/src/extension.web.ts +++ b/src/extension.web.ts @@ -100,7 +100,6 @@ import { ServiceManager } from './platform/ioc/serviceManager'; import { OutputChannelLogger } from './platform/logging/outputChannelLogger'; import { ConsoleLogger } from './platform/logging/consoleLogger'; import { initializeGlobals as initializeTelemetryGlobals } from './platform/telemetry/telemetry'; -import { removeOldCachedItems } from './platform/common/cache'; durations.codeLoadingTime = stopWatch.elapsedTime; @@ -116,7 +115,6 @@ let activatedServiceContainer: IServiceContainer | undefined; export async function activate(context: IExtensionContext): Promise { context.subscriptions.push({ dispose: () => (Exiting.isExiting = true) }); try { - removeOldCachedItems(context.globalState).then(noop, noop); let api: IExtensionApi; let ready: Promise; let serviceContainer: IServiceContainer; diff --git a/src/kernels/errors/kernelErrorHandler.ts b/src/kernels/errors/kernelErrorHandler.ts index 47f74ed456f..ed300e35e31 100644 --- a/src/kernels/errors/kernelErrorHandler.ts +++ b/src/kernels/errors/kernelErrorHandler.ts @@ -20,7 +20,7 @@ import { } from '../../platform/common/types'; import { DataScience, Common } from '../../platform/common/utils/localize'; import { sendTelemetryEvent, Telemetry } from '../../telemetry'; -import { Commands, Identifiers } from '../../platform/common/constants'; +import { Commands } from '../../platform/common/constants'; import { getDisplayNameOrNameOfKernelConnection } from '../helpers'; import { translateProductToModule } from '../../platform/interpreter/installer/utils'; import { ProductNames } from '../../platform/interpreter/installer/productNames'; @@ -47,15 +47,12 @@ import { KernelDeadError } from './kernelDeadError'; import { DisplayOptions } from '../displayOptions'; import { IJupyterInterpreterDependencyManager, + IJupyterServerUriEntry, IJupyterServerUriStorage, IJupyterUriProviderRegistration, JupyterInterpreterDependencyResponse } from '../jupyter/types'; -import { - extractJupyterServerHandleAndId, - handleExpiredCertsError, - handleSelfCertsError -} from '../jupyter/jupyterUtils'; +import { handleExpiredCertsError, handleSelfCertsError } from '../jupyter/jupyterUtils'; import { getDisplayPath, getFilePath } from '../../platform/common/platform/fs-paths'; import { isCancellationError } from '../../platform/common/cancellation'; import { JupyterExpiredCertsError } from '../../platform/errors/jupyterExpiredCertsError'; @@ -237,10 +234,9 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle error: RemoteJupyterServerUriProviderError, errorContext?: KernelAction ) { - const savedList = await this.serverUriStorage.getMRUs(); + const server = await this.serverUriStorage.get(error.serverId); const message = error.originalError?.message || error.message; - const serverId = error.serverId; - const displayName = savedList.find((item) => item.serverId === serverId)?.displayName; + const displayName = server?.displayName; const idAndHandle = `${error.providerId}:${error.handle}`; const serverName = displayName || idAndHandle; @@ -253,59 +249,37 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle error: RemoteJupyterServerConnectionError, errorContext?: KernelAction ) { - const savedList = await this.serverUriStorage.getMRUs(); + const info = await this.serverUriStorage.get(error.serverId); const message = error.originalError.message || ''; - const serverId = error.serverId; - const displayName = savedList.find((item) => item.serverId === serverId)?.displayName; - - if (error.baseUrl === Identifiers.REMOTE_URI) { - // 3rd party server uri error - const idAndHandle = extractJupyterServerHandleAndId(error.url); - if (idAndHandle) { - const serverUri = await this.jupyterUriProviderRegistration.getJupyterServerUri( - idAndHandle.id, - idAndHandle.handle - ); - const serverName = - serverUri?.displayName ?? this.generateJupyterIdAndHandleErrorName(error.url) ?? error.url; - return getUserFriendlyErrorMessage( - DataScience.remoteJupyterConnectionFailedWithServerWithError(serverName, message), - errorContext - ); - } + if (info?.provider) { + const serverName = info?.displayName ?? error.url; + return getUserFriendlyErrorMessage( + DataScience.remoteJupyterConnectionFailedWithServerWithError(serverName, message), + errorContext + ); } const baseUrl = error.baseUrl; - const serverName = displayName && baseUrl ? `${displayName} (${baseUrl})` : displayName || baseUrl; + const serverName = + info?.displayName && baseUrl ? `${info.displayName} (${baseUrl})` : info?.displayName || baseUrl; return getUserFriendlyErrorMessage( DataScience.remoteJupyterConnectionFailedWithServerWithError(serverName, message), errorContext ); } - private generateJupyterIdAndHandleErrorName(url: string): string | undefined { - const idAndHandle = extractJupyterServerHandleAndId(url); - - return idAndHandle ? `${idAndHandle.id}:${idAndHandle.handle}` : undefined; - } - private async handleJupyterServerProviderConnectionError(uri: string) { - const idAndHandle = extractJupyterServerHandleAndId(uri); - - if (!idAndHandle) { - return false; - } - - const provider = await this.jupyterUriProviderRegistration.getProvider(idAndHandle.id); - if (!provider || !provider.getHandles) { + private async handleJupyterServerProviderConnectionError(info: IJupyterServerUriEntry) { + const provider = info.provider && (await this.jupyterUriProviderRegistration.getProvider(info.serverId)); + if (!info.provider || !provider || !provider.getHandles) { return false; } try { const handles = await provider.getHandles(); - if (!handles.includes(idAndHandle.handle)) { - await this.serverUriStorage.removeMRU(uri); + if (!handles.includes(info.provider.handle)) { + await this.serverUriStorage.remove(info.uri); } return true; } catch (_ex) { @@ -366,7 +340,6 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle err instanceof InvalidRemoteJupyterServerUriHandleError ) { this.sendKernelTelemetry(err, errorContext, resource, err.category); - const savedList = await this.serverUriStorage.getMRUs(); const message = err instanceof InvalidRemoteJupyterServerUriHandleError ? '' @@ -374,7 +347,7 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle ? err.originalError.message || '' : err.originalError?.message || err.message; const serverId = err instanceof RemoteJupyterServerConnectionError ? err.serverId : err.serverId; - const server = savedList.find((item) => item.serverId === serverId); + const server = await this.serverUriStorage.get(serverId); const displayName = server?.displayName; const baseUrl = err instanceof RemoteJupyterServerConnectionError ? err.baseUrl : ''; const idAndHandle = @@ -386,7 +359,7 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle ? this.extensions.getExtension(err.extensionId)?.packageJSON.displayName || err.extensionId : ''; const options = actionSource === 'jupyterExtension' ? [DataScience.selectDifferentKernel] : []; - if (server && (await this.handleJupyterServerProviderConnectionError(server.uri))) { + if (server && (await this.handleJupyterServerProviderConnectionError(server))) { return KernelInterpreterDependencyResponse.selectDifferentKernel; } @@ -401,13 +374,10 @@ export abstract class DataScienceErrorHandler implements IDataScienceErrorHandle ); switch (selection) { case DataScience.removeRemoteJupyterConnectionButtonText: { - // Start with saved list. - const uriList = await this.serverUriStorage.getMRUs(); - // Remove this uri if already found (going to add again with a new time) - const item = uriList.find((f) => f.serverId === serverId); + const item = await this.serverUriStorage.get(serverId); if (item) { - await this.serverUriStorage.removeMRU(item.uri); + await this.serverUriStorage.remove(item.uri); } // Wait until all of the remote controllers associated with this server have been removed. return KernelInterpreterDependencyResponse.cancel; diff --git a/src/kernels/errors/kernelErrorHandler.unit.test.ts b/src/kernels/errors/kernelErrorHandler.unit.test.ts index a0f4c77ca0e..53945bc3ea6 100644 --- a/src/kernels/errors/kernelErrorHandler.unit.test.ts +++ b/src/kernels/errors/kernelErrorHandler.unit.test.ts @@ -794,7 +794,6 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d }, serverId }); - when(uriStorage.getMRUs()).thenResolve([]); when( applicationShell.showErrorMessage(anything(), anything(), anything(), anything(), anything()) ).thenResolve(); @@ -816,7 +815,7 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d DataScience.selectDifferentKernel ) ).once(); - verify(uriStorage.removeMRU(uri)).never(); + verify(uriStorage.remove(uri)).never(); }); test('Display error when connection to remote jupyter server fails due to 3rd party extension', async () => { const uri = generateUriFromRemoteProvider('1', 'a'); @@ -833,7 +832,7 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d }, serverId }); - when(uriStorage.getMRUs()).thenResolve([{ time: 1, uri, serverId, displayName: 'Hello Server' }]); + when(uriStorage.get(serverId)).thenResolve({ time: 1, uri, serverId, displayName: 'Hello Server' }); when( applicationShell.showErrorMessage(anything(), anything(), anything(), anything(), anything()) ).thenResolve(); @@ -855,7 +854,8 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d DataScience.selectDifferentKernel ) ).once(); - verify(uriStorage.removeMRU(uri)).never(); + verify(uriStorage.remove(uri)).never(); + verify(uriStorage.get(serverId)).atLeast(1); }); test('Remove remote Uri if user choses to do so, when connection to remote jupyter server fails', async () => { const uri = 'http://hello:1234/jupyter'; @@ -875,11 +875,8 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d when( applicationShell.showErrorMessage(anything(), anything(), anything(), anything(), anything()) ).thenResolve(DataScience.removeRemoteJupyterConnectionButtonText as any); - when(uriStorage.removeMRU(anything())).thenResolve(); - when(uriStorage.getMRUs()).thenResolve([ - { time: 1, serverId: 'foobar', uri: 'one' }, - { uri, serverId, time: 2 } - ]); + when(uriStorage.remove(anything())).thenResolve(); + when(uriStorage.get(serverId)).thenResolve({ uri, serverId, time: 2 }); const result = await dataScienceErrorHandler.handleKernelError( error, 'start', @@ -888,8 +885,8 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d 'jupyterExtension' ); assert.strictEqual(result, KernelInterpreterDependencyResponse.cancel); - verify(uriStorage.removeMRU(uri)).once(); - verify(uriStorage.getMRUs()).atLeast(1); + verify(uriStorage.remove(uri)).once(); + verify(uriStorage.get(serverId)).atLeast(1); }); test('Change remote Uri if user choses to do so, when connection to remote jupyter server fails', async () => { const uri = 'http://hello:1234/jupyter'; @@ -906,7 +903,6 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d }, serverId }); - when(uriStorage.getMRUs()).thenResolve([]); when( applicationShell.showErrorMessage(anything(), anything(), anything(), anything(), anything()) ).thenResolve(DataScience.changeRemoteJupyterConnectionButtonText as any); @@ -919,7 +915,7 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d 'jupyterExtension' ); assert.strictEqual(result, KernelInterpreterDependencyResponse.cancel); - verify(uriStorage.removeMRU(uri)).never(); + verify(uriStorage.remove(uri)).never(); }); test('Select different kernel user choses to do so, when connection to remote jupyter server fails', async () => { const uri = 'http://hello:1234/jupyter'; @@ -936,7 +932,6 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d }, serverId }); - when(uriStorage.getMRUs()).thenResolve([]); when( applicationShell.showErrorMessage(anything(), anything(), anything(), anything(), anything()) ).thenResolve(DataScience.selectDifferentKernel as any); @@ -948,7 +943,7 @@ Failed to run jupyter as observable with args notebook --no-browser --notebook-d 'jupyterExtension' ); assert.strictEqual(result, KernelInterpreterDependencyResponse.selectDifferentKernel); - verify(uriStorage.removeMRU(uri)).never(); + verify(uriStorage.remove(uri)).never(); }); function verifyErrorMessage(message: string, linkInfo?: string) { message = message.includes('command:jupyter.viewOutput') diff --git a/src/kernels/jupyter/connection/jupyterConnection.ts b/src/kernels/jupyter/connection/jupyterConnection.ts index 0a35dd268c0..19bb232dd6a 100644 --- a/src/kernels/jupyter/connection/jupyterConnection.ts +++ b/src/kernels/jupyter/connection/jupyterConnection.ts @@ -45,7 +45,7 @@ export class JupyterConnection { private async getUriFromServerId(serverId: string) { // Since there's one server per session, don't use a resource to figure out these settings - const savedList = await this.serverUriStorage.getMRUs(); + const savedList = await this.serverUriStorage.getAll(); return savedList.find((item) => item.serverId === serverId)?.uri; } private async createConnectionInfoFromUri(uri: string) { diff --git a/src/kernels/jupyter/connection/jupyterConnection.unit.test.ts b/src/kernels/jupyter/connection/jupyterConnection.unit.test.ts index f73a1aa432d..c8cddd09d6e 100644 --- a/src/kernels/jupyter/connection/jupyterConnection.unit.test.ts +++ b/src/kernels/jupyter/connection/jupyterConnection.unit.test.ts @@ -191,19 +191,6 @@ suite('JupyterConnection', () => { assert.equal(connection.hostName, expectedServerInfo.hostname); assert.equal(connection.token, expectedServerInfo.token); }); - test('Disconnect event is fired in connection', async () => { - (dsSettings).jupyterLaunchTimeout = 10_000; - const waiter = createConnectionWaiter(); - observableOutput.next({ source: 'stderr', out: 'Jupyter listening on http://123.123.123:8888' }); - let disconnected = false; - - const connection = await waiter.waitForConnection(); - connection.disconnected(() => (disconnected = true)); - - childProc.emit('exit', 999); - - assert.isTrue(disconnected); - }); test('Throw timeout error', async () => { (dsSettings).jupyterLaunchTimeout = 10; const waiter = createConnectionWaiter(); diff --git a/src/kernels/jupyter/connection/jupyterRemoteCachedKernelValidator.ts b/src/kernels/jupyter/connection/jupyterRemoteCachedKernelValidator.ts index f79dabc1977..ec445ac60ca 100644 --- a/src/kernels/jupyter/connection/jupyterRemoteCachedKernelValidator.ts +++ b/src/kernels/jupyter/connection/jupyterRemoteCachedKernelValidator.ts @@ -4,7 +4,6 @@ import { inject, injectable } from 'inversify'; import { traceWarning } from '../../../platform/logging'; import { LiveRemoteKernelConnectionMetadata } from '../../types'; -import { extractJupyterServerHandleAndId } from '../jupyterUtils'; import { IJupyterRemoteCachedKernelValidator, IJupyterServerUriStorage, @@ -29,29 +28,27 @@ export class JupyterRemoteCachedKernelValidator implements IJupyterRemoteCachedK if (!this.liveKernelConnectionTracker.wasKernelUsed(kernel)) { return false; } - const providersPromise = this.providerRegistration.getProviders(); - const currentList = await this.uriStorage.getMRUs(); - const item = currentList.find((item) => item.serverId === kernel.serverId); + const item = await this.uriStorage.get(kernel.serverId); if (!item) { // Server has been removed and we have some old cached data. return false; } // Check if this has a provider associated with it. - const info = extractJupyterServerHandleAndId(item.uri); - if (!info) { + if (!item.provider) { // Could be a regular remote Jupyter Uri entered by the user. // As its in the list, its still valid. return true; } - const providers = await providersPromise; - const provider = providers.find((item) => item.id === info.id); + const provider = await this.providerRegistration.getProvider(item.provider.id); if (!provider) { - traceWarning(`Extension may have been uninstalled for provider ${info.id}, handle ${info.handle}`); + traceWarning( + `Extension may have been uninstalled for provider ${item.provider.id}, handle ${item.provider.handle}` + ); return false; } if (provider.getHandles) { const handles = await provider.getHandles(); - if (handles.includes(info.handle)) { + if (handles.includes(item.provider.handle)) { return true; } else { traceWarning( diff --git a/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts b/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts index 0e2e78b6558..c9dad4874a6 100644 --- a/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts +++ b/src/kernels/jupyter/connection/jupyterUriProviderRegistration.ts @@ -2,8 +2,8 @@ // Licensed under the MIT License. import { inject, injectable, named } from 'inversify'; -import { EventEmitter, Memento } from 'vscode'; -import { JVSC_EXTENSION_ID } from '../../../platform/common/constants'; +import { Event, EventEmitter, Memento, QuickPickItem } from 'vscode'; +import { JVSC_EXTENSION_ID, Telemetry } from '../../../platform/common/constants'; import { GLOBAL_MEMENTO, @@ -16,7 +16,6 @@ import { swallowExceptions } from '../../../platform/common/utils/decorators'; import * as localize from '../../../platform/common/utils/localize'; import { noop } from '../../../platform/common/utils/misc'; import { InvalidRemoteJupyterServerUriHandleError } from '../../errors/invalidRemoteJupyterServerUriHandleError'; -import { JupyterUriProviderWrapper } from './jupyterUriProviderWrapper'; import { computeServerId, generateUriFromRemoteProvider } from '../jupyterUtils'; import { IJupyterServerUri, @@ -24,6 +23,8 @@ import { IJupyterUriProviderRegistration, JupyterServerUriHandle } from '../types'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { traceError } from '../../../platform/logging'; const REGISTRATION_ID_EXTENSION_OWNER_MEMENTO_KEY = 'REGISTRATION_ID_EXTENSION_OWNER_MEMENTO_KEY'; @@ -47,16 +48,16 @@ export class JupyterUriProviderRegistration implements IJupyterUriProviderRegist } public async getProviders(): Promise> { - await this.checkOtherExtensions(); + await this.loadOtherExtensions(); // Other extensions should have registered in their activate callback return Promise.all([...this.providers.values()].map((p) => p[0])); } public async getProvider(id: string): Promise { - await this.checkOtherExtensions(); - - return this.providers.get(id)?.[0]; + await this.loadOtherExtensions(); + const value = this.providers.get(id); + return value ? value[0] : undefined; } public registerProvider(provider: IJupyterUriProvider) { @@ -77,32 +78,33 @@ export class JupyterUriProviderRegistration implements IJupyterUriProviderRegist }; } public async getJupyterServerUri(id: string, handle: JupyterServerUriHandle): Promise { - await this.checkOtherExtensions(); + await this.loadOtherExtensions(); const providerPromise = this.providers.get(id)?.[0]; - if (providerPromise) { - const provider = await providerPromise; - if (provider.getHandles) { - const handles = await provider.getHandles(); - if (!handles.includes(handle)) { - const extensionId = this.providerExtensionMapping.get(id)!; - const serverId = await computeServerId(generateUriFromRemoteProvider(id, handle)); - throw new InvalidRemoteJupyterServerUriHandleError(id, handle, extensionId, serverId); - } + if (!providerPromise) { + traceError(`${localize.DataScience.unknownServerUri}. Provider Id=${id} and handle=${handle}`); + throw new Error(localize.DataScience.unknownServerUri); + } + const provider = await providerPromise; + if (provider.getHandles) { + const handles = await provider.getHandles(); + if (!handles.includes(handle)) { + const extensionId = this.providerExtensionMapping.get(id)!; + const serverId = await computeServerId(generateUriFromRemoteProvider(id, handle)); + throw new InvalidRemoteJupyterServerUriHandleError(id, handle, extensionId, serverId); } - return provider.getServerUri(handle); } - throw new Error(localize.DataScience.unknownServerUri); + return provider.getServerUri(handle); } - private checkOtherExtensions(): Promise { + private loadOtherExtensions(): Promise { if (!this.loadedOtherExtensionsPromise) { - this.loadedOtherExtensionsPromise = this.loadOtherExtensions(); + this.loadedOtherExtensionsPromise = this.loadOtherExtensionsImpl(); } return this.loadedOtherExtensionsPromise; } - private async loadOtherExtensions(): Promise { + private async loadOtherExtensionsImpl(): Promise { this.loadExistingProviderExtensionMapping(); const extensionIds = new Set(); this.globalMemento @@ -123,6 +125,7 @@ export class JupyterUriProviderRegistration implements IJupyterUriProviderRegist ? JVSC_EXTENSION_ID : (await this.extensions.determineExtensionFromCallStack()).extensionId; this.updateRegistrationInfo(provider.id, extensionId).catch(noop); + // eslint-disable-next-line @typescript-eslint/no-use-before-define return new JupyterUriProviderWrapper(provider, extensionId, localDisposables); } @swallowExceptions() @@ -144,3 +147,97 @@ export class JupyterUriProviderRegistration implements IJupyterUriProviderRegist registeredList.forEach((item) => this.providerExtensionMapping.set(item.providerId, item.extensionId)); } } + +const handlesForWhichWeHaveSentTelemetry = new Set(); +/** + * This class wraps an IJupyterUriProvider provided by another extension. It allows us to show + * extra data on the other extension's UI. + */ +class JupyterUriProviderWrapper implements IJupyterUriProvider { + public readonly id: string; + public readonly displayName: string | undefined; + public readonly detail: string | undefined; + + public readonly onDidChangeHandles?: Event; + public readonly getHandles?: () => Promise; + public readonly removeHandle?: (handle: JupyterServerUriHandle) => Promise; + + constructor( + private readonly provider: IJupyterUriProvider, + private extensionId: string, + disposables: IDisposableRegistry + ) { + this.id = this.provider.id; + this.displayName = this.provider.displayName; + this.detail = this.provider.detail; + + if (provider.onDidChangeHandles) { + const _onDidChangeHandles = new EventEmitter(); + this.onDidChangeHandles = _onDidChangeHandles.event.bind(this); + + disposables.push(_onDidChangeHandles); + disposables.push(provider.onDidChangeHandles(() => _onDidChangeHandles.fire())); + } + + if (provider.getHandles) { + this.getHandles = async () => provider.getHandles!(); + } + + if (provider.removeHandle) { + this.removeHandle = (handle: JupyterServerUriHandle) => provider.removeHandle!(handle); + } + } + public async getQuickPickEntryItems(): Promise { + if (!this.provider.getQuickPickEntryItems) { + return []; + } + return (await this.provider.getQuickPickEntryItems()).map((q) => { + return { + ...q, + // Add the package name onto the description + description: localize.DataScience.uriProviderDescriptionFormat(q.description || '', this.extensionId), + original: q + }; + }); + } + public async handleQuickPick( + item: QuickPickItem, + back: boolean + ): Promise { + if (!this.provider.handleQuickPick) { + return; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((item as any).original) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this.provider.handleQuickPick((item as any).original, back); + } + return this.provider.handleQuickPick(item, back); + } + + public async getServerUri(handle: JupyterServerUriHandle): Promise { + const server = await this.provider.getServerUri(handle); + if (!this.id.startsWith('_builtin') && !handlesForWhichWeHaveSentTelemetry.has(handle)) { + handlesForWhichWeHaveSentTelemetry.add(handle); + // Need this info to try and remove some of the properties from the API. + // Before we do that we need to determine what extensions are using which properties. + const pemUsed: (keyof IJupyterServerUri)[] = []; + Object.keys(server).forEach((k) => { + const value = server[k as keyof IJupyterServerUri]; + if (!value) { + return; + } + if (typeof value === 'object' && Object.keys(value).length === 0 && !(value instanceof Date)) { + return; + } + pemUsed.push(k as keyof IJupyterServerUri); + }); + sendTelemetryEvent(Telemetry.JupyterServerProviderResponseApi, undefined, { + providerId: this.id, + extensionId: this.extensionId, + pemUsed + }); + } + return server; + } +} diff --git a/src/kernels/jupyter/connection/jupyterUriProviderWrapper.ts b/src/kernels/jupyter/connection/jupyterUriProviderWrapper.ts deleted file mode 100644 index bf0270a3a79..00000000000 --- a/src/kernels/jupyter/connection/jupyterUriProviderWrapper.ts +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import * as vscode from 'vscode'; -import { IDisposableRegistry } from '../../../platform/common/types'; -import * as localize from '../../../platform/common/utils/localize'; -import { IJupyterUriProvider, JupyterServerUriHandle, IJupyterServerUri } from '../types'; -import { Telemetry, sendTelemetryEvent } from '../../../telemetry'; - -const handlesForWhichWeHaveSentTelemetry = new Set(); -/** - * This class wraps an IJupyterUriProvider provided by another extension. It allows us to show - * extra data on the other extension's UI. - */ -export class JupyterUriProviderWrapper implements IJupyterUriProvider { - onDidChangeHandles?: vscode.Event; - getHandles?(): Promise; - removeHandle?(handle: JupyterServerUriHandle): Promise; - - constructor( - private readonly provider: IJupyterUriProvider, - private extensionId: string, - disposables: IDisposableRegistry - ) { - if (provider.onDidChangeHandles) { - const _onDidChangeHandles = new vscode.EventEmitter(); - this.onDidChangeHandles = _onDidChangeHandles.event.bind(this); - - disposables.push(_onDidChangeHandles); - disposables.push( - provider.onDidChangeHandles(() => { - _onDidChangeHandles.fire(); - }) - ); - } - - if (provider.getHandles) { - this.getHandles = async () => { - return provider.getHandles!(); - }; - } - - if (provider.removeHandle) { - this.removeHandle = (handle: JupyterServerUriHandle) => { - return provider.removeHandle!(handle); - }; - } - } - public get id() { - return this.provider.id; - } - public get displayName(): string | undefined { - return this.provider.displayName; - } - public get detail(): string | undefined { - return this.provider.detail; - } - public async getQuickPickEntryItems(): Promise { - if (!this.provider.getQuickPickEntryItems) { - return []; - } - return (await this.provider.getQuickPickEntryItems()).map((q) => { - return { - ...q, - // Add the package name onto the description - description: localize.DataScience.uriProviderDescriptionFormat(q.description || '', this.extensionId), - original: q - }; - }); - } - public async handleQuickPick( - item: vscode.QuickPickItem, - back: boolean - ): Promise { - if (!this.provider.handleQuickPick) { - return; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((item as any).original) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return this.provider.handleQuickPick((item as any).original, back); - } - return this.provider.handleQuickPick(item, back); - } - - public async getServerUri(handle: JupyterServerUriHandle): Promise { - const server = await this.provider.getServerUri(handle); - if (!this.id.startsWith('_builtin') && !handlesForWhichWeHaveSentTelemetry.has(handle)) { - handlesForWhichWeHaveSentTelemetry.add(handle); - // Need this info to try and remove some of the properties from the API. - // Before we do that we need to determine what extensions are using which properties. - const pemUsed: (keyof IJupyterServerUri)[] = []; - Object.keys(server).forEach((k) => { - const value = server[k as keyof IJupyterServerUri]; - if (!value) { - return; - } - if (typeof value === 'object' && Object.keys(value).length === 0 && !(value instanceof Date)) { - return; - } - pemUsed.push(k as keyof IJupyterServerUri); - }); - sendTelemetryEvent(Telemetry.JupyterServerProviderResponseApi, undefined, { - providerId: this.id, - extensionId: this.extensionId, - pemUsed - }); - } - return server; - } -} diff --git a/src/kernels/jupyter/connection/liveRemoteKernelConnectionTracker.ts b/src/kernels/jupyter/connection/liveRemoteKernelConnectionTracker.ts index 780a3430b35..cf95e11c64b 100644 --- a/src/kernels/jupyter/connection/liveRemoteKernelConnectionTracker.ts +++ b/src/kernels/jupyter/connection/liveRemoteKernelConnectionTracker.ts @@ -18,7 +18,9 @@ type UriString = string; type UriSessionUsedByResources = Record>; /** - * Class to track the remote kernels that have been used by notebooks. + * Keeps track of the kernel id and server id that was last used by a resource. + * If a kernel is no longer used by a resource (e.g. another kernel is selected), + * then the previous kernel would be removed from the list of kernels for this resource. */ @injectable() export class LiveRemoteKernelConnectionUsageTracker diff --git a/src/kernels/jupyter/connection/remoteJupyterServerMruUpdate.ts b/src/kernels/jupyter/connection/remoteJupyterServerMruUpdate.ts index 8f667d35b32..88732b2f7b5 100644 --- a/src/kernels/jupyter/connection/remoteJupyterServerMruUpdate.ts +++ b/src/kernels/jupyter/connection/remoteJupyterServerMruUpdate.ts @@ -45,7 +45,7 @@ export class RemoteJupyterServerMruUpdate implements IExtensionSyncActivationSer if (kernel.status === 'idle' && !kernel.disposed && !kernel.disposing) { const timeout = setTimeout(() => { // Log this remote URI into our MRU list - this.serverStorage.updateMRU(connection.serverId).catch(noop); + this.serverStorage.update(connection.serverId).catch(noop); }, INTERVAL_IN_SECONDS_TO_UPDATE_MRU); this.monitoredKernels.set(kernel, timeout); this.disposables.push(new Disposable(() => clearTimeout(timeout))); @@ -56,6 +56,6 @@ export class RemoteJupyterServerMruUpdate implements IExtensionSyncActivationSer ); // Log this remote URI into our MRU list - this.serverStorage.updateMRU(connection.serverId).catch(noop); + this.serverStorage.update(connection.serverId).catch(noop); } } diff --git a/src/kernels/jupyter/connection/serverSelector.ts b/src/kernels/jupyter/connection/serverSelector.ts index 523e48107db..9cbae53bca0 100644 --- a/src/kernels/jupyter/connection/serverSelector.ts +++ b/src/kernels/jupyter/connection/serverSelector.ts @@ -121,7 +121,7 @@ export class JupyterServerSelector { } const connection = await this.jupyterConnection.createConnectionInfo({ uri: userURI }); - await this.serverUriStorage.addMRU(userURI, connection.displayName); + await this.serverUriStorage.add(userURI, connection.displayName); // Indicate setting a jupyter URI to a remote setting. Check if an azure remote or not sendTelemetryEvent(Telemetry.SetJupyterURIToUserSpecified, undefined, { diff --git a/src/kernels/jupyter/connection/serverSelectorCommand.ts b/src/kernels/jupyter/connection/serverSelectorCommand.ts index 8f4911ef6c9..009f1261939 100644 --- a/src/kernels/jupyter/connection/serverSelectorCommand.ts +++ b/src/kernels/jupyter/connection/serverSelectorCommand.ts @@ -44,6 +44,6 @@ export class JupyterServerSelectorCommand implements IExtensionSyncActivationSer } private async clearJupyterUris(): Promise { - return this.serverUriStorage.clearMRU(); + return this.serverUriStorage.clear(); } } diff --git a/src/kernels/jupyter/connection/serverUriStorage.ts b/src/kernels/jupyter/connection/serverUriStorage.ts index 33324ec87de..fd4098e306e 100644 --- a/src/kernels/jupyter/connection/serverUriStorage.ts +++ b/src/kernels/jupyter/connection/serverUriStorage.ts @@ -3,14 +3,9 @@ import { EventEmitter, Memento } from 'vscode'; import { inject, injectable, named } from 'inversify'; -import { - IWorkspaceService, - IEncryptedStorage, - IApplicationEnvironment -} from '../../../platform/common/application/types'; +import { IEncryptedStorage } from '../../../platform/common/application/types'; import { Settings } from '../../../platform/common/constants'; -import { getFilePath } from '../../../platform/common/platform/fs-paths'; -import { ICryptoUtils, IMemento, GLOBAL_MEMENTO } from '../../../platform/common/types'; +import { IMemento, GLOBAL_MEMENTO } from '../../../platform/common/types'; import { traceInfoIfCI, traceVerbose } from '../../../platform/logging'; import { computeServerId, extractJupyterServerHandleAndId } from '../jupyterUtils'; import { IJupyterServerUriEntry, IJupyterServerUriStorage, IJupyterUriProviderRegistration } from '../types'; @@ -34,16 +29,13 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { return this._onDidAddUri.event; } constructor( - @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(ICryptoUtils) private readonly crypto: ICryptoUtils, @inject(IEncryptedStorage) private readonly encryptedStorage: IEncryptedStorage, - @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment, @inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalMemento: Memento, @inject(IJupyterUriProviderRegistration) private readonly jupyterPickerRegistration: IJupyterUriProviderRegistration ) {} - public async updateMRU(serverId: string) { - const uriList = await this.getMRUs(); + public async update(serverId: string) { + const uriList = await this.getAll(); const existingEntry = uriList.find((entry) => entry.serverId === serverId); if (!existingEntry) { @@ -53,7 +45,7 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { await this.addToUriList(existingEntry.uri, existingEntry.displayName || ''); } private async addToUriList(uri: string, displayName: string) { - const [uriList, serverId] = await Promise.all([this.getMRUs(), computeServerId(uri)]); + const [uriList, serverId] = await Promise.all([this.getAll(), computeServerId(uri)]); // Check if we have already found a display name for this server displayName = uriList.find((entry) => entry.serverId === serverId)?.displayName || displayName || uri; @@ -62,16 +54,24 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { const editedList = uriList.filter((f, i) => f.uri !== uri && i < Settings.JupyterServerUriListMax - 1); // Add this entry into the last. - const entry = { uri, time: Date.now(), serverId, displayName, isValidated: true }; + const idAndHandle = extractJupyterServerHandleAndId(uri); + const entry: IJupyterServerUriEntry = { + uri, + time: Date.now(), + serverId, + displayName, + isValidated: true, + provider: idAndHandle + }; editedList.push(entry); // Signal that we added in the entry this._onDidAddUri.fire(entry); - + this._onDidChangeUri.fire(); // Needs to happen as soon as we change so that dependencies update synchronously return this.updateMemento(editedList); } - public async removeMRU(uri: string) { - const uriList = await this.getMRUs(); + public async remove(uri: string) { + const uriList = await this.getAll(); await this.updateMemento(uriList.filter((f) => f.uri !== uri)); const removedItem = uriList.find((f) => f.uri === uri); @@ -111,7 +111,7 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { blob ); } - public async getMRUs(): Promise { + public async getAll(): Promise { if (this.lastSavedList) { return this.lastSavedList; } @@ -132,6 +132,7 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { const uriAndDisplayName = item.split(Settings.JupyterServerRemoteLaunchNameSeparator); const uri = uriAndDisplayName[0]; const serverId = await computeServerId(uri); + const idAndHandle = extractJupyterServerHandleAndId(uri); // 'same' is specified for the display name to keep storage shorter if it is the same value as the URI const displayName = uriAndDisplayName[1] === Settings.JupyterServerRemoteLaunchUriEqualsDisplayName || @@ -143,33 +144,25 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { serverId, displayName, uri, - isValidated: true + isValidated: true, + provider: idAndHandle }; if (uri === Settings.JupyterServerLocalLaunch) { return; } - try { - const idAndHandle = extractJupyterServerHandleAndId(uri); - if (idAndHandle) { - return this.jupyterPickerRegistration - .getJupyterServerUri(idAndHandle.id, idAndHandle.handle) - .then( - () => server, - () => { - server.isValidated = false; - return server; - } - ); - } - } catch (ex) { - traceVerbose(`Failed to extract jupyter server uri ${uri} ${ex}`); - server.isValidated = false; - return server; - } - - return server; + return idAndHandle + ? this.jupyterPickerRegistration + .getJupyterServerUri(idAndHandle.id, idAndHandle.handle) + .then( + () => server, + () => { + server.isValidated = false; + return server; + } + ) + : server; }) ); @@ -183,8 +176,8 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { return this.lastSavedList; } - public async clearMRU(): Promise { - const uriList = await this.getMRUs(); + public async clear(): Promise { + const uriList = await this.getAll(); this.lastSavedList = Promise.resolve([]); // Clear out memento and encrypted storage await this.globalMemento.update(Settings.JupyterServerUriList, []); @@ -197,33 +190,15 @@ export class JupyterServerUriStorage implements IJupyterServerUriStorage { // Notify out that we've removed the list to clean up controller entries, passwords, ect this._onDidRemoveUris.fire(uriList); } - public async getMRU(id: string): Promise { - const savedList = await this.getMRUs(); + public async get(id: string): Promise { + const savedList = await this.getAll(); return savedList.find((item) => item.serverId === id); } - public async addMRU(uri: string, displayName: string): Promise { + public async add(uri: string, displayName: string): Promise { traceInfoIfCI(`setUri: ${uri}`); // display name is wrong here await this.addToUriList(uri, displayName ?? uri); this._onDidChangeUri.fire(); // Needs to happen as soon as we change so that dependencies update synchronously - - // Save in the storage (unique account per workspace) - const key = await this.getUriAccountKey(); - await this.encryptedStorage.store(Settings.JupyterServerRemoteLaunchService, key, uri); - } - - /** - * Returns a unique identifier for the current workspace - */ - private async getUriAccountKey(): Promise { - if (this.workspaceService.rootFolder) { - // Folder situation - return this.crypto.createHash(getFilePath(this.workspaceService.rootFolder), 'SHA-512'); - } else if (this.workspaceService.workspaceFile) { - // Workspace situation - return this.crypto.createHash(getFilePath(this.workspaceService.workspaceFile), 'SHA-512'); - } - return this.appEnv.machineId; // Global key when no folder or workspace file } } diff --git a/src/kernels/jupyter/finder/remoteKernelFinder.ts b/src/kernels/jupyter/finder/remoteKernelFinder.ts index 2355d4feb63..25a012a867f 100644 --- a/src/kernels/jupyter/finder/remoteKernelFinder.ts +++ b/src/kernels/jupyter/finder/remoteKernelFinder.ts @@ -13,10 +13,9 @@ import { RemoteKernelConnectionMetadata, RemoteKernelSpecConnectionMetadata } from '../../types'; -import { IDisposable, IExtensions } from '../../../platform/common/types'; +import { IAsyncDisposable, IDisposable, IExtensions } from '../../../platform/common/types'; import { IJupyterSessionManagerFactory, - IJupyterSessionManager, IJupyterRemoteCachedKernelValidator, IRemoteKernelFinder, IJupyterServerUriEntry @@ -326,81 +325,71 @@ export class RemoteKernelFinder implements IRemoteKernelFinder, IDisposable { // Talk to the remote server to determine sessions public async listKernelsFromConnection(connInfo: IJupyterConnection): Promise { - // Get a jupyter session manager to talk to - let sessionManager: IJupyterSessionManager | undefined; - // This should only be used when doing remote. - if (connInfo.type === 'jupyter') { - try { - sessionManager = await this.jupyterSessionManagerFactory.create(connInfo); - - // Get running and specs at the same time - const [running, specs, sessions, serverId] = await Promise.all([ - sessionManager.getRunningKernels(), - sessionManager.getKernelSpecs(), - sessionManager.getRunningSessions(), - computeServerId(connInfo.url) - ]); - - // Turn them both into a combined list - const mappedSpecs = await Promise.all( - specs.map(async (s) => { - await sendKernelSpecTelemetry(s, 'remote'); - const kernel = RemoteKernelSpecConnectionMetadata.create({ - kernelSpec: s, - id: getKernelId(s, undefined, serverId), - baseUrl: connInfo.baseUrl, - serverId: serverId - }); - return kernel; - }) - ); - const mappedLive = sessions.map((s) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const liveKernel = s.kernel as any; - const lastActivityTime = liveKernel.last_activity - ? new Date(Date.parse(liveKernel.last_activity.toString())) - : new Date(); - const numberOfConnections = liveKernel.connections - ? parseInt(liveKernel.connections.toString(), 10) - : 0; - const activeKernel = running.find((active) => active.id === s.kernel?.id) || {}; - const matchingSpec: Partial = - specs.find((spec) => spec.name === s.kernel?.name) || {}; - - const kernel = LiveRemoteKernelConnectionMetadata.create({ - kernelModel: { - ...s.kernel, - ...matchingSpec, - ...activeKernel, - name: s.kernel?.name || '', - lastActivityTime, - numberOfConnections, - model: s - }, + const disposables: IAsyncDisposable[] = []; + try { + const sessionManager = await this.jupyterSessionManagerFactory.create(connInfo); + disposables.push(sessionManager); + + // Get running and specs at the same time + const [running, specs, sessions, serverId] = await Promise.all([ + sessionManager.getRunningKernels(), + sessionManager.getKernelSpecs(), + sessionManager.getRunningSessions(), + computeServerId(connInfo.url) + ]); + + // Turn them both into a combined list + const mappedSpecs = await Promise.all( + specs.map(async (s) => { + await sendKernelSpecTelemetry(s, 'remote'); + const kernel = RemoteKernelSpecConnectionMetadata.create({ + kernelSpec: s, + id: getKernelId(s, undefined, serverId), baseUrl: connInfo.baseUrl, - id: s.kernel?.id || '', - serverId + serverId: serverId }); return kernel; + }) + ); + const mappedLive = sessions.map((s) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const liveKernel = s.kernel as any; + const lastActivityTime = liveKernel.last_activity + ? new Date(Date.parse(liveKernel.last_activity.toString())) + : new Date(); + const numberOfConnections = liveKernel.connections + ? parseInt(liveKernel.connections.toString(), 10) + : 0; + const activeKernel = running.find((active) => active.id === s.kernel?.id) || {}; + const matchingSpec: Partial = + specs.find((spec) => spec.name === s.kernel?.name) || {}; + + const kernel = LiveRemoteKernelConnectionMetadata.create({ + kernelModel: { + ...s.kernel, + ...matchingSpec, + ...activeKernel, + name: s.kernel?.name || '', + lastActivityTime, + numberOfConnections, + model: s + }, + baseUrl: connInfo.baseUrl, + id: s.kernel?.id || '', + serverId }); + return kernel; + }); - // Filter out excluded ids - const filtered = mappedLive.filter((k) => !this.kernelIdsToHide.has(k.kernelModel.id || '')); - const items = [...filtered, ...mappedSpecs]; - return items; - } catch (ex) { - traceError( - `Error fetching kernels from ${connInfo.baseUrl} (${connInfo.displayName}, ${connInfo.type}):`, - ex - ); - throw ex; - } finally { - if (sessionManager) { - await sessionManager.dispose(); - } - } + // Filter out excluded ids + const filtered = mappedLive.filter((k) => !this.kernelIdsToHide.has(k.kernelModel.id || '')); + return [...filtered, ...mappedSpecs]; + } catch (ex) { + traceError(`Error fetching kernels from ${connInfo.baseUrl} (${connInfo.displayName}):`, ex); + throw ex; + } finally { + await Promise.all(disposables.map((d) => d.dispose().catch(noop))); } - return []; } private async writeToCache(values: RemoteKernelConnectionMetadata[]) { diff --git a/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts b/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts index ac034d15cd4..07c40903182 100644 --- a/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts +++ b/src/kernels/jupyter/finder/remoteKernelFinder.unit.test.ts @@ -7,7 +7,7 @@ import type { Session } from '@jupyterlab/services'; import { assert } from 'chai'; import { anything, instance, mock, when } from 'ts-mockito'; import { getDisplayNameOrNameOfKernelConnection } from '../../helpers'; -import { Disposable, EventEmitter, Memento, Uri } from 'vscode'; +import { Disposable, Memento, Uri } from 'vscode'; import { CryptoUtils } from '../../../platform/common/crypto'; import { noop } from '../../../test/core'; import { @@ -41,17 +41,14 @@ suite(`Remote Kernel Finder`, () => { let fs: IFileSystemNode; let memento: Memento; let jupyterSessionManager: IJupyterSessionManager; - const dummyEvent = new EventEmitter(); let cachedRemoteKernelValidator: IJupyterRemoteCachedKernelValidator; let kernelsChanged: TestEventHandler; let jupyterConnection: JupyterConnection; const connInfo: IJupyterConnection = { url: 'http://foobar', - type: 'jupyter', localLaunch: false, baseUrl: 'http://foobar', displayName: 'foobar connection', - disconnected: dummyEvent.event, token: '', hostName: 'foobar', rootDirectory: Uri.file('.'), @@ -121,6 +118,7 @@ suite(`Remote Kernel Finder`, () => { return Promise.resolve(d.toLowerCase()); }); jupyterSessionManager = mock(JupyterSessionManager); + when(jupyterSessionManager.dispose()).thenResolve(); const jupyterSessionManagerFactory = mock(JupyterSessionManagerFactory); when(jupyterSessionManagerFactory.create(anything())).thenResolve(instance(jupyterSessionManager)); const extensionChecker = mock(PythonExtensionChecker); diff --git a/src/kernels/jupyter/finder/remoteKernelFinderController.ts b/src/kernels/jupyter/finder/remoteKernelFinderController.ts index fa9b8450dfa..06ebbf422e3 100644 --- a/src/kernels/jupyter/finder/remoteKernelFinderController.ts +++ b/src/kernels/jupyter/finder/remoteKernelFinderController.ts @@ -44,7 +44,7 @@ export class RemoteKernelFinderController implements IExtensionSyncActivationSer activate() { // Add in the URIs that we already know about this.serverUriStorage - .getMRUs() + .getAll() .then((currentServers) => { currentServers.forEach(this.createRemoteKernelFinder.bind(this)); diff --git a/src/kernels/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.node.ts b/src/kernels/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.node.ts deleted file mode 100644 index bbc43e2693a..00000000000 --- a/src/kernels/jupyter/interpreter/jupyterInterpreterOldCacheStateStore.node.ts +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { Uri } from 'vscode'; -import { IWorkspaceService } from '../../../platform/common/application/types'; -import { IPersistentState, IPersistentStateFactory } from '../../../platform/common/types'; - -type CacheInfo = { - /** - * Cache store (across VSC sessions). - * - * @type {IPersistentState} - */ - state: IPersistentState; -}; - -/** - * Old way to store the global jupyter interpreter - */ -@injectable() -export class JupyterInterpreterOldCacheStateStore { - private readonly workspaceJupyterInterpreter: CacheInfo; - private readonly globalJupyterInterpreter: CacheInfo; - constructor( - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IPersistentStateFactory) persistentStateFactory: IPersistentStateFactory - ) { - // Cache stores to keep track of jupyter interpreters found. - const workspaceState = - persistentStateFactory.createWorkspacePersistentState('DS-VSC-JupyterInterpreter'); - const globalState = persistentStateFactory.createGlobalPersistentState('DS-VSC-JupyterInterpreter'); - this.workspaceJupyterInterpreter = { state: workspaceState }; - this.globalJupyterInterpreter = { state: globalState }; - } - private get cacheStore(): CacheInfo { - return this.workspace.hasWorkspaceFolders ? this.workspaceJupyterInterpreter : this.globalJupyterInterpreter; - } - public getCachedInterpreterPath(): Uri | undefined { - return this.cacheStore.state.value ? Uri.file(this.cacheStore.state.value) : undefined; - } - public async clearCache(): Promise { - await this.cacheStore.state.updateValue(undefined); - } -} diff --git a/src/kernels/jupyter/interpreter/jupyterInterpreterService.node.ts b/src/kernels/jupyter/interpreter/jupyterInterpreterService.node.ts index a35669d0e25..834f0d438f6 100644 --- a/src/kernels/jupyter/interpreter/jupyterInterpreterService.node.ts +++ b/src/kernels/jupyter/interpreter/jupyterInterpreterService.node.ts @@ -11,7 +11,6 @@ import { PythonEnvironment } from '../../../platform/pythonEnvironments/info'; import { sendTelemetryEvent, Telemetry } from '../../../telemetry'; import { JupyterInstallError } from '../../../platform/errors/jupyterInstallError'; import { JupyterInterpreterDependencyService } from './jupyterInterpreterDependencyService.node'; -import { JupyterInterpreterOldCacheStateStore } from './jupyterInterpreterOldCacheStateStore.node'; import { JupyterInterpreterSelector } from './jupyterInterpreterSelector.node'; import { JupyterInterpreterStateStore } from './jupyterInterpreterStateStore.node'; import { JupyterInterpreterDependencyResponse } from '../types'; @@ -34,8 +33,6 @@ export class JupyterInterpreterService { } constructor( - @inject(JupyterInterpreterOldCacheStateStore) - private readonly oldVersionCacheStateStore: JupyterInterpreterOldCacheStateStore, @inject(JupyterInterpreterStateStore) private readonly interpreterSelectionState: JupyterInterpreterStateStore, @inject(JupyterInterpreterSelector) private readonly jupyterInterpreterSelector: JupyterInterpreterSelector, @inject(JupyterInterpreterDependencyService) @@ -174,19 +171,6 @@ export class JupyterInterpreterService { this.changeSelectedInterpreterProperty(interpreter); } - // Check the location that we stored jupyter launch path in the old version - // if it's there, return it and clear the location - private getInterpreterFromChangeOfOlderVersionOfExtension(): Uri | undefined { - const pythonPath = this.oldVersionCacheStateStore.getCachedInterpreterPath(); - if (!pythonPath) { - return; - } - - // Clear the cache to not check again - this.oldVersionCacheStateStore.clearCache().catch(noop); - return pythonPath; - } - private changeSelectedInterpreterProperty(interpreter: PythonEnvironment) { this._selectedInterpreter = interpreter; this._onDidChangeInterpreter.fire(interpreter); @@ -236,14 +220,8 @@ export class JupyterInterpreterService { private async getInitialInterpreterImpl(token?: CancellationToken): Promise { let interpreter: PythonEnvironment | undefined; - // Check the old version location first, we will clear it if we find it here - const oldVersionPythonPath = this.getInterpreterFromChangeOfOlderVersionOfExtension(); - if (oldVersionPythonPath) { - interpreter = await this.validateInterpreterPath(oldVersionPythonPath, token); - } - // Next check the saved global path - if (!interpreter && this.interpreterSelectionState.selectedPythonPath) { + if (this.interpreterSelectionState.selectedPythonPath) { interpreter = await this.validateInterpreterPath(this.interpreterSelectionState.selectedPythonPath, token); // If we had a global path, but it's not valid, trash it diff --git a/src/kernels/jupyter/interpreter/jupyterInterpreterService.unit.test.ts b/src/kernels/jupyter/interpreter/jupyterInterpreterService.unit.test.ts index 62d3c0f7643..9858a07f22b 100644 --- a/src/kernels/jupyter/interpreter/jupyterInterpreterService.unit.test.ts +++ b/src/kernels/jupyter/interpreter/jupyterInterpreterService.unit.test.ts @@ -15,7 +15,6 @@ import { MockMemento } from '../../../test/mocks/mementos'; import { createPythonInterpreter } from '../../../test/utils/interpreters'; import { JupyterInterpreterDependencyResponse } from '../types'; import { JupyterInterpreterDependencyService } from './jupyterInterpreterDependencyService.node'; -import { JupyterInterpreterOldCacheStateStore } from './jupyterInterpreterOldCacheStateStore.node'; import { JupyterInterpreterSelector } from './jupyterInterpreterSelector.node'; import { JupyterInterpreterService } from './jupyterInterpreterService.node'; import { JupyterInterpreterStateStore } from './jupyterInterpreterStateStore.node'; @@ -30,7 +29,6 @@ suite('Jupyter Interpreter Service', () => { let selectedInterpreterEventArgs: PythonEnvironment | undefined; let memento: Memento; let interpreterSelectionState: JupyterInterpreterStateStore; - let oldVersionCacheStateStore: JupyterInterpreterOldCacheStateStore; let appShell: IApplicationShell; const selectedJupyterInterpreter = createPythonInterpreter({ displayName: 'JupyterInterpreter' }); const pythonInterpreter: PythonEnvironment = { @@ -53,14 +51,12 @@ suite('Jupyter Interpreter Service', () => { interpreterService = mock(); memento = mock(MockMemento); interpreterSelectionState = mock(JupyterInterpreterStateStore); - oldVersionCacheStateStore = mock(JupyterInterpreterOldCacheStateStore); appShell = mock(); const workspace = mock(); const onDidGrantWorkspaceTrust = new EventEmitter(); disposables.push(onDidGrantWorkspaceTrust); when(workspace.onDidGrantWorkspaceTrust).thenReturn(onDidGrantWorkspaceTrust.event); jupyterInterpreterService = new JupyterInterpreterService( - instance(oldVersionCacheStateStore), instance(interpreterSelectionState), instance(interpreterSelector), instance(interpreterConfiguration), @@ -146,23 +142,13 @@ suite('Jupyter Interpreter Service', () => { verify(interpreterSelector.selectInterpreter()).never(); assert.equal(response, JupyterInterpreterDependencyResponse.cancel); }); - test('setInitialInterpreter if older version is set should use and clear', async () => { - when(oldVersionCacheStateStore.getCachedInterpreterPath()).thenReturn(pythonInterpreter.uri); - when(oldVersionCacheStateStore.clearCache()).thenResolve(); - when(interpreterConfiguration.areDependenciesInstalled(pythonInterpreter, anything())).thenResolve(true); - const initialInterpreter = await jupyterInterpreterService.setInitialInterpreter(undefined); - verify(oldVersionCacheStateStore.clearCache()).once(); - assert.equal(initialInterpreter, pythonInterpreter); - }); test('setInitialInterpreter use saved interpreter if valid', async () => { - when(oldVersionCacheStateStore.getCachedInterpreterPath()).thenReturn(undefined); when(interpreterSelectionState.selectedPythonPath).thenReturn(pythonInterpreter.uri); when(interpreterConfiguration.areDependenciesInstalled(pythonInterpreter, anything())).thenResolve(true); const initialInterpreter = await jupyterInterpreterService.setInitialInterpreter(undefined); assert.equal(initialInterpreter, pythonInterpreter); }); test('setInitialInterpreter saved interpreter invalid, clear it and use active interpreter', async () => { - when(oldVersionCacheStateStore.getCachedInterpreterPath()).thenReturn(undefined); when(interpreterSelectionState.selectedPythonPath).thenReturn(secondPythonInterpreter.uri); when(interpreterConfiguration.areDependenciesInstalled(secondPythonInterpreter, anything())).thenResolve(false); when(interpreterService.getActiveInterpreter(anything())).thenResolve(pythonInterpreter); diff --git a/src/kernels/jupyter/jupyterUtils.ts b/src/kernels/jupyter/jupyterUtils.ts index 122c11dce4c..73214d295d6 100644 --- a/src/kernels/jupyter/jupyterUtils.ts +++ b/src/kernels/jupyter/jupyterUtils.ts @@ -113,7 +113,6 @@ export function createRemoteConnectionInfo( const hostName = serverUri ? new URL(serverUri.baseUrl).hostname : url.hostname; const isEmptyAuthHeader = Object.keys(serverUri?.authorizationHeader ?? {}).length === 0; return { - type: 'jupyter', baseUrl, providerId, token, @@ -123,9 +122,6 @@ export function createRemoteConnectionInfo( serverUri && serverUri.displayName ? serverUri.displayName : getJupyterConnectionDisplayName(token, baseUrl), - disconnected: (_l) => { - return { dispose: noop }; - }, dispose: noop, rootDirectory: Uri.file(''), // Temporarily support workingDirectory as a fallback for old extensions using that (to be removed in the next release). diff --git a/src/kernels/jupyter/launcher/jupyterConnection.node.ts b/src/kernels/jupyter/launcher/jupyterConnection.node.ts index 80fe65bd3ff..07c17996c93 100644 --- a/src/kernels/jupyter/launcher/jupyterConnection.node.ts +++ b/src/kernels/jupyter/launcher/jupyterConnection.node.ts @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { ChildProcess } from 'child_process'; import { Subscription } from 'rxjs/Subscription'; -import { CancellationError, CancellationToken, Disposable, Event, EventEmitter, Uri } from 'vscode'; +import { CancellationError, CancellationToken, Disposable, Uri } from 'vscode'; import { IConfigurationService, IDisposable } from '../../../platform/common/types'; import { Cancellation } from '../../../platform/common/cancellation'; import { traceError, traceWarning, traceVerbose } from '../../../platform/logging'; @@ -104,15 +103,7 @@ export class JupyterConnectionWaiter implements IDisposable { processDisposable: Disposable ) { // eslint-disable-next-line @typescript-eslint/no-use-before-define - return new JupyterConnection( - url, - baseUrl, - token, - hostName, - this.rootDir, - processDisposable, - this.launchResult.proc - ); + return new JupyterConnection(url, baseUrl, token, hostName, this.rootDir, processDisposable); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -234,35 +225,19 @@ export class JupyterConnectionWaiter implements IDisposable { // Represents an active connection to a running jupyter notebook class JupyterConnection implements IJupyterConnection { public readonly localLaunch: boolean = true; - public readonly type = 'jupyter'; - private eventEmitter: EventEmitter = new EventEmitter(); constructor( public readonly url: string, public readonly baseUrl: string, public readonly token: string, public readonly hostName: string, public readonly rootDirectory: Uri, - private readonly disposable: Disposable, - childProc: ChildProcess | undefined - ) { - // If the local process exits, set our exit code and fire our event - if (childProc) { - childProc.on('exit', (c) => { - // Our code expects the exit code to be of type `number` or `undefined`. - const code = typeof c === 'number' ? c : 0; - this.eventEmitter.fire(code); - }); - } - } + private readonly disposable: Disposable + ) {} public get displayName(): string { return getJupyterConnectionDisplayName(this.token, this.baseUrl); } - public get disconnected(): Event { - return this.eventEmitter.event; - } - public dispose() { if (this.disposable) { this.disposable.dispose(); diff --git a/src/kernels/jupyter/launcher/jupyterServerConnector.node.ts b/src/kernels/jupyter/launcher/jupyterServerConnector.node.ts index 4722212375b..e828f74d266 100644 --- a/src/kernels/jupyter/launcher/jupyterServerConnector.node.ts +++ b/src/kernels/jupyter/launcher/jupyterServerConnector.node.ts @@ -13,7 +13,7 @@ export class JupyterServerConnector implements IJupyterServerConnector { private readonly startupUi = new DisplayOptions(true); constructor( @inject(IJupyterServerProvider) - private readonly jupyterNotebookProvider: IJupyterServerProvider, + private readonly jupyterServerProvider: IJupyterServerProvider, @inject(IPythonExtensionChecker) private readonly extensionChecker: IPythonExtensionChecker ) {} @@ -29,7 +29,7 @@ export class JupyterServerConnector implements IJupyterServerConnector { }); options.ui = this.startupUi; if (this.extensionChecker.isPythonExtensionInstalled) { - return this.jupyterNotebookProvider.getOrCreateServer(options).finally(() => handler.dispose()); + return this.jupyterServerProvider.getOrStartServer(options).finally(() => handler.dispose()); } else { handler.dispose(); if (!this.startupUi.disableUI) { diff --git a/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts b/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts index 016b2ee82d2..200fa8c6211 100644 --- a/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts +++ b/src/kernels/jupyter/launcher/jupyterServerHelper.node.ts @@ -76,25 +76,12 @@ export class JupyterServerHelper implements IJupyterServerHelper { traceVerbose(`Finished disposing HostJupyterExecution`); } - private async hostConnectToNotebookServer( - resource: Resource, - cancelToken: CancellationToken - ): Promise { - if (!this._disposed) { - return this.connectToNotebookServerImpl(resource, cancelToken); - } - throw new Error('Notebook server is disposed'); - } - - public async connectToNotebookServer( - resource: Resource, - cancelToken: CancellationToken - ): Promise { + public async startServer(resource: Resource, cancelToken: CancellationToken): Promise { if (this._disposed) { throw new Error('Notebook server is disposed'); } if (!this.cache) { - const promise = (this.cache = this.hostConnectToNotebookServer(resource, cancelToken)); + const promise = (this.cache = this.startJupyterWithRetry(resource, cancelToken)); promise.catch((ex) => { traceError(`Failed to start the Jupyter Server`, ex); if (this.cache === promise) { @@ -105,10 +92,6 @@ export class JupyterServerHelper implements IJupyterServerHelper { return this.cache; } - public async getJupyterServerConnection(): Promise { - return !this._disposed && this.cache ? this.cache : undefined; - } - public async refreshCommands(): Promise { await this.jupyterInterpreterService?.refreshCommands(); } @@ -136,10 +119,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { } /* eslint-disable complexity, */ - public connectToNotebookServerImpl( - resource: Resource, - cancelToken: CancellationToken - ): Promise { + private startJupyterWithRetry(resource: Resource, cancelToken: CancellationToken): Promise { // Return nothing if we cancel // eslint-disable-next-line return Cancellation.race(async () => { @@ -152,7 +132,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { while (tryCount <= maxTries && !this.disposed) { try { // Start or connect to the process - connection = await this.start(resource, cancelToken); + connection = await this.startImpl(resource, cancelToken); traceVerbose(`Connection complete server`); return connection; @@ -182,7 +162,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { }, cancelToken); } - private async start(resource: Resource, cancelToken: CancellationToken): Promise { + private async startImpl(resource: Resource, cancelToken: CancellationToken): Promise { // If our uri is undefined or if it's set to local launch we need to launch a server locally // If that works, then attempt to start the server traceInfo(`Launching server`); diff --git a/src/kernels/jupyter/launcher/jupyterServerHelper.web.ts b/src/kernels/jupyter/launcher/jupyterServerHelper.web.ts index 7e96f8c07a0..2262ba16f99 100644 --- a/src/kernels/jupyter/launcher/jupyterServerHelper.web.ts +++ b/src/kernels/jupyter/launcher/jupyterServerHelper.web.ts @@ -15,10 +15,7 @@ export class JupyterServerHelper implements IJupyterServerHelper { // } - public async connectToNotebookServer( - _resource: Resource, - _cancelToken: CancellationToken - ): Promise { + public async startServer(_resource: Resource, _cancelToken: CancellationToken): Promise { throw new Error('Invalid Operation in the Web'); } public async getJupyterServerConnection(): Promise { diff --git a/src/kernels/jupyter/launcher/jupyterServerProvider.node.ts b/src/kernels/jupyter/launcher/jupyterServerProvider.node.ts index 2e2dd86390f..7e320e28044 100644 --- a/src/kernels/jupyter/launcher/jupyterServerProvider.node.ts +++ b/src/kernels/jupyter/launcher/jupyterServerProvider.node.ts @@ -21,24 +21,9 @@ export class JupyterServerProvider implements IJupyterServerProvider { private readonly jupyterServerHelper: IJupyterServerHelper | undefined, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService ) {} - public async getOrCreateServer(options: GetServerOptions): Promise { - // If we are just fetching or only want to create for local, see if exists - if (this.jupyterServerHelper) { - const server = await this.jupyterServerHelper.getJupyterServerConnection(options.resource); - // Possible it wasn't created, hence create it. - if (server) { - return server; - } - } - - // Otherwise create a new server - return this.createServer(options); - } - - private async createServer(options: GetServerOptions): Promise { + public async getOrStartServer(options: GetServerOptions): Promise { if (!this.serverPromise) { - // Start a server - const promise = (this.serverPromise = this.startServer(options)); + const promise = (this.serverPromise = this.startServerImpl(options)); promise.catch(() => { if (promise === this.serverPromise) { this.serverPromise = undefined; @@ -48,7 +33,7 @@ export class JupyterServerProvider implements IJupyterServerProvider { return this.serverPromise; } - private async startServer(options: GetServerOptions): Promise { + private async startServerImpl(options: GetServerOptions): Promise { const jupyterServerHelper = this.jupyterServerHelper; if (!jupyterServerHelper) { throw new NotSupportedInWebError(); @@ -68,7 +53,7 @@ export class JupyterServerProvider implements IJupyterServerProvider { } // Then actually start the server traceVerbose(`Starting notebook server.`); - const result = await jupyterServerHelper.connectToNotebookServer(options.resource, options.token); + const result = await jupyterServerHelper.startServer(options.resource, options.token); Cancellation.throwIfCanceled(options.token); return result; } catch (e) { diff --git a/src/kernels/jupyter/launcher/jupyterServerProvider.web.ts b/src/kernels/jupyter/launcher/jupyterServerProvider.web.ts index 0d0482533c6..3d45859305e 100644 --- a/src/kernels/jupyter/launcher/jupyterServerProvider.web.ts +++ b/src/kernels/jupyter/launcher/jupyterServerProvider.web.ts @@ -7,7 +7,7 @@ import { IJupyterServerProvider } from '../types'; @injectable() export class JupyterServerProvider implements IJupyterServerProvider { - public async getOrCreateServer(_: GetServerOptions): Promise { + public async getOrStartServer(_: GetServerOptions): Promise { throw new Error('Invalid Operation in Web'); } } diff --git a/src/kernels/jupyter/launcher/juypterServerProvider.unit.test.ts b/src/kernels/jupyter/launcher/juypterServerProvider.unit.test.ts index 70a49feba68..c97523fb615 100644 --- a/src/kernels/jupyter/launcher/juypterServerProvider.unit.test.ts +++ b/src/kernels/jupyter/launcher/juypterServerProvider.unit.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { SemVer } from 'semver'; -import { anything, instance, mock, verify, when } from 'ts-mockito'; +import { anything, instance, mock, when } from 'ts-mockito'; import * as typemoq from 'typemoq'; import { CancellationTokenSource, Disposable, EventEmitter, Uri } from 'vscode'; import { disposeAllDisposables } from '../../../platform/common/helpers'; @@ -52,27 +52,14 @@ suite('Jupyter Server Provider', () => { disposables.push(source); }); teardown(() => disposeAllDisposables(disposables)); - test('Get Only - server', async () => { - const connection = mock(); - when((connection as any).then).thenReturn(undefined); - when(jupyterServerHelper.getJupyterServerConnection(anything())).thenResolve(instance(connection)); - - const server = await serverProvider.getOrCreateServer({ - resource: undefined, - ui: new DisplayOptions(false), - token: source.token - }); - expect(server).to.not.equal(undefined, 'Server expected to be defined'); - verify(jupyterServerHelper.getJupyterServerConnection(anything())).once(); - }); test('Get Or Create', async () => { when(jupyterServerHelper.getUsableJupyterPython()).thenResolve(workingPython); const connection = createTypeMoq('jupyter server'); - when(jupyterServerHelper.connectToNotebookServer(anything(), anything())).thenResolve(connection.object); + when(jupyterServerHelper.startServer(anything(), anything())).thenResolve(connection.object); // Disable UI just lets us skip mocking the progress reporter - const server = await serverProvider.getOrCreateServer({ + const server = await serverProvider.getOrStartServer({ ui: new DisplayOptions(true), resource: undefined, token: source.token diff --git a/src/kernels/jupyter/serviceRegistry.node.ts b/src/kernels/jupyter/serviceRegistry.node.ts index 31ce4cc5b8d..6db1f5e52e0 100644 --- a/src/kernels/jupyter/serviceRegistry.node.ts +++ b/src/kernels/jupyter/serviceRegistry.node.ts @@ -8,7 +8,6 @@ import { IDataScienceErrorHandler } from '../errors/types'; import { IKernelSessionFactory, IJupyterServerConnector } from '../types'; import { JupyterCommandFactory } from './interpreter/jupyterCommand.node'; import { JupyterInterpreterDependencyService } from './interpreter/jupyterInterpreterDependencyService.node'; -import { JupyterInterpreterOldCacheStateStore } from './interpreter/jupyterInterpreterOldCacheStateStore.node'; import { JupyterInterpreterSelectionCommand } from './interpreter/jupyterInterpreterSelectionCommand.node'; import { JupyterInterpreterSelector } from './interpreter/jupyterInterpreterSelector.node'; import { JupyterInterpreterService } from './interpreter/jupyterInterpreterService.node'; @@ -89,10 +88,6 @@ export function registerTypes(serviceManager: IServiceManager, _isDevMode: boole INbConvertExportToPythonService, NbConvertExportToPythonService ); - serviceManager.addSingleton( - JupyterInterpreterOldCacheStateStore, - JupyterInterpreterOldCacheStateStore - ); serviceManager.addSingleton(JupyterInterpreterSelector, JupyterInterpreterSelector); serviceManager.addSingleton(JupyterInterpreterService, JupyterInterpreterService); serviceManager.addSingleton( diff --git a/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts b/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts index dbf968fc9e9..173ef19cfbc 100644 --- a/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts +++ b/src/kernels/jupyter/session/jupyterKernelSessionFactory.ts @@ -72,7 +72,7 @@ export class JupyterKernelSessionFactory implements IKernelSessionFactory { ? await this.jupyterConnection.createConnectionInfo({ serverId: options.kernelConnection.serverId }) - : await this.jupyterNotebookProvider.getOrCreateServer({ + : await this.jupyterNotebookProvider.getOrStartServer({ resource: options.resource, token: options.token, ui: options.ui diff --git a/src/kernels/jupyter/session/kernelSessionFactory.unit.test.ts b/src/kernels/jupyter/session/kernelSessionFactory.unit.test.ts index b8597dedb86..a2adb376b1f 100644 --- a/src/kernels/jupyter/session/kernelSessionFactory.unit.test.ts +++ b/src/kernels/jupyter/session/kernelSessionFactory.unit.test.ts @@ -55,7 +55,7 @@ suite('NotebookProvider', () => { localLaunch: true, baseUrl: 'http://localhost:8888' } as any); - when(jupyterNotebookProvider.getOrCreateServer(anything())).thenResolve({ + when(jupyterNotebookProvider.getOrStartServer(anything())).thenResolve({ localLaunch: true, baseUrl: 'http://localhost:8888' } as any); @@ -75,7 +75,7 @@ suite('NotebookProvider', () => { await asyncDisposables.dispose(); }); test('NotebookProvider getOrCreateNotebook jupyter provider does not have notebook already', async () => { - when(jupyterNotebookProvider.getOrCreateServer(anything())).thenResolve({} as any); + when(jupyterNotebookProvider.getOrStartServer(anything())).thenResolve({} as any); const doc = mock(); when(doc.uri).thenReturn(Uri('C:\\\\foo.py')); const session = await jupyterKernelSessionFactory.create({ @@ -89,7 +89,7 @@ suite('NotebookProvider', () => { }); test('NotebookProvider getOrCreateNotebook second request should return the notebook already cached', async () => { - when(jupyterNotebookProvider.getOrCreateServer(anything())).thenResolve({} as any); + when(jupyterNotebookProvider.getOrStartServer(anything())).thenResolve({} as any); const doc = mock(); when(doc.uri).thenReturn(Uri('C:\\\\foo.py')); diff --git a/src/kernels/jupyter/types.ts b/src/kernels/jupyter/types.ts index 31b7877b37a..9928e6cc78d 100644 --- a/src/kernels/jupyter/types.ts +++ b/src/kernels/jupyter/types.ts @@ -47,9 +47,8 @@ export enum JupyterInterpreterDependencyResponse { export const IJupyterServerHelper = Symbol('JupyterServerHelper'); export interface IJupyterServerHelper extends IAsyncDisposable { isJupyterServerSupported(cancelToken?: CancellationToken): Promise; - connectToNotebookServer(resource: Resource, cancelToken?: CancellationToken): Promise; + startServer(resource: Resource, cancelToken?: CancellationToken): Promise; getUsableJupyterPython(cancelToken?: CancellationToken): Promise; - getJupyterServerConnection(resource: Resource): Promise; getJupyterServerError(): Promise; refreshCommands(): Promise; } @@ -152,7 +151,7 @@ export interface IJupyterServerProvider { * Stats the local Jupyter Notebook server (if not already started) * and returns the connection information. */ - getOrCreateServer(options: GetServerOptions): Promise; + getOrStartServer(options: GetServerOptions): Promise; } export interface IJupyterServerUri { @@ -246,6 +245,10 @@ export interface IJupyterServerUriEntry { * Uri of the server to connect to */ uri: string; + provider?: { + id: string; + handle: JupyterServerUriHandle; + }; /** * Unique ID using a hash of the full uri */ @@ -272,12 +275,12 @@ export interface IJupyterServerUriStorage { /** * Updates MRU list marking this server as the most recently used. */ - updateMRU(serverId: string): Promise; - getMRUs(): Promise; - removeMRU(uri: string): Promise; - clearMRU(): Promise; - getMRU(serverId: string): Promise; - addMRU(uri: string, displayName: string): Promise; + update(serverId: string): Promise; + getAll(): Promise; + remove(uri: string): Promise; + clear(): Promise; + get(serverId: string): Promise; + add(uri: string, displayName: string): Promise; } export interface IBackupFile { diff --git a/src/kernels/kernelAutoReConnectMonitor.ts b/src/kernels/kernelAutoReConnectMonitor.ts index 166df1517ab..bfa56b4ed35 100644 --- a/src/kernels/kernelAutoReConnectMonitor.ts +++ b/src/kernels/kernelAutoReConnectMonitor.ts @@ -23,7 +23,6 @@ import { RemoteKernelConnectionMetadata } from './types'; import { IJupyterServerUriStorage, IJupyterUriProviderRegistration } from './jupyter/types'; -import { extractJupyterServerHandleAndId } from './jupyter/jupyterUtils'; /** * In the case of Jupyter kernels, when a kernel dies Jupyter will automatically restart that kernel. @@ -214,19 +213,13 @@ export class KernelAutoReconnectMonitor implements IExtensionSyncActivationServi kernel: IKernel, metadata: RemoteKernelConnectionMetadata ): Promise { - const uriItem = await this.serverUriStorage.getMRU(metadata.serverId); + const uriItem = await this.serverUriStorage.get(metadata.serverId); - if (!uriItem) { + if (!uriItem?.provider) { return false; } - const idAndHandle = extractJupyterServerHandleAndId(uriItem.uri); - - if (!idAndHandle) { - return false; - } - - const provider = await this.jupyterUriProviderRegistration.getProvider(idAndHandle.id); + const provider = await this.jupyterUriProviderRegistration.getProvider(uriItem.provider.id); if (!provider || !provider.getHandles) { return false; } @@ -234,8 +227,8 @@ export class KernelAutoReconnectMonitor implements IExtensionSyncActivationServi try { const handles = await provider.getHandles(); - if (!handles.includes(idAndHandle.handle)) { - await this.serverUriStorage.removeMRU(uriItem.uri); + if (!handles.includes(uriItem.provider.handle)) { + await this.serverUriStorage.remove(uriItem.uri); this.kernelReconnectProgress.get(kernel)?.dispose(); this.kernelReconnectProgress.delete(kernel); } diff --git a/src/kernels/kernelAutoReConnectMonitor.unit.test.ts b/src/kernels/kernelAutoReConnectMonitor.unit.test.ts index e02cfc172d1..fcf4b03de04 100644 --- a/src/kernels/kernelAutoReConnectMonitor.unit.test.ts +++ b/src/kernels/kernelAutoReConnectMonitor.unit.test.ts @@ -31,7 +31,7 @@ import { KernelAutoReconnectMonitor } from './kernelAutoReConnectMonitor'; import { CellExecutionCreator, NotebookCellExecutionWrapper } from './execution/cellExecutionCreator'; import { mockedVSCodeNamespaces } from '../test/vscode-mock'; import { JupyterNotebookView } from '../platform/common/constants'; -import { IJupyterServerUriStorage, IJupyterUriProviderRegistration } from './jupyter/types'; +import { IJupyterServerUriEntry, IJupyterServerUriStorage, IJupyterUriProviderRegistration } from './jupyter/types'; import { noop } from '../test/core'; suite('Kernel ReConnect Progress Message', () => { @@ -62,7 +62,7 @@ suite('Kernel ReConnect Progress Message', () => { when(kernelProvider.getKernelExecution(anything())).thenReturn(instance(kernelExecution)); clock = fakeTimers.install(); jupyterServerUriStorage = mock(); - when(jupyterServerUriStorage.getMRUs()).thenResolve([]); + when(jupyterServerUriStorage.getAll()).thenResolve([]); jupyterUriProviderRegistration = mock(); disposables.push(new Disposable(() => clock.uninstall())); @@ -170,7 +170,7 @@ suite('Kernel ReConnect Failed Monitor', () => { when(kernelProvider.onDidRestartKernel).thenReturn(onDidRestartKernel.event); when(kernelProvider.getKernelExecution(anything())).thenReturn(instance(kernelExecution)); jupyterServerUriStorage = mock(); - when(jupyterServerUriStorage.getMRUs()).thenResolve([]); + when(jupyterServerUriStorage.getAll()).thenResolve([]); jupyterUriProviderRegistration = mock(); monitor = new KernelAutoReconnectMonitor( instance(appShell), @@ -321,13 +321,17 @@ suite('Kernel ReConnect Failed Monitor', () => { test('Handle contributed server disconnect (server contributed by uri provider)', async () => { const kernel = createKernel(); - const server = { + const server: IJupyterServerUriEntry = { uri: 'https://remote?id=remoteUriProvider&uriHandle=1', serverId: '1234', - time: 1234 + time: 1234, + provider: { + handle: '1', + id: '1' + } }; - when(jupyterServerUriStorage.getMRUs()).thenResolve([server]); - when(jupyterServerUriStorage.getMRU(anything())).thenResolve(server); + when(jupyterServerUriStorage.getAll()).thenResolve([server]); + when(jupyterServerUriStorage.get(server.serverId)).thenResolve(server); when(jupyterUriProviderRegistration.getProvider(anything())).thenResolve({ id: 'remoteUriProvider', getServerUri: (_handle) => diff --git a/src/kernels/types.ts b/src/kernels/types.ts index c27c6fa7e3b..f6cd6a7a63d 100644 --- a/src/kernels/types.ts +++ b/src/kernels/types.ts @@ -528,10 +528,8 @@ export interface IThirdPartyKernelProvider extends IBaseKernelProvider; // Jupyter specific members readonly baseUrl: string; diff --git a/src/notebooks/controllers/connectionDisplayData.ts b/src/notebooks/controllers/connectionDisplayData.ts index b0d9726e1b0..24445ce2534 100644 --- a/src/notebooks/controllers/connectionDisplayData.ts +++ b/src/notebooks/controllers/connectionDisplayData.ts @@ -145,8 +145,7 @@ async function getRemoteServerDisplayName( kernelConnection: RemoteKernelConnectionMetadata, serverUriStorage: IJupyterServerUriStorage ): Promise { - const savedUriList = await serverUriStorage.getMRUs(); - const targetConnection = savedUriList.find((uriEntry) => uriEntry.serverId === kernelConnection.serverId); + const targetConnection = await serverUriStorage.get(kernelConnection.serverId); // We only show this if we have a display name and the name is not the same as the URI (this prevents showing the long token for user entered URIs). if (targetConnection && targetConnection.displayName && targetConnection.uri !== targetConnection.displayName) { diff --git a/src/notebooks/controllers/kernelSource/notebookKernelSourceSelector.ts b/src/notebooks/controllers/kernelSource/notebookKernelSourceSelector.ts index e1d628e9e35..733aedaecd3 100644 --- a/src/notebooks/controllers/kernelSource/notebookKernelSourceSelector.ts +++ b/src/notebooks/controllers/kernelSource/notebookKernelSourceSelector.ts @@ -14,7 +14,7 @@ import { ThemeIcon } from 'vscode'; import { ContributedKernelFinderKind, IContributedKernelFinder } from '../../../kernels/internalTypes'; -import { extractJupyterServerHandleAndId, generateUriFromRemoteProvider } from '../../../kernels/jupyter/jupyterUtils'; +import { generateUriFromRemoteProvider } from '../../../kernels/jupyter/jupyterUtils'; import { JupyterServerSelector } from '../../../kernels/jupyter/connection/serverSelector'; import { IJupyterServerUriStorage, @@ -181,12 +181,6 @@ export class NotebookKernelSourceSelector implements INotebookKernelSourceSelect multiStep: IMultiStepInput, state: MultiStepResult ): Promise | void> { - const savedURIList = await this.serverUriStorage.getMRUs(); - - if (token.isCancellationRequested) { - return; - } - const servers = this.kernelFinder.registered.filter( (info) => info.kind === 'remote' && (info as IRemoteKernelFinder).serverUri.uri ) as IRemoteKernelFinder[]; @@ -194,30 +188,31 @@ export class NotebookKernelSourceSelector implements INotebookKernelSourceSelect for (const server of servers) { // remote server - const savedURI = savedURIList.find((uri) => uri.uri === server.serverUri.uri); - if (savedURI) { - const idAndHandle = extractJupyterServerHandleAndId(savedURI.uri); + const savedURI = await this.serverUriStorage.get(server.serverUri.serverId); + if (token.isCancellationRequested) { + return; + } - if (idAndHandle && idAndHandle.id === provider.id) { - // local server - const uriDate = new Date(savedURI.time); - items.push({ - type: KernelFinderEntityQuickPickType.KernelFinder, - kernelFinderInfo: server, - serverUri: savedURI.uri, - idAndHandle: idAndHandle, - label: server.displayName, - detail: DataScience.jupyterSelectURIMRUDetail(uriDate), - buttons: provider.removeHandle - ? [ - { - iconPath: new ThemeIcon('trash'), - tooltip: DataScience.removeRemoteJupyterServerEntryInQuickPick - } - ] - : [] - }); - } + const idAndHandle = savedURI?.provider; + if (idAndHandle && idAndHandle.id === provider.id) { + // local server + const uriDate = new Date(savedURI.time); + items.push({ + type: KernelFinderEntityQuickPickType.KernelFinder, + kernelFinderInfo: server, + serverUri: savedURI.uri, + idAndHandle, + label: server.displayName, + detail: DataScience.jupyterSelectURIMRUDetail(uriDate), + buttons: provider.removeHandle + ? [ + { + iconPath: new ThemeIcon('trash'), + tooltip: DataScience.removeRemoteJupyterServerEntryInQuickPick + } + ] + : [] + }); } } diff --git a/src/notebooks/controllers/remoteKernelControllerWatcher.ts b/src/notebooks/controllers/remoteKernelControllerWatcher.ts index f0566abe1f6..eab39b71dcb 100644 --- a/src/notebooks/controllers/remoteKernelControllerWatcher.ts +++ b/src/notebooks/controllers/remoteKernelControllerWatcher.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { extractJupyterServerHandleAndId, generateUriFromRemoteProvider } from '../../kernels/jupyter/jupyterUtils'; +import { generateUriFromRemoteProvider } from '../../kernels/jupyter/jupyterUtils'; import { IJupyterServerUriStorage, IJupyterUriProvider, @@ -47,13 +47,13 @@ export class RemoteKernelControllerWatcher implements IExtensionSyncActivationSe if (!provider.getHandles) { return; } - const [handles, uris] = await Promise.all([provider.getHandles(), this.uriStorage.getMRUs()]); + const [handles, uris] = await Promise.all([provider.getHandles(), this.uriStorage.getAll()]); const serverJupyterProviderMap = new Map(); const registeredHandles: string[] = []; await Promise.all( uris.map(async (item) => { // Check if this url is associated with a provider. - const info = extractJupyterServerHandleAndId(item.uri); + const info = item.provider; if (!info || info.id !== provider.id) { return; } @@ -71,9 +71,9 @@ export class RemoteKernelControllerWatcher implements IExtensionSyncActivationSe // If not then remove this uri from the list. if (!handles.includes(info.handle)) { // Looks like the 3rd party provider has updated its handles and this server is no longer available. - await this.uriStorage.removeMRU(item.uri); + await this.uriStorage.remove(item.uri); } else if (!item.isValidated) { - await this.uriStorage.addMRU(item.uri, item.displayName ?? item.uri).catch(noop); + await this.uriStorage.add(item.uri, item.displayName ?? item.uri).catch(noop); } }) ); @@ -85,7 +85,7 @@ export class RemoteKernelControllerWatcher implements IExtensionSyncActivationSe try { const serverUri = await provider.getServerUri(handle); const uri = generateUriFromRemoteProvider(provider.id, handle); - await this.uriStorage.addMRU(uri, serverUri.displayName); + await this.uriStorage.add(uri, serverUri.displayName); } catch (ex) { traceError(`Failed to get server uri and add it to uri Storage for handle ${handle}`, ex); } diff --git a/src/notebooks/controllers/remoteKernelControllerWatcher.unit.test.ts b/src/notebooks/controllers/remoteKernelControllerWatcher.unit.test.ts index 595edd7b63f..4ee67f60a39 100644 --- a/src/notebooks/controllers/remoteKernelControllerWatcher.unit.test.ts +++ b/src/notebooks/controllers/remoteKernelControllerWatcher.unit.test.ts @@ -38,7 +38,7 @@ suite('RemoteKernelControllerWatcher', () => { onDidChangeProviders = new EventEmitter(); disposables.push(onDidChangeProviders); when(uriProviderRegistration.onDidChangeProviders).thenReturn(onDidChangeProviders.event); - when(uriStorage.removeMRU(anything())).thenResolve(); + when(uriStorage.remove(anything())).thenResolve(); watcher = new RemoteKernelControllerWatcher( disposables, instance(uriProviderRegistration), @@ -117,10 +117,29 @@ suite('RemoteKernelControllerWatcher', () => { instance(remoteLiveKernel) ]); - when(uriStorage.getMRUs()).thenResolve([ - { time: 1, serverId, uri: remoteUriForProvider1, displayName: 'Something' } + when(uriStorage.getAll()).thenResolve([ + { + time: 1, + serverId, + uri: remoteUriForProvider1, + displayName: 'Something', + provider: { + handle: provider1Handle1, + id: provider1Id + } + } ]); - when(uriStorage.addMRU(anything(), anything())).thenResolve(); + when(uriStorage.get(serverId)).thenResolve({ + time: 1, + serverId, + uri: remoteUriForProvider1, + displayName: 'Something', + provider: { + handle: provider1Handle1, + id: provider1Id + } + }); + when(uriStorage.add(anything(), anything())).thenResolve(); watcher.activate(); @@ -152,7 +171,7 @@ suite('RemoteKernelControllerWatcher', () => { await onDidChangeHandles!(); assert.isOk(onDidChangeHandles, 'onDidChangeHandles should be defined'); - verify(uriStorage.removeMRU(anything())).never(); + verify(uriStorage.remove(anything())).never(); verify(localKernel.dispose()).never(); verify(remoteKernelSpec.dispose()).never(); verify(remoteLiveKernel.dispose()).never(); @@ -163,7 +182,7 @@ suite('RemoteKernelControllerWatcher', () => { await onDidChangeHandles!(); assert.isOk(onDidChangeHandles, 'onDidChangeHandles should be defined'); - verify(uriStorage.removeMRU(remoteUriForProvider1)).once(); + verify(uriStorage.remove(remoteUriForProvider1)).once(); verify(localKernel.dispose()).never(); verify(remoteKernelSpec.dispose()).once(); verify(remoteLiveKernel.dispose()).once(); diff --git a/src/platform/common/cache.ts b/src/platform/common/cache.ts index ba421a5d8d9..5871dc53d70 100644 --- a/src/platform/common/cache.ts +++ b/src/platform/common/cache.ts @@ -3,25 +3,54 @@ import { Memento } from 'vscode'; import { noop } from './utils/misc'; +import { IExtensionSyncActivationService } from '../activation/types'; +import { IApplicationEnvironment, IWorkspaceService } from './application/types'; +import { GLOBAL_MEMENTO, ICryptoUtils, IMemento } from './types'; +import { inject, injectable, named } from 'inversify'; +import { getFilePath } from './platform/fs-paths'; -export async function removeOldCachedItems(globalState: Memento): Promise { - await Promise.all( - [ - 'currentServerHash', - 'connectToLocalKernelsOnly', - 'JUPYTER_LOCAL_KERNELSPECS', - 'JUPYTER_LOCAL_KERNELSPECS_V1', - 'JUPYTER_LOCAL_KERNELSPECS_V2', - 'JUPYTER_LOCAL_KERNELSPECS_V3', - 'JUPYTER_REMOTE_KERNELSPECS', - 'JUPYTER_REMOTE_KERNELSPECS_V1', - 'JUPYTER_REMOTE_KERNELSPECS_V2', - 'JUPYTER_REMOTE_KERNELSPECS_V3', - 'JUPYTER_LOCAL_KERNELSPECS_V4', - 'LOCAL_KERNEL_SPECS_CACHE_KEY_V_2022_10', - 'LOCAL_KERNEL_PYTHON_AND_RELATED_SPECS_CACHE_KEY_V_2022_10' - ] - .filter((key) => globalState.get(key, undefined) !== undefined) - .map((key) => globalState.update(key, undefined).then(noop, noop)) - ); +@injectable() +export class OldCacheCleaner implements IExtensionSyncActivationService { + constructor( + @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, + @inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalState: Memento, + @inject(ICryptoUtils) private readonly crypto: ICryptoUtils, + @inject(IApplicationEnvironment) private readonly appEnv: IApplicationEnvironment + ) {} + public activate(): void { + this.removeOldCachedItems().then(noop, noop); + } + async removeOldCachedItems(): Promise { + await Promise.all( + [ + await this.getUriAccountKey(), + 'currentServerHash', + 'connectToLocalKernelsOnly', + 'JUPYTER_LOCAL_KERNELSPECS', + 'JUPYTER_LOCAL_KERNELSPECS_V1', + 'JUPYTER_LOCAL_KERNELSPECS_V2', + 'JUPYTER_LOCAL_KERNELSPECS_V3', + 'JUPYTER_REMOTE_KERNELSPECS', + 'JUPYTER_REMOTE_KERNELSPECS_V1', + 'JUPYTER_REMOTE_KERNELSPECS_V2', + 'JUPYTER_REMOTE_KERNELSPECS_V3', + 'JUPYTER_LOCAL_KERNELSPECS_V4', + 'LOCAL_KERNEL_SPECS_CACHE_KEY_V_2022_10', + 'LOCAL_KERNEL_PYTHON_AND_RELATED_SPECS_CACHE_KEY_V_2022_10' + ] + .filter((key) => this.globalState.get(key, undefined) !== undefined) + .map((key) => this.globalState.update(key, undefined).then(noop, noop)) + ); + } + + async getUriAccountKey(): Promise { + if (this.workspace.rootFolder) { + // Folder situation + return this.crypto.createHash(getFilePath(this.workspace.rootFolder), 'SHA-512'); + } else if (this.workspace.workspaceFile) { + // Workspace situation + return this.crypto.createHash(getFilePath(this.workspace.workspaceFile), 'SHA-512'); + } + return this.appEnv.machineId; // Global key when no folder or workspace file + } } diff --git a/src/platform/common/serviceRegistry.node.ts b/src/platform/common/serviceRegistry.node.ts index 86dd6321b5a..f2656565a93 100644 --- a/src/platform/common/serviceRegistry.node.ts +++ b/src/platform/common/serviceRegistry.node.ts @@ -47,6 +47,7 @@ import { registerTypes as processRegisterTypes } from './process/serviceRegistry import { registerTypes as variableRegisterTypes } from './variables/serviceRegistry.node'; import { RunInDedicatedExtensionHostCommandHandler } from './application/commands/runInDedicatedExtensionHost.node'; import { TerminalManager } from './application/terminalManager.node'; +import { OldCacheCleaner } from './cache'; // eslint-disable-next-line export function registerTypes(serviceManager: IServiceManager) { @@ -72,6 +73,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IAsyncDisposableRegistry, AsyncDisposableRegistry); serviceManager.addSingleton(IMultiStepInputFactory, MultiStepInputFactory); serviceManager.addSingleton(IExtensionSyncActivationService, LanguageInitializer); + serviceManager.addSingleton(IExtensionSyncActivationService, OldCacheCleaner); serviceManager.addSingleton( IExtensionSyncActivationService, ReloadVSCodeCommandHandler diff --git a/src/platform/common/serviceRegistry.web.ts b/src/platform/common/serviceRegistry.web.ts index 3e373ac397c..7fce947c2a7 100644 --- a/src/platform/common/serviceRegistry.web.ts +++ b/src/platform/common/serviceRegistry.web.ts @@ -33,6 +33,8 @@ import { DebugService } from './application/debugService'; import { HttpClient } from './net/httpClient'; import { DataFrameScriptGenerator } from '../interpreter/dataFrameScriptGenerator'; import { VariableScriptGenerator } from '../interpreter/variableScriptGenerator'; +import { IExtensionSyncActivationService } from '../activation/types'; +import { OldCacheCleaner } from './cache'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingletonInstance(IsWindows, false); @@ -52,6 +54,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IHttpClient, HttpClient); serviceManager.addSingleton(IDataFrameScriptGenerator, DataFrameScriptGenerator); serviceManager.addSingleton(IVariableScriptGenerator, VariableScriptGenerator); + serviceManager.addSingleton(IExtensionSyncActivationService, OldCacheCleaner); registerPlatformTypes(serviceManager); } diff --git a/src/standalone/userJupyterServer/userServerUrlProvider.ts b/src/standalone/userJupyterServer/userServerUrlProvider.ts index 6a5024d7aaf..9740730b483 100644 --- a/src/standalone/userJupyterServer/userServerUrlProvider.ts +++ b/src/standalone/userJupyterServer/userServerUrlProvider.ts @@ -15,7 +15,6 @@ import { window } from 'vscode'; import { JupyterConnection } from '../../kernels/jupyter/connection/jupyterConnection'; -import { extractJupyterServerHandleAndId } from '../../kernels/jupyter/jupyterUtils'; import { validateSelectJupyterURI } from '../../kernels/jupyter/connection/serverSelector'; import { IJupyterServerUri, @@ -88,10 +87,10 @@ export class UserJupyterServerUrlProvider implements IExtensionSyncActivationSer this._initializeCachedServerInfo() .then(async () => { // once cache is initialized, check if we should do migration - const existingServers = await this.serverUriStorage.getMRUs(); + const existingServers = await this.serverUriStorage.getAll(); const migratedServers = []; for (const server of existingServers) { - const info = extractJupyterServerHandleAndId(server.uri); + const info = server.provider; if (info) { continue; } diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index 2788e76b224..081b3b87759 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -347,13 +347,9 @@ async function shutdownRemoteKernels() { const cancelToken = new CancellationTokenSource(); let sessionManager: IJupyterSessionManager | undefined; try { - const uris = await serverUriStorage.getMRUs(); const connection = await jupyterConnection.createConnectionInfo({ - serverId: uris[0].serverId + serverId: (await serverUriStorage.getAll())[0].serverId }); - if (connection.type !== 'jupyter') { - return; - } const sessionManager = await jupyterSessionManagerFactory.create(connection); const liveKernels = await sessionManager.getRunningKernels(); await Promise.all( diff --git a/src/test/datascience/notebook/kernelSelection.vscode.test.ts b/src/test/datascience/notebook/kernelSelection.vscode.test.ts index 1e1f01c9095..d7481a21ed1 100644 --- a/src/test/datascience/notebook/kernelSelection.vscode.test.ts +++ b/src/test/datascience/notebook/kernelSelection.vscode.test.ts @@ -175,7 +175,7 @@ suite('Kernel Selection @kernelPicker', function () { await closeNotebooksAndCleanUpAfterTests(disposables); console.log(`End test completed ${this.currentTest?.title}`); if (jupyterServerUri) { - await serverUriStorage.addMRU(jupyterServerUri, ''); + await serverUriStorage.add(jupyterServerUri, ''); } });