Skip to content

Commit 4b3111b

Browse files
LiveShare support for raw kernels (microsoft#11560)
1 parent e5ba13f commit 4b3111b

File tree

9 files changed

+237
-112
lines changed

9 files changed

+237
-112
lines changed

news/1 Enhancements/10988.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add basic liveshare support for raw kernels.

src/client/datascience/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,4 +450,6 @@ export namespace LiveShareCommands {
450450
export const guestCheck = 'guestCheck';
451451
export const createNotebook = 'createNotebook';
452452
export const inspect = 'inspect';
453+
export const rawKernelSupported = 'rawKernelSupported';
454+
export const createRawNotebook = 'createRawNotebook';
453455
}

src/client/datascience/interactive-common/notebookProvider.ts

Lines changed: 5 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66
import { inject, injectable } from 'inversify';
77
import { EventEmitter, Uri } from 'vscode';
88
import { IWorkspaceService } from '../../common/application/types';
9-
import { LocalZMQKernel } from '../../common/experimentGroups';
10-
import { traceError, traceInfo } from '../../common/logger';
119
import { IFileSystem } from '../../common/platform/types';
12-
import { IConfigurationService, IDisposableRegistry, IExperimentsManager, Resource } from '../../common/types';
10+
import { IDisposableRegistry, Resource } from '../../common/types';
1311
import { noop } from '../../common/utils/misc';
14-
import { sendTelemetryEvent } from '../../telemetry';
15-
import { Identifiers, Settings, Telemetry } from '../constants';
12+
import { Identifiers } from '../constants';
1613
import {
1714
ConnectNotebookProviderOptions,
1815
GetNotebookOptions,
@@ -30,7 +27,6 @@ import {
3027
export class NotebookProvider implements INotebookProvider {
3128
private readonly notebooks = new Map<string, Promise<INotebook>>();
3229
private _notebookCreated = new EventEmitter<{ identity: Uri; notebook: INotebook }>();
33-
private _zmqSupported: boolean | undefined;
3430
public get activeNotebooks() {
3531
return [...this.notebooks.values()];
3632
}
@@ -41,8 +37,6 @@ export class NotebookProvider implements INotebookProvider {
4137
@inject(IDisposableRegistry) disposables: IDisposableRegistry,
4238
@inject(IRawNotebookProvider) private readonly rawNotebookProvider: IRawNotebookProvider,
4339
@inject(IJupyterNotebookProvider) private readonly jupyterNotebookProvider: IJupyterNotebookProvider,
44-
@inject(IConfigurationService) private readonly configuration: IConfigurationService,
45-
@inject(IExperimentsManager) private readonly experimentsManager: IExperimentsManager,
4640
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService
4741
) {
4842
disposables.push(editorProvider.onDidCloseNotebookEditor(this.onDidCloseNotebookEditor, this));
@@ -57,23 +51,23 @@ export class NotebookProvider implements INotebookProvider {
5751
// Disconnect from the specified provider
5852
public async disconnect(options: ConnectNotebookProviderOptions): Promise<void> {
5953
// Only need to disconnect from actual jupyter servers
60-
if (!(await this.rawKernelSupported())) {
54+
if (!(await this.rawNotebookProvider.supported())) {
6155
return this.jupyterNotebookProvider.disconnect(options);
6256
}
6357
}
6458

6559
// Attempt to connect to our server provider, and if we do, return the connection info
6660
public async connect(options: ConnectNotebookProviderOptions): Promise<INotebookProviderConnection | undefined> {
6761
// Connect to either a jupyter server or a stubbed out raw notebook "connection"
68-
if (await this.rawKernelSupported()) {
62+
if (await this.rawNotebookProvider.supported()) {
6963
return this.rawNotebookProvider.connect();
7064
} else {
7165
return this.jupyterNotebookProvider.connect(options);
7266
}
7367
}
7468

7569
public async getOrCreateNotebook(options: GetNotebookOptions): Promise<INotebook | undefined> {
76-
const rawKernel = await this.rawKernelSupported();
70+
const rawKernel = await this.rawNotebookProvider.supported();
7771

7872
// Check to see if our provider already has this notebook
7973
const notebook = rawKernel
@@ -117,45 +111,6 @@ export class NotebookProvider implements INotebookProvider {
117111
return promise;
118112
}
119113

120-
// Check to see if we have all that we need for supporting raw kernel launch
121-
private async rawKernelSupported(): Promise<boolean> {
122-
const zmqOk = await this.zmqSupported();
123-
124-
return zmqOk && this.localLaunch() && this.experimentsManager.inExperiment(LocalZMQKernel.experiment)
125-
? true
126-
: false;
127-
}
128-
129-
private localLaunch(): boolean {
130-
const settings = this.configuration.getSettings(undefined);
131-
const serverURI: string | undefined = settings.datascience.jupyterServerURI;
132-
133-
if (!serverURI || serverURI.toLowerCase() === Settings.JupyterServerLocalLaunch) {
134-
return true;
135-
}
136-
137-
return false;
138-
}
139-
140-
// Check to see if this machine supports our local ZMQ launching
141-
private async zmqSupported(): Promise<boolean> {
142-
if (this._zmqSupported !== undefined) {
143-
return this._zmqSupported;
144-
}
145-
146-
try {
147-
await import('zeromq');
148-
traceInfo(`ZMQ install verified.`);
149-
this._zmqSupported = true;
150-
} catch (e) {
151-
traceError(`Exception while attempting zmq :`, e);
152-
sendTelemetryEvent(Telemetry.ZMQNotSupported);
153-
this._zmqSupported = false;
154-
}
155-
156-
return this._zmqSupported;
157-
}
158-
159114
// Cache the promise that will return a notebook
160115
private cacheNotebookPromise(identity: Uri, promise: Promise<INotebook>) {
161116
this.notebooks.set(identity.fsPath, promise);

src/client/datascience/raw-kernel/liveshare/guestRawNotebookProvider.ts

Lines changed: 89 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,32 @@ import * as vsls from 'vsls/vscode';
88
import { IApplicationShell, ILiveShareApi, IWorkspaceService } from '../../../common/application/types';
99
import { IFileSystem } from '../../../common/platform/types';
1010
import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry, Resource } from '../../../common/types';
11+
import { createDeferred } from '../../../common/utils/async';
12+
import * as localize from '../../../common/utils/localize';
1113
import { IServiceContainer } from '../../../ioc/types';
12-
import { LiveShare } from '../../constants';
14+
import { LiveShare, LiveShareCommands } from '../../constants';
15+
import { GuestJupyterNotebook } from '../../jupyter/liveshare/guestJupyterNotebook';
1316
import {
1417
LiveShareParticipantDefault,
1518
LiveShareParticipantGuest
1619
} from '../../jupyter/liveshare/liveShareParticipantMixin';
1720
import { ILiveShareParticipant } from '../../jupyter/liveshare/types';
18-
import { INotebook, IRawConnection, IRawNotebookProvider } from '../../types';
21+
import { IDataScience, INotebook, IRawConnection, IRawNotebookProvider } from '../../types';
22+
import { RawConnection } from '../rawNotebookProvider';
1923

2024
export class GuestRawNotebookProvider
2125
extends LiveShareParticipantGuest(LiveShareParticipantDefault, LiveShare.RawNotebookProviderService)
2226
implements IRawNotebookProvider, ILiveShareParticipant {
27+
// Keep track of guest notebooks on this side
28+
private notebooks = new Map<string, Promise<INotebook>>();
29+
private rawConnection = new RawConnection();
30+
2331
constructor(
24-
liveShare: ILiveShareApi,
25-
_disposableRegistry: IDisposableRegistry,
32+
private readonly liveShare: ILiveShareApi,
33+
private readonly dataScience: IDataScience,
34+
private readonly disposableRegistry: IDisposableRegistry,
2635
_asyncRegistry: IAsyncDisposableRegistry,
27-
_configService: IConfigurationService,
36+
private readonly configService: IConfigurationService,
2837
_workspaceService: IWorkspaceService,
2938
_appShell: IApplicationShell,
3039
_fs: IFileSystem,
@@ -34,41 +43,97 @@ export class GuestRawNotebookProvider
3443
}
3544

3645
public async supported(): Promise<boolean> {
37-
// For now just false, but when implemented will message the host
38-
return false;
46+
// Query the host to see if liveshare is supported
47+
const service = await this.waitForService();
48+
let result = false;
49+
if (service) {
50+
result = await service.request(LiveShareCommands.rawKernelSupported, []);
51+
}
52+
53+
return result;
3954
}
4055

4156
public async createNotebook(
42-
_identity: Uri,
43-
_resource: Resource,
57+
identity: Uri,
58+
resource: Resource,
4459
_disableUI: boolean,
45-
_notebookMetadata: nbformat.INotebookMetadata,
60+
notebookMetadata: nbformat.INotebookMetadata,
4661
_cancelToken: CancellationToken
4762
): Promise<INotebook> {
48-
throw new Error('Not implemented');
49-
}
63+
// Remember we can have multiple native editors opened against the same ipynb file.
64+
if (this.notebooks.get(identity.toString())) {
65+
return this.notebooks.get(identity.toString())!;
66+
}
67+
68+
const deferred = createDeferred<INotebook>();
69+
this.notebooks.set(identity.toString(), deferred.promise);
70+
// Tell the host side to generate a notebook for this uri
71+
const service = await this.waitForService();
72+
if (service) {
73+
const resourceString = resource ? resource.toString() : undefined;
74+
const identityString = identity.toString();
75+
const notebookMetadataString = JSON.stringify(notebookMetadata);
76+
await service.request(LiveShareCommands.createRawNotebook, [
77+
resourceString,
78+
identityString,
79+
notebookMetadataString
80+
]);
81+
}
5082

51-
public connect(): Promise<IRawConnection> {
52-
throw new Error('Not implemented');
83+
// Return a new notebook to listen to
84+
const result = new GuestJupyterNotebook(
85+
this.liveShare,
86+
this.disposableRegistry,
87+
this.configService,
88+
resource,
89+
identity,
90+
undefined,
91+
this.dataScience.activationStartTime
92+
);
93+
deferred.resolve(result);
94+
const oldDispose = result.dispose.bind(result);
95+
result.dispose = () => {
96+
this.notebooks.delete(identity.toString());
97+
return oldDispose();
98+
};
99+
100+
return result;
53101
}
54102

55-
public async onSessionChange(_api: vsls.LiveShare | null): Promise<void> {
56-
// Not implemented yet
103+
public async connect(): Promise<IRawConnection> {
104+
return Promise.resolve(this.rawConnection);
57105
}
58106

59-
public async getNotebook(_resource: Uri): Promise<INotebook | undefined> {
60-
throw new Error('Not implemented');
107+
public async onSessionChange(api: vsls.LiveShare | null): Promise<void> {
108+
await super.onSessionChange(api);
109+
110+
this.notebooks.forEach(async (notebook) => {
111+
const guestNotebook = (await notebook) as GuestJupyterNotebook;
112+
if (guestNotebook) {
113+
await guestNotebook.onSessionChange(api);
114+
}
115+
});
61116
}
62117

63-
public async shutdown(): Promise<void> {
64-
throw new Error('Not implemented');
118+
public async getNotebook(resource: Uri): Promise<INotebook | undefined> {
119+
return this.notebooks.get(resource.toString());
65120
}
66121

67-
public dispose(): Promise<void> {
68-
throw new Error('Not implemented');
122+
public async onAttach(api: vsls.LiveShare | null): Promise<void> {
123+
await super.onAttach(api);
124+
125+
if (api) {
126+
const service = await this.waitForService();
127+
128+
// Wait for sync up
129+
const synced = service ? await service.request(LiveShareCommands.syncRequest, []) : undefined;
130+
if (!synced && api.session && api.session.role !== vsls.Role.None) {
131+
throw new Error(localize.DataScience.liveShareSyncFailure());
132+
}
133+
}
69134
}
70135

71-
public async onAttach(_api: vsls.LiveShare | null): Promise<void> {
72-
// Not implemented yet
136+
public async waitForServiceName(): Promise<string> {
137+
return LiveShare.RawNotebookProviderService;
73138
}
74139
}

src/client/datascience/raw-kernel/liveshare/hostRawNotebookProvider.ts

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,22 @@ import {
1515
IAsyncDisposableRegistry,
1616
IConfigurationService,
1717
IDisposableRegistry,
18+
IExperimentsManager,
1819
IOutputChannel,
1920
Resource
2021
} from '../../../common/types';
2122
import { createDeferred } from '../../../common/utils/async';
2223
import * as localize from '../../../common/utils/localize';
2324
import { IServiceContainer } from '../../../ioc/types';
24-
import { Identifiers, LiveShare, Settings } from '../../constants';
25+
import { Identifiers, LiveShare, LiveShareCommands, Settings } from '../../constants';
2526
import { KernelSelector } from '../../jupyter/kernels/kernelSelector';
2627
import { HostJupyterNotebook } from '../../jupyter/liveshare/hostJupyterNotebook';
2728
import { LiveShareParticipantHost } from '../../jupyter/liveshare/liveShareParticipantMixin';
2829
import { IRoleBasedObject } from '../../jupyter/liveshare/roleBasedFactory';
2930
import { IKernelLauncher } from '../../kernel-launcher/types';
3031
import { ProgressReporter } from '../../progress/progressReporter';
3132
import {
33+
IDataScience,
3234
IJupyterKernelSpec,
3335
INotebook,
3436
INotebookExecutionInfo,
@@ -48,6 +50,7 @@ export class HostRawNotebookProvider
4850
private disposed = false;
4951
constructor(
5052
private liveShare: ILiveShareApi,
53+
_dataScience: IDataScience,
5154
private disposableRegistry: IDisposableRegistry,
5255
asyncRegistry: IAsyncDisposableRegistry,
5356
private configService: IConfigurationService,
@@ -58,9 +61,10 @@ export class HostRawNotebookProvider
5861
private kernelLauncher: IKernelLauncher,
5962
private kernelSelector: KernelSelector,
6063
private progressReporter: ProgressReporter,
61-
private outputChannel: IOutputChannel
64+
private outputChannel: IOutputChannel,
65+
experimentsManager: IExperimentsManager
6266
) {
63-
super(liveShare, asyncRegistry);
67+
super(liveShare, asyncRegistry, configService, experimentsManager);
6468
}
6569

6670
public async dispose(): Promise<void> {
@@ -70,20 +74,57 @@ export class HostRawNotebookProvider
7074
}
7175
}
7276

73-
public async onAttach(_api: vsls.LiveShare | null): Promise<void> {
74-
// Not implemented yet
77+
public async onAttach(api: vsls.LiveShare | null): Promise<void> {
78+
await super.onAttach(api);
79+
if (api && !this.disposed) {
80+
const service = await this.waitForService();
81+
// Attach event handlers to different requests
82+
if (service) {
83+
service.onRequest(LiveShareCommands.syncRequest, (_args: any[], _cancellation: CancellationToken) =>
84+
this.onSync()
85+
);
86+
service.onRequest(
87+
LiveShareCommands.rawKernelSupported,
88+
(_args: any[], _cancellation: CancellationToken) => this.supported()
89+
);
90+
service.onRequest(
91+
LiveShareCommands.createRawNotebook,
92+
async (args: any[], _cancellation: CancellationToken) => {
93+
const resource = this.parseUri(args[0]);
94+
const identity = this.parseUri(args[1]);
95+
const notebookMetadata = JSON.parse(args[2]) as nbformat.INotebookMetadata;
96+
// Don't return the notebook. We don't want it to be serialized. We just want its live share server to be started.
97+
const notebook = (await this.createNotebook(
98+
identity!,
99+
resource,
100+
true, // Disable UI for this creation
101+
notebookMetadata,
102+
undefined
103+
)) as HostJupyterNotebook;
104+
await notebook.onAttach(api);
105+
}
106+
);
107+
}
108+
}
75109
}
76110

77-
public async onSessionChange(_api: vsls.LiveShare | null): Promise<void> {
78-
// Not implemented yet
111+
public async onSessionChange(api: vsls.LiveShare | null): Promise<void> {
112+
await super.onSessionChange(api);
113+
114+
this.getNotebooks().forEach(async (notebook) => {
115+
const hostNotebook = (await notebook) as HostJupyterNotebook;
116+
if (hostNotebook) {
117+
await hostNotebook.onSessionChange(api);
118+
}
119+
});
79120
}
80121

81-
public async onDetach(_api: vsls.LiveShare | null): Promise<void> {
82-
// Not implemented yet
122+
public async onDetach(api: vsls.LiveShare | null): Promise<void> {
123+
await super.onDetach(api);
83124
}
84125

85126
public async waitForServiceName(): Promise<string> {
86-
return 'Not implemented';
127+
return LiveShare.RawNotebookProviderService;
87128
}
88129

89130
protected async createNotebookInstance(
@@ -186,4 +227,18 @@ export class HostRawNotebookProvider
186227
purpose: Identifiers.RawPurpose
187228
};
188229
}
230+
231+
private parseUri(uri: string | undefined): Resource {
232+
const parsed = uri ? vscode.Uri.parse(uri) : undefined;
233+
return parsed &&
234+
parsed.scheme &&
235+
parsed.scheme !== Identifiers.InteractiveWindowIdentityScheme &&
236+
parsed.scheme === 'vsls'
237+
? this.finishedApi!.convertSharedUriToLocal(parsed)
238+
: parsed;
239+
}
240+
241+
private onSync(): Promise<any> {
242+
return Promise.resolve(true);
243+
}
189244
}

0 commit comments

Comments
 (0)