Skip to content

Commit acaba16

Browse files
committed
Swap controllers once kernel starts
1 parent a520b86 commit acaba16

25 files changed

+761
-127
lines changed

.github/workflows/build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ jobs:
291291
matrix:
292292
jupyter: [raw, local]
293293
python: [nonConda, conda]
294-
pythonVersion: ['3.9', '3.10']
295294
webOrDesktop: ['']
295+
pythonVersion: ['3.9', '3.10']
296296
# Whether we're using stable (<empty>) or pre-release versions of Python packages.
297297
# When installing pre-release versions, we're only focused on jupyter & related packages.
298298
# Not pre-release versions of pandas, numpy or other such packages that are not core to Jupyter.

TELEMETRY.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4311,7 +4311,7 @@ No properties for event
43114311
const telemetryEvent = isLocalConnection(this.kernelConnection)
43124312
? Telemetry.SelectLocalJupyterKernel
43134313
: Telemetry.SelectRemoteJupyterKernel;
4314-
sendKernelTelemetryEvent(document.uri, telemetryEvent);
4314+
sendKernelTelemetryEvent(notebook.uri, telemetryEvent);
43154315
this.notebookApi.notebookEditors
43164316
```
43174317

@@ -4338,9 +4338,9 @@ No properties for event
43384338
const telemetryEvent = isLocalConnection(this.kernelConnection)
43394339
? Telemetry.SelectLocalJupyterKernel
43404340
: Telemetry.SelectRemoteJupyterKernel;
4341-
sendKernelTelemetryEvent(document.uri, telemetryEvent);
4341+
sendKernelTelemetryEvent(notebook.uri, telemetryEvent);
43424342
this.notebookApi.notebookEditors
4343-
.filter((editor) => editor.notebook === document)
4343+
.filter((editor) => editor.notebook === notebook)
43444344
```
43454345

