Skip to content

Commit 28c1f2d

Browse files
committed
chore: second part of code review comments
1 parent 15280c6 commit 28c1f2d

File tree

7 files changed

+116
-21
lines changed

7 files changed

+116
-21
lines changed

src/kernels/deepnote/deepnoteServerProvider.node.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import { IJupyterServerProviderRegistry } from '../jupyter/types';
1010
import { JVSC_EXTENSION_ID } from '../../platform/common/constants';
1111
import { logger } from '../../platform/logging';
1212
import { DeepnoteServerNotFoundError } from '../../platform/errors/deepnoteServerNotFoundError';
13-
import { DeepnoteServerInfo } from './types';
13+
import { DeepnoteServerInfo, IDeepnoteServerProvider } from './types';
1414

1515
/**
1616
* Jupyter Server Provider for Deepnote kernels.
1717
* This provider resolves server connections for Deepnote kernels.
1818
*/
1919
@injectable()
20-
export class DeepnoteServerProvider implements IExtensionSyncActivationService, JupyterServerProvider {
20+
export class DeepnoteServerProvider
21+
implements IDeepnoteServerProvider, IExtensionSyncActivationService, JupyterServerProvider
22+
{
2123
public readonly id = 'deepnote-server';
2224
private readonly _onDidChangeServers = new EventEmitter<void>();
2325
public readonly onDidChangeServers: Event<void> = this._onDidChangeServers.event;
@@ -53,6 +55,28 @@ export class DeepnoteServerProvider implements IExtensionSyncActivationService,
5355
this._onDidChangeServers.fire();
5456
}
5557

58+
/**
59+
* Unregister a server for a specific handle.
60+
* Called when the server is no longer needed or notebook is closed.
61+
* No-op if the handle doesn't exist.
62+
*/
63+
public unregisterServer(handle: string): void {
64+
if (this.servers.has(handle)) {
65+
logger.info(`Unregistering Deepnote server: ${handle}`);
66+
this.servers.delete(handle);
67+
this._onDidChangeServers.fire();
68+
}
69+
}
70+
71+
/**
72+
* Dispose of all servers and resources.
73+
*/
74+
public dispose(): void {
75+
logger.info('Disposing Deepnote server provider, clearing all registered servers');
76+
this.servers.clear();
77+
this._onDidChangeServers.dispose();
78+
}
79+
5680
/**
5781
* Provides the list of available Deepnote servers.
5882
*/

src/kernels/deepnote/deepnoteToolkitInstaller.node.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ export class DeepnoteToolkitInstaller implements IDeepnoteToolkitInstaller {
8484
return existingVenv;
8585
}
8686

87+
// Double-check for race condition: another caller might have started installation
88+
// while we were checking the venv
89+
const pendingAfterCheck = this.pendingInstallations.get(venvKey);
90+
if (pendingAfterCheck) {
91+
logger.info(`Another installation started for ${venvKey} while checking, waiting for it...`);
92+
try {
93+
return await pendingAfterCheck;
94+
} catch {
95+
logger.info(`Concurrent installation for ${venvKey} failed, retrying...`);
96+
}
97+
}
98+
8799
// Start the installation and track it
88100
const installation = this.installImpl(baseInterpreter, deepnoteFileUri, venvPath, token);
89101
this.pendingInstallations.set(venvKey, installation);
@@ -127,18 +139,21 @@ export class DeepnoteToolkitInstaller implements IDeepnoteToolkitInstaller {
127139
throwOnStdErr: false
128140
});
129141