43464346
</details>
@@ -8959,7 +8959,7 @@ No properties for event
89598959
default:
89608960
// We don't know as its the default kernel on Jupyter server.
89618961
}
8962-
sendKernelTelemetryEvent(document.uri, Telemetry.SwitchKernel);
8962+
sendKernelTelemetryEvent(notebook.uri, Telemetry.SwitchKernel);
89638963
// If we have an existing kernel, then we know for a fact the user is changing the kernel.
89648964
// Else VSC is just setting a kernel for a notebook after it has opened.
89658965
if (existingKernel) {

news/2 Fixes/10572.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure the kernel picker points to the live Kernel Connection when starting a new remote kernel.

src/interactive-window/interactiveWindow.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { IConfigurationService, InteractiveWindowMode, IsWebExtension, Resource
3333
import { noop } from '../platform/common/utils/misc';
3434
import {
3535
IKernel,
36+
IKernelProvider,
3637
isLocalConnection,
3738
KernelAction,
3839
KernelConnectionMetadata,
@@ -42,7 +43,6 @@ import { chainable } from '../platform/common/utils/decorators';
4243
import { InteractiveCellResultError } from '../platform/errors/interactiveCellResultError';
4344
import { DataScience } from '../platform/common/utils/localize';
4445
import { createDeferred, Deferred } from '../platform/common/utils/async';
45-
import { IServiceContainer } from '../platform/ioc/types';
4646
import { SysInfoReason } from '../messageTypes';
4747
import { createOutputWithErrorMessageForDisplay } from '../platform/errors/errorUtils';
4848
import { INotebookExporter } from '../kernels/jupyter/types';
@@ -55,15 +55,13 @@ import {
5555
IInteractiveWindowDebuggingManager,
5656
InteractiveTab
5757
} from './types';
58-
import { generateInteractiveCode, isInteractiveInputTab } from './helpers';
58+
import { generateInteractiveCode, getInteractiveCellMetadata, isInteractiveInputTab } from './helpers';
5959
import {
6060
IControllerRegistration,
6161
IControllerSelection,
6262
IVSCodeNotebookController
6363
} from '../notebooks/controllers/types';
6464
import { DisplayOptions } from '../kernels/displayOptions';
65-
import { getInteractiveCellMetadata } from './helpers';
66-
import { KernelConnector } from '../notebooks/controllers/kernelConnector';
6765
import { getFilePath } from '../platform/common/platform/fs-paths';
6866
import {
6967
ICodeGeneratorFactory,
@@ -76,6 +74,8 @@ import { updateNotebookMetadata } from '../kernels/execution/helpers';
7674
import { chainWithPendingUpdates } from '../kernels/execution/notebookUpdater';
7775
import { initializeInteractiveOrNotebookTelemetryBasedOnUserAction } from '../kernels/telemetry/helper';
7876
import { generateMarkdownFromCodeLines, parseForComments } from '../platform/common/utils';
77+
import { IServiceContainer } from '../platform/ioc/types';
78+
import { KernelConnector } from '../notebooks/controllers/kernelConnector';
7979

8080
/**
8181
* ViewModel for an interactive window from the Jupyter extension's point of view.
@@ -111,7 +111,8 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
111111
private kernelDisposables: Disposable[] = [];
112112
private _insertSysInfoPromise: Promise<NotebookCell> | undefined;
113113
private currentKernelInfo: {
114-
kernel?: Deferred<IKernel>;
114+
kernelStarted?: Deferred<void>;
115+
kernel?: IKernel;
115116
controller?: NotebookController;
116117
metadata?: KernelConnectionMetadata;
117118
} = {};
@@ -141,6 +142,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
141142
private readonly debuggingManager: IInteractiveWindowDebuggingManager;
142143
private readonly isWebExtension: boolean;
143144
private readonly commandManager: ICommandManager;
145+
private readonly kernelProvider: IKernelProvider;
144146
private readonly controllerRegistration: IControllerRegistration;
145147
constructor(
146148
private readonly serviceContainer: IServiceContainer,
@@ -163,6 +165,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
163165
this.errorHandler = this.serviceContainer.get<IDataScienceErrorHandler>(IDataScienceErrorHandler);
164166
this.codeGeneratorFactory = this.serviceContainer.get<ICodeGeneratorFactory>(ICodeGeneratorFactory);
165167
this.storageFactory = this.serviceContainer.get<IGeneratedCodeStorageFactory>(IGeneratedCodeStorageFactory);
168+
this.kernelProvider = this.serviceContainer.get<IKernelProvider>(IKernelProvider);
166169
this.debuggingManager = this.serviceContainer.get<IInteractiveWindowDebuggingManager>(
167170
IInteractiveWindowDebuggingManager
168171
);
@@ -249,32 +252,40 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
249252
// This cannot happen, but we need to make typescript happy.
250253
throw new Error('Controller not selected');
251254
}
252-
if (this.currentKernelInfo.kernel) {
253-
return this.currentKernelInfo.kernel.promise;
255+
if (this.currentKernelInfo.kernelStarted) {
256+
await this.currentKernelInfo.kernelStarted.promise;
257+
return this.currentKernelInfo.kernel!;
254258
}
255-
const kernelPromise = createDeferred<IKernel>();
256-
kernelPromise.promise.catch(noop);
257-
this.currentKernelInfo = { controller, metadata, kernel: kernelPromise };
259+
const kernelStarted = createDeferred<void>();
260+
kernelStarted.promise.catch(noop);
261+
this.currentKernelInfo = { controller, metadata, kernelStarted };
258262

259263
const sysInfoCell = this.insertSysInfoMessage(metadata, SysInfoReason.Start);
260264
try {
261265
// Try creating a kernel
262266
initializeInteractiveOrNotebookTelemetryBasedOnUserAction(this.owner, metadata);
263267

264-
const onStartKernel = (action: KernelAction, k: IKernel) => {
268+
const onKernelStarted = async (action: KernelAction, k: IKernel) => {
265269
if (action !== 'start' && action !== 'restart') {
266270
return;
267271
}
268272
// Id may be different if the user switched controllers
269-
this.currentKernelInfo.controller = k.controller;
270-
this.currentKernelInfo.metadata = k.kernelConnectionMetadata;
273+
this.currentKernelInfo.kernel = k;
271274
!!this.pendingCellAdd && this.setPendingCellAdd(this.pendingCellAdd);
272275
this.updateSysInfoMessage(
273276
this.getSysInfoMessage(k.kernelConnectionMetadata, SysInfoReason.Start),
274277
false,
275278
sysInfoCell
276279
);
277280
};
281+
const onKernelStartCompleted = async (action: KernelAction, _: unknown, k: IKernel) => {
282+
if (action !== 'start' && action !== 'restart') {
283+
return;
284+
}
285+
// Id may be different if the user switched controllers
286+
this.currentKernelInfo.controller = k.controller;
287+
this.currentKernelInfo.metadata = k.kernelConnectionMetadata;
288+
};
278289
// When connecting, we need to update the sys info message
279290
this.updateSysInfoMessage(this.getSysInfoMessage(metadata, SysInfoReason.Start), false, sysInfoCell);
280291
const kernel = await KernelConnector.connectToNotebookKernel(
@@ -284,7 +295,8 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
284295
new DisplayOptions(false),
285296
this.internalDisposables,
286297
'jupyterExtension',
287-
onStartKernel
298+
onKernelStarted,
299+
onKernelStartCompleted
288300
);
289301
this.currentKernelInfo.controller = kernel.controller;
290302
this.currentKernelInfo.metadata = kernel.kernelConnectionMetadata;
@@ -324,10 +336,11 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
324336
this.fileInKernel = undefined;
325337
await this.runInitialization(kernel, this.owner);
326338
this.finishSysInfoMessage(kernel, sysInfoCell, SysInfoReason.Start);
327-
kernelPromise.resolve(kernel);
339+
kernelStarted.resolve();
328340
return kernel;
329341
} catch (ex) {
330-
kernelPromise.reject(ex);
342+
kernelStarted.reject(ex);
343+
this.currentKernelInfo.kernelStarted = undefined;
331344
this.currentKernelInfo.kernel = undefined;
332345
this.disconnectKernel();
333346
if (this.owner) {
@@ -443,6 +456,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
443456
message = message.split('\n').join(' \n');
444457
this.updateSysInfoMessage(message, true, cellPromise);
445458
}
459+
446460
private listenForControllerSelection() {
447461
// Ensure we hear about any controller changes so we can update our cached promises
448462
this.notebookControllerSelection.onControllerSelected(
@@ -452,7 +466,13 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
452466
}
453467

454468
// Clear cached kernel when the selected controller for this document changes
455-
if (e.controller.id !== this.currentKernelInfo.controller?.id) {
469+
const kernel = this.kernelProvider.get(this.notebookDocument.uri);
470+
const isControllerAttachedToTheSameKernel =
471+
kernel && this.currentKernelInfo.kernel && kernel === this.currentKernelInfo.kernel;
472+
if (
473+
e.controller.connection.id !== this.currentKernelInfo.kernel?.kernelConnectionMetadata.id &&
474+
!isControllerAttachedToTheSameKernel
475+
) {
456476
this.disconnectKernel();
457477
this.startKernel(e.controller.controller, e.controller.connection).ignoreErrors();
458478
}
@@ -621,6 +641,7 @@ export class InteractiveWindow implements IInteractiveWindowLoadable {
621641
private disconnectKernel() {
622642
this.kernelDisposables.forEach((d) => d.dispose());
623643
this.kernelDisposables = [];
644+
this.currentKernelInfo.kernelStarted = undefined;
624645
this.currentKernelInfo.kernel = undefined;
625646
}
626647

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { KernelConnectionMetadata } from './types';
5+
6+
export class KernelConnectionMetadataProxy {
7+
public get kernelModel() {
8+
return 'kernelModel' in this.metadata ? this.metadata['kernelModel'] : undefined;
9+
}
10+
public get interpreter() {
11+
return this.metadata.interpreter;
12+
}
13+
public get kernelSpec() {
14+
return 'kernelSpec' in this.metadata ? this.metadata['kernelSpec'] : undefined;
15+
}
16+
public get kind() {
17+
return this.metadata.kind;
18+
}
19+
public get baseUrl() {
20+
return 'baseUrl' in this.metadata ? this.metadata['baseUrl'] : undefined;
21+
}
22+
public get serverId() {
23+
return 'serverId' in this.metadata ? this.metadata['serverId'] : undefined;
24+
}
25+
public get id() {
26+
return this.metadata.id;
27+
}
28+
private constructor(public metadata: KernelConnectionMetadata) {
29+
this.metadata = metadata;
30+
}
31+
public update(metadata: KernelConnectionMetadata) {
32+
this.metadata = metadata;
33+
}
34+
public toString() {
35+
return JSON.stringify(this.toJSON());
36+
}
37+
public toJSON() {
38+
return {
39+
kernelModel: this.kernelModel,
40+
kernelSpec: this.kernelSpec,
41+
interpreter: this.interpreter,
42+
kind: this.kind,
43+
baseUrl: this.baseUrl,
44+
serverId: this.serverId,
45+
id: this.id
46+
};
47+
}
48+
static isWrapped(
49+
metadata: KernelConnectionMetadata | KernelConnectionMetadataProxy
50+
): metadata is KernelConnectionMetadataProxy {
51+
if (metadata instanceof KernelConnectionMetadataProxy) {
52+
return true;
53+
}
54+
return false;
55+
}
56+
static wrap(metadata: KernelConnectionMetadata): KernelConnectionMetadata {
57+
if (metadata instanceof KernelConnectionMetadataProxy) {
58+
return metadata;
59+
}
60+
return new KernelConnectionMetadataProxy(metadata) as unknown as KernelConnectionMetadata;
61+
}
62+
}

src/kernels/kernelProvider.base.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
'use strict';
55
import type { KernelMessage } from '@jupyterlab/services';
6-
import { Event, EventEmitter, NotebookDocument, Uri } from 'vscode';
6+
import { Event, EventEmitter, NotebookController, NotebookDocument, Uri } from 'vscode';
77
import { IVSCodeNotebook } from '../platform/common/application/types';
88
import { traceInfoIfCI, traceVerbose, traceWarning } from '../platform/logging';
99
import { getDisplayPath } from '../platform/common/platform/fs-paths';
@@ -15,8 +15,11 @@ import {
1515
IKernel,
1616
KernelOptions,
1717
IThirdPartyKernelProvider,
18-
ThirdPartyKernelOptions
18+
ThirdPartyKernelOptions,
19+
KernelConnectionMetadata
1920
} from './types';
21+
import { NotebookControllerWrapper } from './notebookControllerWrapper';
22+
import { KernelConnectionMetadataProxy } from './kernelConnectionMetadataWrapper';
2023

2124
/**
2225
* Provides kernels to the system. Generally backed by a URI or a notebook object.
@@ -137,6 +140,26 @@ export abstract class BaseCoreKernelProvider implements IKernelProvider {
137140
}
138141
this.kernelsByNotebook.delete(notebook);
139142
}
143+
updateKernel(kernel: IKernel, metadata: KernelConnectionMetadata, controller: NotebookController): void {
144+
const controllerWrapper = kernel.controller;
145+
if (NotebookControllerWrapper.isWrapped(controllerWrapper)) {
146+
controllerWrapper.update(controller);
147+
} else {
148+
throw new Error('IKernel instantiated without passing a NotebookControllerWrapper');
149+
}
150+
const metadataWrapper = kernel.kernelConnectionMetadata;
151+
if (KernelConnectionMetadataProxy.isWrapped(metadataWrapper)) {
152+
metadataWrapper.update(metadata);
153+
} else {
154+
throw new Error('IKernel instantiated without passing a KernelConnectionMetadataWrapper');
155+
}
156+
157+
const info = this.kernelsByNotebook.get(kernel.notebook);
158+
if (info) {
159+
info.options.controller = controller;
160+
info.options.metadata = metadata;
161+
}
162+
}
140163
}
141164

142165
export abstract class BaseThirdPartyKernelProvider implements IThirdPartyKernelProvider {

src/kernels/kernelProvider.node.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import {
2727
KernelOptions,
2828
ThirdPartyKernelOptions
2929
} from './types';
30+
import { KernelConnectionMetadataProxy } from './kernelConnectionMetadataWrapper';
31+
import { NotebookControllerWrapper } from './notebookControllerWrapper';
3032

3133
/**
3234
* Node version of a kernel provider. Needed in order to create the node version of a kernel.
@@ -65,12 +67,12 @@ export class KernelProvider extends BaseCoreKernelProvider {
6567
uri,
6668
resourceUri,
6769
notebook,
68-
options.metadata,
70+
KernelConnectionMetadataProxy.wrap(options.metadata),
6971
this.notebookProvider,
7072
waitForIdleTimeout,
7173
interruptTimeout,
7274
this.appShell,
73-
options.controller,
75+
NotebookControllerWrapper.wrap(options.controller),
7476
this.configService,
7577
this.outputTracker,
7678
this.statusProvider,

src/kernels/kernelProvider.web.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
KernelOptions,
2626
ThirdPartyKernelOptions
2727
} from './types';
28+
import { NotebookControllerWrapper } from './notebookControllerWrapper';
29+
import { KernelConnectionMetadataProxy } from './kernelConnectionMetadataWrapper';
2830

2931
/**
3032
* Web version of a kernel provider. Needed in order to create the web version of a kernel.
@@ -62,12 +64,12 @@ export class KernelProvider extends BaseCoreKernelProvider {
6264
uri,
6365
resourceUri,
6466
notebook,
65-
options.metadata,
67+
KernelConnectionMetadataProxy.wrap(options.metadata),
6668
this.notebookProvider,
6769
waitForIdleTimeout,
6870
interruptTimeout,
6971
this.appShell,
70-
options.controller,
72+
NotebookControllerWrapper.wrap(options.controller),
7173
this.configService,
7274
this.outputTracker,
7375
this.statusProvider,

0 commit comments

Comments
 (0)