130-
if (venvResult.stderr && !venvResult.stderr.includes('WARNING')) {
131-
logger.error(`Failed to create venv: ${venvResult.stderr}`);
132-
this.outputChannel.appendLine(`Error creating venv: ${venvResult.stderr}`);
133-
return undefined;
142+
// Log any stderr output (warnings, etc.) but don't fail on it
143+
if (venvResult.stderr) {
144+
logger.info(`venv creation stderr: ${venvResult.stderr}`);
134145
}
135146

136147
Cancellation.throwIfCanceled(token);
137148

138-
// Get venv Python interpreter
149+
// Verify venv was created successfully by checking for the Python interpreter
139150
const venvInterpreter = await this.getVenvInterpreter(deepnoteFileUri);
140151
if (!venvInterpreter) {
141-
logger.error('Failed to locate venv Python interpreter');
152+
logger.error('Failed to create venv: Python interpreter not found after venv creation');
153+
if (venvResult.stderr) {
154+
logger.error(`venv stderr: ${venvResult.stderr}`);
155+
}
156+
this.outputChannel.appendLine('Error: Failed to create virtual environment');
142157
return undefined;
143158
}
144159

src/kernels/deepnote/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ export interface DeepnoteServerInfo {
115115
token?: string;
116116
}
117117

118+
export const IDeepnoteServerProvider = Symbol('IDeepnoteServerProvider');
119+
export interface IDeepnoteServerProvider {
120+
/**
121+
* Register a server for a specific handle.
122+
* Called by DeepnoteKernelAutoSelector when a server is started.
123+
*/
124+
registerServer(handle: string, serverInfo: DeepnoteServerInfo): void;
125+
126+
/**
127+
* Unregister a server for a specific handle.
128+
* Called when the server is no longer needed or notebook is closed.
129+
* No-op if the handle doesn't exist.
130+
*/
131+
unregisterServer(handle: string): void;
132+
}
133+
118134
export const IDeepnoteKernelAutoSelector = Symbol('IDeepnoteKernelAutoSelector');
119135
export interface IDeepnoteKernelAutoSelector {
120136
/**

src/notebooks/controllers/controllerRegistration.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
} from './types';
3131
import { VSCodeNotebookController } from './vscodeNotebookController';
3232
import { IJupyterVariablesProvider } from '../../kernels/variables/types';
33+
import { DEEPNOTE_NOTEBOOK_TYPE } from '../../kernels/deepnote/types';
3334

3435
/**
3536
* Keeps track of registered controllers and available KernelConnectionMetadatas.
@@ -236,14 +237,14 @@ export class ControllerRegistration implements IControllerRegistration, IExtensi
236237
}
237238
addOrUpdate(
238239
metadata: KernelConnectionMetadata,
239-
types: ('jupyter-notebook' | 'interactive' | 'deepnote')[]
240+
types: (typeof JupyterNotebookView | typeof InteractiveWindowView | typeof DEEPNOTE_NOTEBOOK_TYPE)[]
240241
): IVSCodeNotebookController[] {
241242
const { added, existing } = this.addImpl(metadata, types, true);
242243
return added.concat(existing);
243244
}
244245
addImpl(
245246
metadata: KernelConnectionMetadata,
246-
types: ('jupyter-notebook' | 'interactive' | 'deepnote')[],
247+
types: (typeof JupyterNotebookView | typeof InteractiveWindowView | typeof DEEPNOTE_NOTEBOOK_TYPE)[],
247248
triggerChangeEvent: boolean
248249
): { added: IVSCodeNotebookController[]; existing: IVSCodeNotebookController[] } {
249250
const added: IVSCodeNotebookController[] = [];

src/notebooks/controllers/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { IDisposable } from '../../platform/common/types';
1616
import { JupyterServerCollection } from '../../api';
1717
import { EnvironmentPath } from '@vscode/python-extension';
1818
import type { VSCodeNotebookController } from './vscodeNotebookController';
19+
import { DEEPNOTE_NOTEBOOK_TYPE } from '../../kernels/deepnote/types';
1920

2021
export const InteractiveControllerIdSuffix = ' (Interactive)';
2122

@@ -83,7 +84,7 @@ export interface IControllerRegistration {
8384
*/
8485
addOrUpdate(
8586
metadata: KernelConnectionMetadata,
86-
types: (typeof JupyterNotebookView | typeof InteractiveWindowView | 'deepnote')[]
87+
types: (typeof JupyterNotebookView | typeof InteractiveWindowView | typeof DEEPNOTE_NOTEBOOK_TYPE)[]
8788
): IVSCodeNotebookController[];
8889
/**
8990
* Gets the controller for a particular connection
@@ -92,7 +93,7 @@ export interface IControllerRegistration {
9293
*/
9394
get(
9495
connection: KernelConnectionMetadata,
95-
notebookType: typeof JupyterNotebookView | typeof InteractiveWindowView | 'deepnote'
96+
notebookType: typeof JupyterNotebookView | typeof InteractiveWindowView | typeof DEEPNOTE_NOTEBOOK_TYPE
9697
): IVSCodeNotebookController | undefined;
9798
}
9899

src/notebooks/deepnote/deepnoteKernelAutoSelector.node.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
IDeepnoteKernelAutoSelector,
1212
IDeepnoteServerStarter,
1313
IDeepnoteToolkitInstaller,
14+
IDeepnoteServerProvider,
1415
DEEPNOTE_NOTEBOOK_TYPE,
1516
DeepnoteKernelConnectionMetadata
1617
} from '../../kernels/deepnote/types';
@@ -19,7 +20,6 @@ import { JVSC_EXTENSION_ID } from '../../platform/common/constants';
1920
import { getDisplayPath } from '../../platform/common/platform/fs-paths';
2021
import { JupyterServerProviderHandle } from '../../kernels/jupyter/types';
2122
import { IPythonExtensionChecker } from '../../platform/api/types';
22-
import { DeepnoteServerProvider } from '../../kernels/deepnote/deepnoteServerProvider.node';
2323
import { JupyterLabHelper } from '../../kernels/jupyter/session/jupyterLabHelper';
2424
import { createJupyterConnectionInfo } from '../../kernels/jupyter/jupyterUtils';
2525
import { IJupyterRequestCreator, IJupyterRequestAgentCreator } from '../../kernels/jupyter/types';
@@ -31,14 +31,17 @@ import { disposeAsync } from '../../platform/common/utils';
3131
*/
3232
@injectable()
3333
export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector, IExtensionSyncActivationService {
34+
// Track server handles per notebook URI for cleanup
35+
private readonly notebookServerHandles = new Map<string, string>();
36+
3437
constructor(
3538
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
3639
@inject(IControllerRegistration) private readonly controllerRegistration: IControllerRegistration,
3740
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
3841
@inject(IDeepnoteToolkitInstaller) private readonly toolkitInstaller: IDeepnoteToolkitInstaller,
3942
@inject(IDeepnoteServerStarter) private readonly serverStarter: IDeepnoteServerStarter,
4043
@inject(IPythonExtensionChecker) private readonly pythonExtensionChecker: IPythonExtensionChecker,
41-
@inject(DeepnoteServerProvider) private readonly serverProvider: DeepnoteServerProvider,
44+
@inject(IDeepnoteServerProvider) private readonly serverProvider: IDeepnoteServerProvider,
4245
@inject(IJupyterRequestCreator) private readonly requestCreator: IJupyterRequestCreator,
4346
@inject(IJupyterRequestAgentCreator)
4447
@optional()
@@ -50,8 +53,13 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
5053
// Listen to notebook open events
5154
workspace.onDidOpenNotebookDocument(this.onDidOpenNotebook, this, this.disposables);
5255

53-
// Handle currently open notebooks
54-
workspace.notebookDocuments.forEach((d) => this.onDidOpenNotebook(d));
56+
// Listen to notebook close events for cleanup
57+
workspace.onDidCloseNotebookDocument(this.onDidCloseNotebook, this, this.disposables);
58+
59+
// Handle currently open notebooks - await all async operations
60+
Promise.all(workspace.notebookDocuments.map((d) => this.onDidOpenNotebook(d))).catch((error) => {
61+
logger.error(`Error handling open notebooks during activation: ${error}`);
62+
});
5563
}
5664

5765
private async onDidOpenNotebook(notebook: NotebookDocument) {
@@ -69,8 +77,34 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
6977
return;
7078
}
7179

72-
// Auto-select Deepnote kernel
73-
await this.ensureKernelSelected(notebook);
80+
// Auto-select Deepnote kernel with error handling
81+
try {
82+
await this.ensureKernelSelected(notebook);
83+
} catch (error) {
84+
logger.error(`Failed to auto-select Deepnote kernel for ${getDisplayPath(notebook.uri)}: ${error}`);
85+
// Don't rethrow - we want activation to continue even if one notebook fails
86+
}
87+
}
88+
89+
private onDidCloseNotebook(notebook: NotebookDocument) {
90+
// Only handle deepnote notebooks
91+
if (notebook.notebookType !== DEEPNOTE_NOTEBOOK_TYPE) {
92+
return;
93+
}
94+
95+
logger.info(`Deepnote notebook closed: ${getDisplayPath(notebook.uri)}`);
96+
97+
// Extract the base file URI to match what we used when registering
98+
const baseFileUri = notebook.uri.with({ query: '', fragment: '' });
99+
const notebookKey = baseFileUri.fsPath;
100+
101+
// Unregister the server if we have a handle for this notebook
102+
const serverHandle = this.notebookServerHandles.get(notebookKey);
103+
if (serverHandle) {
104+
logger.info(`Unregistering server for closed notebook: ${serverHandle}`);
105+
this.serverProvider.unregisterServer(serverHandle);
106+
this.notebookServerHandles.delete(notebookKey);
107+
}
74108
}
75109

76110
public async ensureKernelSelected(notebook: NotebookDocument, token?: CancellationToken): Promise<void> {
@@ -121,6 +155,9 @@ export class DeepnoteKernelAutoSelector implements IDeepnoteKernelAutoSelector,
121155
// Register the server with the provider so it can be resolved
122156
this.serverProvider.registerServer(serverProviderHandle.handle, serverInfo);
123157

158+
// Track the server handle for cleanup when notebook is closed
159+
this.notebookServerHandles.set(baseFileUri.fsPath, serverProviderHandle.handle);
160+
124161
// Connect to the server and get available kernel specs
125162
const connectionInfo = createJupyterConnectionInfo(
126163
serverProviderHandle,

src/notebooks/serviceRegistry.node.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ import { IDeepnoteNotebookManager } from './types';
4646
import {
4747
IDeepnoteToolkitInstaller,
4848
IDeepnoteServerStarter,
49-
IDeepnoteKernelAutoSelector
49+
IDeepnoteKernelAutoSelector,
50+
IDeepnoteServerProvider
5051
} from '../kernels/deepnote/types';
5152
import { DeepnoteToolkitInstaller } from '../kernels/deepnote/deepnoteToolkitInstaller.node';
5253
import { DeepnoteServerStarter } from '../kernels/deepnote/deepnoteServerStarter.node';
@@ -129,8 +130,8 @@ export function registerTypes(serviceManager: IServiceManager, isDevMode: boolea
129130
// Deepnote kernel services
130131
serviceManager.addSingleton<IDeepnoteToolkitInstaller>(IDeepnoteToolkitInstaller, DeepnoteToolkitInstaller);
131132
serviceManager.addSingleton<IDeepnoteServerStarter>(IDeepnoteServerStarter, DeepnoteServerStarter);
132-
serviceManager.addSingleton<DeepnoteServerProvider>(DeepnoteServerProvider, DeepnoteServerProvider);
133-
serviceManager.addBinding(DeepnoteServerProvider, IExtensionSyncActivationService);
133+
serviceManager.addSingleton<IDeepnoteServerProvider>(IDeepnoteServerProvider, DeepnoteServerProvider);
134+
serviceManager.addBinding(IDeepnoteServerProvider, IExtensionSyncActivationService);
134135
serviceManager.addSingleton<IDeepnoteKernelAutoSelector>(IDeepnoteKernelAutoSelector, DeepnoteKernelAutoSelector);
135136
serviceManager.addBinding(IDeepnoteKernelAutoSelector, IExtensionSyncActivationService);
136137

0 commit comments

Comments
 (0)