@difizen/libro-kernel 0.0.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/es/basemanager.d.ts +94 -0
- package/es/basemanager.d.ts.map +1 -0
- package/es/basemanager.js +110 -0
- package/es/contents/contents-drive.d.ts +189 -0
- package/es/contents/contents-drive.d.ts.map +1 -0
- package/es/contents/contents-drive.js +792 -0
- package/es/contents/contents-manager.d.ts +229 -0
- package/es/contents/contents-manager.d.ts.map +1 -0
- package/es/contents/contents-manager.js +551 -0
- package/es/contents/contents-module.d.ts +3 -0
- package/es/contents/contents-module.d.ts.map +1 -0
- package/es/contents/contents-module.js +4 -0
- package/es/contents/contents-protocol.d.ts +487 -0
- package/es/contents/contents-protocol.d.ts.map +1 -0
- package/es/contents/contents-protocol.js +1 -0
- package/es/contents/index.d.ts +6 -0
- package/es/contents/index.d.ts.map +1 -0
- package/es/contents/index.js +5 -0
- package/es/contents/validate.d.ts +10 -0
- package/es/contents/validate.d.ts.map +1 -0
- package/es/contents/validate.js +22 -0
- package/es/index.d.ts +10 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +9 -0
- package/es/index.less +0 -0
- package/es/kernel/comm.d.ts +92 -0
- package/es/kernel/comm.d.ts.map +1 -0
- package/es/kernel/comm.js +216 -0
- package/es/kernel/future.d.ts +178 -0
- package/es/kernel/future.d.ts.map +1 -0
- package/es/kernel/future.js +587 -0
- package/es/kernel/index.d.ts +8 -0
- package/es/kernel/index.d.ts.map +1 -0
- package/es/kernel/index.js +8 -0
- package/es/kernel/kernel-connection.d.ts +550 -0
- package/es/kernel/kernel-connection.d.ts.map +1 -0
- package/es/kernel/kernel-connection.js +1957 -0
- package/es/kernel/kernel-module.d.ts +3 -0
- package/es/kernel/kernel-module.d.ts.map +1 -0
- package/es/kernel/kernel-module.js +32 -0
- package/es/kernel/libro-kernel-manager.d.ts +69 -0
- package/es/kernel/libro-kernel-manager.d.ts.map +1 -0
- package/es/kernel/libro-kernel-manager.js +349 -0
- package/es/kernel/libro-kernel-protocol.d.ts +675 -0
- package/es/kernel/libro-kernel-protocol.d.ts.map +1 -0
- package/es/kernel/libro-kernel-protocol.js +60 -0
- package/es/kernel/libro-kernel-utils.d.ts +95 -0
- package/es/kernel/libro-kernel-utils.d.ts.map +1 -0
- package/es/kernel/libro-kernel-utils.js +130 -0
- package/es/kernel/libro-kernel.d.ts +14 -0
- package/es/kernel/libro-kernel.d.ts.map +1 -0
- package/es/kernel/libro-kernel.js +54 -0
- package/es/kernel/messages.d.ts +845 -0
- package/es/kernel/messages.d.ts.map +1 -0
- package/es/kernel/messages.js +457 -0
- package/es/kernel/restapi.d.ts +78 -0
- package/es/kernel/restapi.d.ts.map +1 -0
- package/es/kernel/restapi.js +367 -0
- package/es/kernel/serialize.d.ts +10 -0
- package/es/kernel/serialize.d.ts.map +1 -0
- package/es/kernel/serialize.js +214 -0
- package/es/kernel/validate.d.ts +15 -0
- package/es/kernel/validate.d.ts.map +1 -0
- package/es/kernel/validate.js +125 -0
- package/es/kernelspec/index.d.ts +5 -0
- package/es/kernelspec/index.d.ts.map +1 -0
- package/es/kernelspec/index.js +4 -0
- package/es/kernelspec/kernelspec-module.d.ts +3 -0
- package/es/kernelspec/kernelspec-module.d.ts.map +1 -0
- package/es/kernelspec/kernelspec-module.js +4 -0
- package/es/kernelspec/kernelspec.d.ts +33 -0
- package/es/kernelspec/kernelspec.d.ts.map +1 -0
- package/es/kernelspec/kernelspec.js +1 -0
- package/es/kernelspec/manager.d.ts +81 -0
- package/es/kernelspec/manager.d.ts.map +1 -0
- package/es/kernelspec/manager.js +248 -0
- package/es/kernelspec/restapi.d.ts +71 -0
- package/es/kernelspec/restapi.d.ts.map +1 -0
- package/es/kernelspec/restapi.js +107 -0
- package/es/kernelspec/validate.d.ts +10 -0
- package/es/kernelspec/validate.d.ts.map +1 -0
- package/es/kernelspec/validate.js +69 -0
- package/es/libro-kernel-connection-manager.d.ts +19 -0
- package/es/libro-kernel-connection-manager.d.ts.map +1 -0
- package/es/libro-kernel-connection-manager.js +142 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +9 -0
- package/es/page-config.d.ts +36 -0
- package/es/page-config.d.ts.map +1 -0
- package/es/page-config.js +129 -0
- package/es/protocol.d.ts +13 -0
- package/es/protocol.d.ts.map +1 -0
- package/es/protocol.js +8 -0
- package/es/server/connection-error.d.ts +36 -0
- package/es/server/connection-error.d.ts.map +1 -0
- package/es/server/connection-error.js +109 -0
- package/es/server/index.d.ts +6 -0
- package/es/server/index.d.ts.map +1 -0
- package/es/server/index.js +5 -0
- package/es/server/server-connection-protocol.d.ts +49 -0
- package/es/server/server-connection-protocol.d.ts.map +1 -0
- package/es/server/server-connection-protocol.js +0 -0
- package/es/server/server-connection.d.ts +25 -0
- package/es/server/server-connection.d.ts.map +1 -0
- package/es/server/server-connection.js +159 -0
- package/es/server/server-manager.d.ts +22 -0
- package/es/server/server-manager.d.ts.map +1 -0
- package/es/server/server-manager.js +163 -0
- package/es/server/server-module.d.ts +3 -0
- package/es/server/server-module.d.ts.map +1 -0
- package/es/server/server-module.js +4 -0
- package/es/session/index.d.ts +5 -0
- package/es/session/index.d.ts.map +1 -0
- package/es/session/index.js +4 -0
- package/es/session/libro-session-manager.d.ts +71 -0
- package/es/session/libro-session-manager.d.ts.map +1 -0
- package/es/session/libro-session-manager.js +539 -0
- package/es/session/libro-session-protocol.d.ts +50 -0
- package/es/session/libro-session-protocol.d.ts.map +1 -0
- package/es/session/libro-session-protocol.js +21 -0
- package/es/session/libro-session.d.ts +12 -0
- package/es/session/libro-session.d.ts.map +1 -0
- package/es/session/libro-session.js +19 -0
- package/es/session/restapi.d.ts +28 -0
- package/es/session/restapi.d.ts.map +1 -0
- package/es/session/restapi.js +214 -0
- package/es/session/session-module.d.ts +3 -0
- package/es/session/session-module.d.ts.map +1 -0
- package/es/session/session-module.js +18 -0
- package/es/session/validate.d.ts +14 -0
- package/es/session/validate.d.ts.map +1 -0
- package/es/session/validate.js +37 -0
- package/es/utils.d.ts +4 -0
- package/es/utils.d.ts.map +1 -0
- package/es/utils.js +29 -0
- package/es/validate-property.d.ts +2 -0
- package/es/validate-property.d.ts.map +1 -0
- package/es/validate-property.js +35 -0
- package/package.json +62 -0
- package/src/basemanager.ts +133 -0
- package/src/contents/contents-drive.ts +495 -0
- package/src/contents/contents-manager.ts +465 -0
- package/src/contents/contents-module.ts +6 -0
- package/src/contents/contents-protocol.ts +604 -0
- package/src/contents/index.ts +5 -0
- package/src/contents/validate.ts +29 -0
- package/src/index.tsx +9 -0
- package/src/kernel/comm.ts +220 -0
- package/src/kernel/future.ts +474 -0
- package/src/kernel/index.ts +7 -0
- package/src/kernel/kernel-connection.ts +1770 -0
- package/src/kernel/kernel-module.ts +50 -0
- package/src/kernel/libro-kernel-manager.ts +199 -0
- package/src/kernel/libro-kernel-protocol.ts +858 -0
- package/src/kernel/libro-kernel-utils.ts +152 -0
- package/src/kernel/libro-kernel.ts +39 -0
- package/src/kernel/messages.ts +1104 -0
- package/src/kernel/restapi.ts +183 -0
- package/src/kernel/serialize.ts +262 -0
- package/src/kernel/validate.ts +101 -0
- package/src/kernelspec/index.ts +5 -0
- package/src/kernelspec/kernelspec-module.ts +9 -0
- package/src/kernelspec/kernelspec.ts +37 -0
- package/src/kernelspec/manager.ts +173 -0
- package/src/kernelspec/restapi.ts +104 -0
- package/src/kernelspec/validate.ts +80 -0
- package/src/libro-kernel-connection-manager.ts +73 -0
- package/src/module.ts +19 -0
- package/src/page-config.ts +106 -0
- package/src/protocol.ts +24 -0
- package/src/server/connection-error.ts +60 -0
- package/src/server/index.ts +5 -0
- package/src/server/server-connection-protocol.ts +57 -0
- package/src/server/server-connection.ts +144 -0
- package/src/server/server-manager.ts +76 -0
- package/src/server/server-module.ts +9 -0
- package/src/session/index.ts +4 -0
- package/src/session/libro-session-manager.ts +377 -0
- package/src/session/libro-session-protocol.ts +61 -0
- package/src/session/libro-session.ts +33 -0
- package/src/session/restapi.ts +126 -0
- package/src/session/session-module.ts +26 -0
- package/src/session/validate.ts +39 -0
- package/src/utils.ts +28 -0
- package/src/validate-property.ts +38 -0
|
@@ -0,0 +1,1770 @@
|
|
|
1
|
+
import type { JSONObject } from '@difizen/libro-common';
|
|
2
|
+
import { deepCopy, URL } from '@difizen/libro-common';
|
|
3
|
+
import type { Disposable, Event as ManaEvent } from '@difizen/mana-app';
|
|
4
|
+
import { prop } from '@difizen/mana-app';
|
|
5
|
+
import { Deferred, Emitter } from '@difizen/mana-app';
|
|
6
|
+
import { inject, transient } from '@difizen/mana-app';
|
|
7
|
+
import { v4 } from 'uuid';
|
|
8
|
+
|
|
9
|
+
import type { ISpecModel } from '../kernelspec/index.js';
|
|
10
|
+
import { KernelSpecRestAPI } from '../kernelspec/index.js';
|
|
11
|
+
import { NetworkError, ServerConnection } from '../server/index.js';
|
|
12
|
+
import type { ISettings } from '../server/index.js';
|
|
13
|
+
|
|
14
|
+
import { CommHandler } from './comm.js';
|
|
15
|
+
import type { KernelFutureHandler } from './future.js';
|
|
16
|
+
import { KernelControlFutureHandler, KernelShellFutureHandler } from './future.js';
|
|
17
|
+
import type {
|
|
18
|
+
ConnectionStatus,
|
|
19
|
+
IAnyMessageArgs,
|
|
20
|
+
IComm,
|
|
21
|
+
IControlFuture,
|
|
22
|
+
IFuture,
|
|
23
|
+
IKernelConnection,
|
|
24
|
+
IKernelModel,
|
|
25
|
+
IShellFuture,
|
|
26
|
+
} from './libro-kernel-protocol.js';
|
|
27
|
+
import {
|
|
28
|
+
KernelConnectionOptions,
|
|
29
|
+
LibroKernelConnectionFactory,
|
|
30
|
+
} from './libro-kernel-protocol.js';
|
|
31
|
+
import {
|
|
32
|
+
isDisplayDataMsg,
|
|
33
|
+
isExecuteResultMsg,
|
|
34
|
+
isInfoRequestMsg,
|
|
35
|
+
isUpdateDisplayDataMsg,
|
|
36
|
+
} from './libro-kernel-utils.js';
|
|
37
|
+
import * as KernelMessage from './messages.js';
|
|
38
|
+
import * as restapi from './restapi.js';
|
|
39
|
+
import { KernelRestAPI } from './restapi.js';
|
|
40
|
+
import { deserialize, serialize } from './serialize.js';
|
|
41
|
+
import * as validate from './validate.js';
|
|
42
|
+
|
|
43
|
+
// 以下 三个 没必要follow jupyter lab
|
|
44
|
+
|
|
45
|
+
// Stub for requirejs.
|
|
46
|
+
declare let requirejs: any;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A protected namespace for the Kernel.
|
|
50
|
+
*/
|
|
51
|
+
namespace Private {
|
|
52
|
+
/**
|
|
53
|
+
* Log the current kernel status.
|
|
54
|
+
*/
|
|
55
|
+
export function logKernelStatus(kernel: IKernelConnection): void {
|
|
56
|
+
switch (kernel.status) {
|
|
57
|
+
case 'idle':
|
|
58
|
+
case 'busy':
|
|
59
|
+
case 'unknown':
|
|
60
|
+
return;
|
|
61
|
+
default:
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.debug(`Kernel: ${kernel.status} (${kernel.id})`);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Send a kernel message to the kernel and resolve the reply message.
|
|
70
|
+
*/
|
|
71
|
+
export async function handleShellMessage<T extends KernelMessage.ShellMessageType>(
|
|
72
|
+
kernel: IKernelConnection,
|
|
73
|
+
msg: KernelMessage.IShellMessage<T>,
|
|
74
|
+
): Promise<KernelMessage.IShellMessage<KernelMessage.ShellMessageType>> {
|
|
75
|
+
const future = kernel.sendShellMessage(msg, true);
|
|
76
|
+
return future.done;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Try to load an object from a module or a registry.
|
|
81
|
+
*
|
|
82
|
+
* Try to load an object from a module asynchronously if a module
|
|
83
|
+
* is specified, otherwise tries to load an object from the global
|
|
84
|
+
* registry, if the global registry is provided.
|
|
85
|
+
*
|
|
86
|
+
* #### Notes
|
|
87
|
+
* Loading a module uses requirejs.
|
|
88
|
+
*/
|
|
89
|
+
export function loadObject(
|
|
90
|
+
name: string,
|
|
91
|
+
moduleName: string | undefined,
|
|
92
|
+
registry?: Record<string, any>,
|
|
93
|
+
): Promise<any> {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
// Try loading the module using require.js
|
|
96
|
+
if (moduleName) {
|
|
97
|
+
if (typeof requirejs === 'undefined') {
|
|
98
|
+
throw new Error('requirejs not found');
|
|
99
|
+
}
|
|
100
|
+
requirejs(
|
|
101
|
+
[moduleName],
|
|
102
|
+
(mod: any) => {
|
|
103
|
+
if (mod[name] === void 0) {
|
|
104
|
+
const msg = `Object '${name}' not found in module '${moduleName}'`;
|
|
105
|
+
reject(new Error(msg));
|
|
106
|
+
} else {
|
|
107
|
+
resolve(mod[name]);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
reject,
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
if (registry?.[name]) {
|
|
114
|
+
resolve(registry[name]);
|
|
115
|
+
} else {
|
|
116
|
+
reject(new Error(`Object '${name}' not found in registry`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get a random integer between min and max, inclusive of both.
|
|
124
|
+
*
|
|
125
|
+
* #### Notes
|
|
126
|
+
* From
|
|
127
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values_inclusive
|
|
128
|
+
*
|
|
129
|
+
* From the MDN page: It might be tempting to use Math.round() to accomplish
|
|
130
|
+
* that, but doing so would cause your random numbers to follow a non-uniform
|
|
131
|
+
* distribution, which may not be acceptable for your needs.
|
|
132
|
+
*/
|
|
133
|
+
export function getRandomIntInclusive(min: number, max: number): number {
|
|
134
|
+
const _min = Math.ceil(min);
|
|
135
|
+
const _max = Math.floor(max);
|
|
136
|
+
return Math.floor(Math.random() * (_max - _min + 1)) + _min;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const KERNEL_INFO_TIMEOUT = 3000;
|
|
141
|
+
const RESTARTING_KERNEL_SESSION = '_RESTARTING_';
|
|
142
|
+
const STARTING_KERNEL_SESSION = '';
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Implementation of the Kernel object.
|
|
146
|
+
*
|
|
147
|
+
* #### Notes
|
|
148
|
+
* Messages from the server are handled in the order they were received and
|
|
149
|
+
* asynchronously. Any message handler can return a promise, and message
|
|
150
|
+
* handling will pause until the promise is fulfilled.
|
|
151
|
+
*/
|
|
152
|
+
@transient()
|
|
153
|
+
export class KernelConnection implements IKernelConnection {
|
|
154
|
+
@inject(KernelSpecRestAPI) kernelSpecRestAPI: KernelSpecRestAPI;
|
|
155
|
+
@inject(KernelRestAPI) kernelRestAPI: KernelRestAPI;
|
|
156
|
+
@inject(LibroKernelConnectionFactory)
|
|
157
|
+
libroKernelConnectionFactory: LibroKernelConnectionFactory;
|
|
158
|
+
/**
|
|
159
|
+
* Construct a kernel object.
|
|
160
|
+
*/
|
|
161
|
+
constructor(
|
|
162
|
+
@inject(KernelConnectionOptions) options: KernelConnectionOptions,
|
|
163
|
+
@inject(ServerConnection) serverConnection: ServerConnection,
|
|
164
|
+
) {
|
|
165
|
+
this.serverSettings = { ...serverConnection.settings, ...options.serverSettings };
|
|
166
|
+
this._name = options.model.name;
|
|
167
|
+
this._id = options.model.id;
|
|
168
|
+
|
|
169
|
+
this._clientId = options.clientId ?? v4();
|
|
170
|
+
this._username = options.username ?? '';
|
|
171
|
+
this.handleComms = options.handleComms ?? true;
|
|
172
|
+
|
|
173
|
+
this._createSocket();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
send(msg: string | ArrayBuffer) {
|
|
177
|
+
this._ws?.send(msg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
get onDisposed(): ManaEvent<void> {
|
|
181
|
+
return this.onDisposedEmitter.event;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The server settings for the kernel.
|
|
186
|
+
*/
|
|
187
|
+
readonly serverSettings: ISettings;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Handle comm messages
|
|
191
|
+
*
|
|
192
|
+
* #### Notes
|
|
193
|
+
* The comm message protocol currently has implicit assumptions that only
|
|
194
|
+
* one kernel connection is handling comm messages. This option allows a
|
|
195
|
+
* kernel connection to opt out of handling comms.
|
|
196
|
+
*
|
|
197
|
+
* See https://github.com/jupyter/jupyter_client/issues/263
|
|
198
|
+
*/
|
|
199
|
+
readonly handleComms: boolean;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* A signal emitted when the kernel status changes.
|
|
203
|
+
*/
|
|
204
|
+
get statusChanged(): ManaEvent<KernelMessage.Status> {
|
|
205
|
+
return this.statusChangedEmitter.event;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A signal emitted when the kernel status changes.
|
|
210
|
+
*/
|
|
211
|
+
get connectionStatusChanged(): ManaEvent<ConnectionStatus> {
|
|
212
|
+
return this.connectionStatusChangedEmitter.event;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A signal emitted for iopub kernel messages.
|
|
217
|
+
*
|
|
218
|
+
* #### Notes
|
|
219
|
+
* This signal is emitted after the iopub message is handled asynchronously.
|
|
220
|
+
*/
|
|
221
|
+
get iopubMessage(): ManaEvent<KernelMessage.IIOPubMessage> {
|
|
222
|
+
return this.iopubMessageEmitter.event;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A signal emitted for unhandled kernel message.
|
|
227
|
+
*
|
|
228
|
+
* #### Notes
|
|
229
|
+
* This signal is emitted for a message that was not handled. It is emitted
|
|
230
|
+
* during the asynchronous message handling code.
|
|
231
|
+
*/
|
|
232
|
+
get unhandledMessage(): ManaEvent<KernelMessage.IMessage> {
|
|
233
|
+
return this.unhandledMessageEmitter.event;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The kernel model
|
|
238
|
+
*/
|
|
239
|
+
get model(): IKernelModel {
|
|
240
|
+
return (
|
|
241
|
+
this._model || {
|
|
242
|
+
id: this.id,
|
|
243
|
+
name: this.name,
|
|
244
|
+
reason: this._reason,
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* A signal emitted for any kernel message.
|
|
251
|
+
*
|
|
252
|
+
* #### Notes
|
|
253
|
+
* This signal is emitted when a message is received, before it is handled
|
|
254
|
+
* asynchronously.
|
|
255
|
+
*
|
|
256
|
+
* This message is emitted when a message is queued for sending (either in
|
|
257
|
+
* the websocket buffer, or our own pending message buffer). The message may
|
|
258
|
+
* actually be sent across the wire at a later time.
|
|
259
|
+
*
|
|
260
|
+
* The message emitted in this signal should not be modified in any way.
|
|
261
|
+
*/
|
|
262
|
+
get anyMessage(): ManaEvent<IAnyMessageArgs> {
|
|
263
|
+
return this.anyMessageEmitter.event;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A signal emitted when a kernel has pending inputs from the user.
|
|
268
|
+
*/
|
|
269
|
+
get pendingInput(): ManaEvent<boolean> {
|
|
270
|
+
return this.pendingInputEmitter.event;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The id of the server-side kernel.
|
|
275
|
+
*/
|
|
276
|
+
get id(): string {
|
|
277
|
+
return this._id;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The name of the server-side kernel.
|
|
282
|
+
*/
|
|
283
|
+
get name(): string {
|
|
284
|
+
return this._name;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The client username.
|
|
289
|
+
*/
|
|
290
|
+
get username(): string {
|
|
291
|
+
return this._username;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The client unique id.
|
|
296
|
+
*/
|
|
297
|
+
get clientId(): string {
|
|
298
|
+
return this._clientId;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The current status of the kernel.
|
|
303
|
+
*/
|
|
304
|
+
get status(): KernelMessage.Status {
|
|
305
|
+
return this._status;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The current connection status of the kernel connection.
|
|
310
|
+
*/
|
|
311
|
+
get connectionStatus(): ConnectionStatus {
|
|
312
|
+
return this._connectionStatus;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Test whether the kernel has been disposed.
|
|
317
|
+
*/
|
|
318
|
+
get isDisposed(): boolean {
|
|
319
|
+
return this._isDisposed;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The cached kernel info.
|
|
324
|
+
*
|
|
325
|
+
* @returns A promise that resolves to the kernel info.
|
|
326
|
+
*/
|
|
327
|
+
get info(): Promise<KernelMessage.IInfoReply> {
|
|
328
|
+
return this._info.promise;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* The kernel spec.
|
|
333
|
+
*
|
|
334
|
+
* @returns A promise that resolves to the kernel spec.
|
|
335
|
+
*/
|
|
336
|
+
get spec(): Promise<ISpecModel | undefined> {
|
|
337
|
+
if (this._specPromise) {
|
|
338
|
+
return this._specPromise;
|
|
339
|
+
}
|
|
340
|
+
this._specPromise = this.kernelSpecRestAPI
|
|
341
|
+
.getSpecs(this.serverSettings)
|
|
342
|
+
.then((specs) => {
|
|
343
|
+
return specs.kernelspecs[this._name];
|
|
344
|
+
});
|
|
345
|
+
return this._specPromise;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Clone the current kernel with a new clientId.
|
|
350
|
+
*/
|
|
351
|
+
clone(
|
|
352
|
+
options: Pick<
|
|
353
|
+
KernelConnectionOptions,
|
|
354
|
+
'clientId' | 'username' | 'handleComms'
|
|
355
|
+
> = {},
|
|
356
|
+
): IKernelConnection {
|
|
357
|
+
return this.libroKernelConnectionFactory({
|
|
358
|
+
model: this.model,
|
|
359
|
+
username: this.username,
|
|
360
|
+
// handleComms defaults to false since that is safer
|
|
361
|
+
handleComms: false,
|
|
362
|
+
...options,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Dispose of the resources held by the kernel.
|
|
368
|
+
*/
|
|
369
|
+
dispose(): void {
|
|
370
|
+
if (this.isDisposed) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
this.onDisposedEmitter.fire();
|
|
374
|
+
|
|
375
|
+
this._updateConnectionStatus('disconnected');
|
|
376
|
+
this._clearKernelState();
|
|
377
|
+
this._pendingMessages = [];
|
|
378
|
+
this._clearSocket();
|
|
379
|
+
|
|
380
|
+
this.connectionStatusChangedEmitter.dispose();
|
|
381
|
+
this.statusChangedEmitter.dispose();
|
|
382
|
+
this.onDisposedEmitter.dispose();
|
|
383
|
+
this.iopubMessageEmitter.dispose();
|
|
384
|
+
this.anyMessageEmitter.dispose();
|
|
385
|
+
this.pendingInputEmitter.dispose();
|
|
386
|
+
this.unhandledMessageEmitter.dispose();
|
|
387
|
+
this._isDisposed = true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Send a shell message to the kernel.
|
|
392
|
+
*
|
|
393
|
+
* #### Notes
|
|
394
|
+
* Send a message to the kernel's shell channel, yielding a future object
|
|
395
|
+
* for accepting replies.
|
|
396
|
+
*
|
|
397
|
+
* If `expectReply` is given and `true`, the future is disposed when both a
|
|
398
|
+
* shell reply and an idle status message are received. If `expectReply`
|
|
399
|
+
* is not given or is `false`, the future is resolved when an idle status
|
|
400
|
+
* message is received.
|
|
401
|
+
* If `disposeOnDone` is not given or is `true`, the Future is disposed at this point.
|
|
402
|
+
* If `disposeOnDone` is given and `false`, it is up to the caller to dispose of the Future.
|
|
403
|
+
*
|
|
404
|
+
* All replies are validated as valid kernel messages.
|
|
405
|
+
*
|
|
406
|
+
* If the kernel status is `dead`, this will throw an error.
|
|
407
|
+
*/
|
|
408
|
+
sendShellMessage<T extends KernelMessage.ShellMessageType>(
|
|
409
|
+
msg: KernelMessage.IShellMessage<T>,
|
|
410
|
+
expectReply = false,
|
|
411
|
+
disposeOnDone = true,
|
|
412
|
+
): IShellFuture<KernelMessage.IShellMessage<T>> {
|
|
413
|
+
return this._sendKernelShellControl(
|
|
414
|
+
KernelShellFutureHandler as any,
|
|
415
|
+
msg,
|
|
416
|
+
expectReply,
|
|
417
|
+
disposeOnDone,
|
|
418
|
+
) as IShellFuture<KernelMessage.IShellMessage<T>>;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Send a control message to the kernel.
|
|
423
|
+
*
|
|
424
|
+
* #### Notes
|
|
425
|
+
* Send a message to the kernel's control channel, yielding a future object
|
|
426
|
+
* for accepting replies.
|
|
427
|
+
*
|
|
428
|
+
* If `expectReply` is given and `true`, the future is disposed when both a
|
|
429
|
+
* control reply and an idle status message are received. If `expectReply`
|
|
430
|
+
* is not given or is `false`, the future is resolved when an idle status
|
|
431
|
+
* message is received.
|
|
432
|
+
* If `disposeOnDone` is not given or is `true`, the Future is disposed at this point.
|
|
433
|
+
* If `disposeOnDone` is given and `false`, it is up to the caller to dispose of the Future.
|
|
434
|
+
*
|
|
435
|
+
* All replies are validated as valid kernel messages.
|
|
436
|
+
*
|
|
437
|
+
* If the kernel status is `dead`, this will throw an error.
|
|
438
|
+
*/
|
|
439
|
+
sendControlMessage<T extends KernelMessage.ControlMessageType>(
|
|
440
|
+
msg: KernelMessage.IControlMessage<T>,
|
|
441
|
+
expectReply = false,
|
|
442
|
+
disposeOnDone = true,
|
|
443
|
+
): IControlFuture<KernelMessage.IControlMessage<T>> {
|
|
444
|
+
return this._sendKernelShellControl(
|
|
445
|
+
KernelControlFutureHandler as any,
|
|
446
|
+
msg,
|
|
447
|
+
expectReply,
|
|
448
|
+
disposeOnDone,
|
|
449
|
+
) as IControlFuture<KernelMessage.IControlMessage<T>>;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
protected _sendKernelShellControl<
|
|
453
|
+
REQUEST extends KernelMessage.IShellControlMessage,
|
|
454
|
+
REPLY extends KernelMessage.IShellControlMessage,
|
|
455
|
+
KFH extends new (...params: any[]) => KernelFutureHandler<REQUEST, REPLY>,
|
|
456
|
+
T extends KernelMessage.IMessage,
|
|
457
|
+
>(
|
|
458
|
+
ctor: KFH,
|
|
459
|
+
msg: T,
|
|
460
|
+
expectReply = false,
|
|
461
|
+
disposeOnDone = true,
|
|
462
|
+
): IFuture<KernelMessage.IShellControlMessage, KernelMessage.IShellControlMessage> {
|
|
463
|
+
this._sendMessage(msg);
|
|
464
|
+
this.anyMessageEmitter.fire({ msg, direction: 'send' });
|
|
465
|
+
|
|
466
|
+
const future = new ctor(
|
|
467
|
+
() => {
|
|
468
|
+
const msgId = msg.header.msg_id;
|
|
469
|
+
this._futures.delete(msgId);
|
|
470
|
+
// Remove stored display id information.
|
|
471
|
+
const displayIds = this._msgIdToDisplayIds.get(msgId);
|
|
472
|
+
if (!displayIds) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
displayIds.forEach((displayId) => {
|
|
476
|
+
const msgIds = this._displayIdToParentIds.get(displayId);
|
|
477
|
+
if (msgIds) {
|
|
478
|
+
const idx = msgIds.indexOf(msgId);
|
|
479
|
+
if (idx === -1) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (msgIds.length === 1) {
|
|
483
|
+
this._displayIdToParentIds.delete(displayId);
|
|
484
|
+
} else {
|
|
485
|
+
msgIds.splice(idx, 1);
|
|
486
|
+
this._displayIdToParentIds.set(displayId, msgIds);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
this._msgIdToDisplayIds.delete(msgId);
|
|
491
|
+
},
|
|
492
|
+
msg,
|
|
493
|
+
expectReply,
|
|
494
|
+
disposeOnDone,
|
|
495
|
+
this,
|
|
496
|
+
);
|
|
497
|
+
this._futures.set(msg.header.msg_id, future as any);
|
|
498
|
+
return future as any;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Send a message on the websocket.
|
|
503
|
+
*
|
|
504
|
+
* If queue is true, queue the message for later sending if we cannot send
|
|
505
|
+
* now. Otherwise throw an error.
|
|
506
|
+
*
|
|
507
|
+
* #### Notes
|
|
508
|
+
* As an exception to the queueing, if we are sending a kernel_info_request
|
|
509
|
+
* message while we think the kernel is restarting, we send the message
|
|
510
|
+
* immediately without queueing. This is so that we can trigger a message
|
|
511
|
+
* back, which will then clear the kernel restarting state.
|
|
512
|
+
*/
|
|
513
|
+
protected _sendMessage(msg: KernelMessage.IMessage, queue = true) {
|
|
514
|
+
if (this.status === 'dead') {
|
|
515
|
+
throw new Error('Kernel is dead');
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// If we have a kernel_info_request and we are starting or restarting, send the
|
|
519
|
+
// kernel_info_request immediately if we can, and if not throw an error so
|
|
520
|
+
// we can retry later. On restarting we do this because we must get at least one message
|
|
521
|
+
// from the kernel to reset the kernel session (thus clearing the restart
|
|
522
|
+
// status sentinel).
|
|
523
|
+
if (
|
|
524
|
+
(this._kernelSession === STARTING_KERNEL_SESSION ||
|
|
525
|
+
this._kernelSession === RESTARTING_KERNEL_SESSION) &&
|
|
526
|
+
isInfoRequestMsg(msg)
|
|
527
|
+
) {
|
|
528
|
+
if (this.connectionStatus === 'connected') {
|
|
529
|
+
this._ws!.send(serialize(msg, this._ws!.protocol));
|
|
530
|
+
return;
|
|
531
|
+
} else {
|
|
532
|
+
throw new Error('Could not send message: status is not connected');
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// If there are pending messages, add to the queue so we keep messages in order
|
|
537
|
+
if (queue && this._pendingMessages.length > 0) {
|
|
538
|
+
this._pendingMessages.push(msg);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Send if the ws allows it, otherwise queue the message.
|
|
543
|
+
if (
|
|
544
|
+
this.connectionStatus === 'connected' &&
|
|
545
|
+
this._kernelSession !== RESTARTING_KERNEL_SESSION
|
|
546
|
+
) {
|
|
547
|
+
this._ws!.send(serialize(msg, this._ws!.protocol));
|
|
548
|
+
} else if (queue) {
|
|
549
|
+
this._pendingMessages.push(msg);
|
|
550
|
+
} else {
|
|
551
|
+
throw new Error('Could not send message');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Interrupt a kernel.
|
|
557
|
+
*
|
|
558
|
+
* #### Notes
|
|
559
|
+
* Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
|
|
560
|
+
*
|
|
561
|
+
* The promise is fulfilled on a valid response and rejected otherwise.
|
|
562
|
+
*
|
|
563
|
+
* It is assumed that the API call does not mutate the kernel id or name.
|
|
564
|
+
*
|
|
565
|
+
* The promise will be rejected if the kernel status is `Dead` or if the
|
|
566
|
+
* request fails or the response is invalid.
|
|
567
|
+
*/
|
|
568
|
+
async interrupt(): Promise<void> {
|
|
569
|
+
this.hasPendingInput = false;
|
|
570
|
+
if (this.status === 'dead') {
|
|
571
|
+
throw new Error('Kernel is dead');
|
|
572
|
+
}
|
|
573
|
+
return this.kernelRestAPI.interruptKernel(this.id, this.serverSettings);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Request a kernel restart.
|
|
578
|
+
*
|
|
579
|
+
* #### Notes
|
|
580
|
+
* Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels)
|
|
581
|
+
* and validates the response model.
|
|
582
|
+
*
|
|
583
|
+
* Any existing Future or Comm objects are cleared once the kernel has
|
|
584
|
+
* actually be restarted.
|
|
585
|
+
*
|
|
586
|
+
* The promise is fulfilled on a valid server response (after the kernel restarts)
|
|
587
|
+
* and rejected otherwise.
|
|
588
|
+
*
|
|
589
|
+
* It is assumed that the API call does not mutate the kernel id or name.
|
|
590
|
+
*
|
|
591
|
+
* The promise will be rejected if the request fails or the response is
|
|
592
|
+
* invalid.
|
|
593
|
+
*/
|
|
594
|
+
async restart(): Promise<void> {
|
|
595
|
+
if (this.status === 'dead') {
|
|
596
|
+
throw new Error('Kernel is dead');
|
|
597
|
+
}
|
|
598
|
+
this._updateStatus('restarting');
|
|
599
|
+
this._clearKernelState();
|
|
600
|
+
this._kernelSession = RESTARTING_KERNEL_SESSION;
|
|
601
|
+
await this.kernelRestAPI.restartKernel(this.id, this.serverSettings);
|
|
602
|
+
// Reconnect to the kernel to address cases where kernel ports
|
|
603
|
+
// have changed during the restart.
|
|
604
|
+
await this.reconnect();
|
|
605
|
+
this.hasPendingInput = false;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Reconnect to a kernel.
|
|
610
|
+
*
|
|
611
|
+
* #### Notes
|
|
612
|
+
* This may try multiple times to reconnect to a kernel, and will sever any
|
|
613
|
+
* existing connection.
|
|
614
|
+
*/
|
|
615
|
+
reconnect(): Promise<void> {
|
|
616
|
+
this._errorIfDisposed();
|
|
617
|
+
const result = new Deferred<void>();
|
|
618
|
+
let toDispose: Disposable | undefined = undefined;
|
|
619
|
+
|
|
620
|
+
// Set up a listener for the connection status changing, which accepts or
|
|
621
|
+
// rejects after the retries are done.
|
|
622
|
+
const fulfill = (status: ConnectionStatus) => {
|
|
623
|
+
if (status === 'connected') {
|
|
624
|
+
result.resolve();
|
|
625
|
+
if (toDispose) {
|
|
626
|
+
toDispose.dispose();
|
|
627
|
+
toDispose = undefined;
|
|
628
|
+
}
|
|
629
|
+
} else if (status === 'disconnected') {
|
|
630
|
+
result.reject(new Error('Kernel connection disconnected'));
|
|
631
|
+
if (toDispose) {
|
|
632
|
+
toDispose.dispose();
|
|
633
|
+
toDispose = undefined;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
toDispose = this.connectionStatusChanged(fulfill);
|
|
638
|
+
|
|
639
|
+
// Reset the reconnect limit so we start the connection attempts fresh
|
|
640
|
+
this._reconnectAttempt = 0;
|
|
641
|
+
|
|
642
|
+
// Start the reconnection process, which will also clear any existing
|
|
643
|
+
// connection.
|
|
644
|
+
this._reconnect();
|
|
645
|
+
|
|
646
|
+
// Return the promise that should resolve on connection or reject if the
|
|
647
|
+
// retries don't work.
|
|
648
|
+
return result.promise;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Shutdown a kernel.
|
|
653
|
+
*
|
|
654
|
+
* #### Notes
|
|
655
|
+
* Uses the [Jupyter Notebook API](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/jupyter/notebook/master/notebook/services/api/api.yaml#!/kernels).
|
|
656
|
+
*
|
|
657
|
+
* The promise is fulfilled on a valid response and rejected otherwise.
|
|
658
|
+
*
|
|
659
|
+
* On a valid response, disposes this kernel connection.
|
|
660
|
+
*
|
|
661
|
+
* If the kernel is already `dead`, disposes this kernel connection without
|
|
662
|
+
* a server request.
|
|
663
|
+
*/
|
|
664
|
+
async shutdown(): Promise<void> {
|
|
665
|
+
if (this.status !== 'dead') {
|
|
666
|
+
await this.kernelRestAPI.shutdownKernel(this.id, this.serverSettings);
|
|
667
|
+
}
|
|
668
|
+
this.handleShutdown();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Handles a kernel shutdown.
|
|
673
|
+
*
|
|
674
|
+
* #### Notes
|
|
675
|
+
* This method should be called if we know from outside information that a
|
|
676
|
+
* kernel is dead (for example, we cannot find the kernel model on the
|
|
677
|
+
* server).
|
|
678
|
+
*/
|
|
679
|
+
handleShutdown(): void {
|
|
680
|
+
this._updateStatus('dead');
|
|
681
|
+
this.dispose();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Send a `kernel_info_request` message.
|
|
686
|
+
*
|
|
687
|
+
* #### Notes
|
|
688
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#kernel-info).
|
|
689
|
+
*
|
|
690
|
+
* Fulfills with the `kernel_info_response` content when the shell reply is
|
|
691
|
+
* received and validated.
|
|
692
|
+
*/
|
|
693
|
+
async requestKernelInfo(): Promise<KernelMessage.IInfoReplyMsg | undefined> {
|
|
694
|
+
const msg = KernelMessage.createMessage({
|
|
695
|
+
msgType: 'kernel_info_request',
|
|
696
|
+
channel: 'shell',
|
|
697
|
+
username: this._username,
|
|
698
|
+
session: this._clientId,
|
|
699
|
+
content: {},
|
|
700
|
+
});
|
|
701
|
+
let reply: KernelMessage.IInfoReplyMsg | undefined;
|
|
702
|
+
try {
|
|
703
|
+
reply = (await Private.handleShellMessage(this, msg)) as
|
|
704
|
+
| KernelMessage.IInfoReplyMsg
|
|
705
|
+
| undefined;
|
|
706
|
+
} catch (e) {
|
|
707
|
+
// If we rejected because the future was disposed, ignore and return.
|
|
708
|
+
if (this.isDisposed) {
|
|
709
|
+
return;
|
|
710
|
+
} else {
|
|
711
|
+
throw e;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
this._errorIfDisposed();
|
|
715
|
+
|
|
716
|
+
if (!reply) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Kernels sometimes do not include a status field on kernel_info_reply
|
|
721
|
+
// messages, so set a default for now.
|
|
722
|
+
// See https://github.com/jupyterlab/jupyterlab/issues/6760
|
|
723
|
+
if (reply.content.status === undefined) {
|
|
724
|
+
(reply.content as any).status = 'ok';
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (reply.content.status !== 'ok') {
|
|
728
|
+
this._info.reject('Kernel info reply errored');
|
|
729
|
+
return reply;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
this._info.resolve(reply.content);
|
|
733
|
+
|
|
734
|
+
this._kernelSession = reply.header.session;
|
|
735
|
+
|
|
736
|
+
return reply;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Send a `complete_request` message.
|
|
741
|
+
*
|
|
742
|
+
* #### Notes
|
|
743
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#completion).
|
|
744
|
+
*
|
|
745
|
+
* Fulfills with the `complete_reply` content when the shell reply is
|
|
746
|
+
* received and validated.
|
|
747
|
+
*/
|
|
748
|
+
requestComplete(
|
|
749
|
+
content: KernelMessage.ICompleteRequestMsg['content'],
|
|
750
|
+
): Promise<KernelMessage.ICompleteReplyMsg> {
|
|
751
|
+
const msg = KernelMessage.createMessage({
|
|
752
|
+
msgType: 'complete_request',
|
|
753
|
+
channel: 'shell',
|
|
754
|
+
username: this._username,
|
|
755
|
+
session: this._clientId,
|
|
756
|
+
content,
|
|
757
|
+
});
|
|
758
|
+
return Private.handleShellMessage(
|
|
759
|
+
this,
|
|
760
|
+
msg,
|
|
761
|
+
) as Promise<KernelMessage.ICompleteReplyMsg>;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Send an `inspect_request` message.
|
|
766
|
+
*
|
|
767
|
+
* #### Notes
|
|
768
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#introspection).
|
|
769
|
+
*
|
|
770
|
+
* Fulfills with the `inspect_reply` content when the shell reply is
|
|
771
|
+
* received and validated.
|
|
772
|
+
*/
|
|
773
|
+
requestInspect(
|
|
774
|
+
content: KernelMessage.IInspectRequestMsg['content'],
|
|
775
|
+
): Promise<KernelMessage.IInspectReplyMsg> {
|
|
776
|
+
const msg = KernelMessage.createMessage({
|
|
777
|
+
msgType: 'inspect_request',
|
|
778
|
+
channel: 'shell',
|
|
779
|
+
username: this._username,
|
|
780
|
+
session: this._clientId,
|
|
781
|
+
content: content,
|
|
782
|
+
});
|
|
783
|
+
return Private.handleShellMessage(
|
|
784
|
+
this,
|
|
785
|
+
msg,
|
|
786
|
+
) as Promise<KernelMessage.IInspectReplyMsg>;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Send a `history_request` message.
|
|
791
|
+
*
|
|
792
|
+
* #### Notes
|
|
793
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#history).
|
|
794
|
+
*
|
|
795
|
+
* Fulfills with the `history_reply` content when the shell reply is
|
|
796
|
+
* received and validated.
|
|
797
|
+
*/
|
|
798
|
+
requestHistory(
|
|
799
|
+
content: KernelMessage.IHistoryRequestMsg['content'],
|
|
800
|
+
): Promise<KernelMessage.IHistoryReplyMsg> {
|
|
801
|
+
const msg = KernelMessage.createMessage({
|
|
802
|
+
msgType: 'history_request',
|
|
803
|
+
channel: 'shell',
|
|
804
|
+
username: this._username,
|
|
805
|
+
session: this._clientId,
|
|
806
|
+
content,
|
|
807
|
+
});
|
|
808
|
+
return Private.handleShellMessage(
|
|
809
|
+
this,
|
|
810
|
+
msg,
|
|
811
|
+
) as Promise<KernelMessage.IHistoryReplyMsg>;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Send an `execute_request` message.
|
|
816
|
+
*
|
|
817
|
+
* #### Notes
|
|
818
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#execute).
|
|
819
|
+
*
|
|
820
|
+
* Future `onReply` is called with the `execute_reply` content when the
|
|
821
|
+
* shell reply is received and validated. The future will resolve when
|
|
822
|
+
* this message is received and the `idle` iopub status is received.
|
|
823
|
+
* The future will also be disposed at this point unless `disposeOnDone`
|
|
824
|
+
* is specified and `false`, in which case it is up to the caller to dispose
|
|
825
|
+
* of the future.
|
|
826
|
+
*
|
|
827
|
+
* **See also:** [[IExecuteReply]]
|
|
828
|
+
*/
|
|
829
|
+
requestExecute(
|
|
830
|
+
content: KernelMessage.IExecuteRequestMsg['content'],
|
|
831
|
+
disposeOnDone = true,
|
|
832
|
+
metadata?: JSONObject,
|
|
833
|
+
): IShellFuture<KernelMessage.IExecuteRequestMsg, KernelMessage.IExecuteReplyMsg> {
|
|
834
|
+
const defaults: JSONObject = {
|
|
835
|
+
silent: false,
|
|
836
|
+
store_history: true,
|
|
837
|
+
user_expressions: {},
|
|
838
|
+
allow_stdin: true,
|
|
839
|
+
stop_on_error: true,
|
|
840
|
+
};
|
|
841
|
+
const msg = KernelMessage.createMessage({
|
|
842
|
+
msgType: 'execute_request',
|
|
843
|
+
channel: 'shell',
|
|
844
|
+
username: this._username,
|
|
845
|
+
session: this._clientId,
|
|
846
|
+
content: { ...defaults, ...content },
|
|
847
|
+
metadata,
|
|
848
|
+
});
|
|
849
|
+
return this.sendShellMessage(msg, true, disposeOnDone) as IShellFuture<
|
|
850
|
+
KernelMessage.IExecuteRequestMsg,
|
|
851
|
+
KernelMessage.IExecuteReplyMsg
|
|
852
|
+
>;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Send an experimental `debug_request` message.
|
|
857
|
+
*
|
|
858
|
+
* @hidden
|
|
859
|
+
*
|
|
860
|
+
* #### Notes
|
|
861
|
+
* Debug messages are experimental messages that are not in the official
|
|
862
|
+
* kernel message specification. As such, this function is *NOT* considered
|
|
863
|
+
* part of the public API, and may change without notice.
|
|
864
|
+
*/
|
|
865
|
+
requestDebug(
|
|
866
|
+
content: KernelMessage.IDebugRequestMsg['content'],
|
|
867
|
+
disposeOnDone = true,
|
|
868
|
+
): IControlFuture<KernelMessage.IDebugRequestMsg, KernelMessage.IDebugReplyMsg> {
|
|
869
|
+
const msg = KernelMessage.createMessage({
|
|
870
|
+
msgType: 'debug_request',
|
|
871
|
+
channel: 'control',
|
|
872
|
+
username: this._username,
|
|
873
|
+
session: this._clientId,
|
|
874
|
+
content,
|
|
875
|
+
});
|
|
876
|
+
return this.sendControlMessage(msg, true, disposeOnDone) as IControlFuture<
|
|
877
|
+
KernelMessage.IDebugRequestMsg,
|
|
878
|
+
KernelMessage.IDebugReplyMsg
|
|
879
|
+
>;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Send an `is_complete_request` message.
|
|
884
|
+
*
|
|
885
|
+
* #### Notes
|
|
886
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#code-completeness).
|
|
887
|
+
*
|
|
888
|
+
* Fulfills with the `is_complete_response` content when the shell reply is
|
|
889
|
+
* received and validated.
|
|
890
|
+
*/
|
|
891
|
+
requestIsComplete(
|
|
892
|
+
content: KernelMessage.IIsCompleteRequestMsg['content'],
|
|
893
|
+
): Promise<KernelMessage.IIsCompleteReplyMsg> {
|
|
894
|
+
const msg = KernelMessage.createMessage({
|
|
895
|
+
msgType: 'is_complete_request',
|
|
896
|
+
channel: 'shell',
|
|
897
|
+
username: this._username,
|
|
898
|
+
session: this._clientId,
|
|
899
|
+
content,
|
|
900
|
+
});
|
|
901
|
+
return Private.handleShellMessage(
|
|
902
|
+
this,
|
|
903
|
+
msg,
|
|
904
|
+
) as Promise<KernelMessage.IIsCompleteReplyMsg>;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Send a `comm_info_request` message.
|
|
909
|
+
*
|
|
910
|
+
* #### Notes
|
|
911
|
+
* Fulfills with the `comm_info_reply` content when the shell reply is
|
|
912
|
+
* received and validated.
|
|
913
|
+
*/
|
|
914
|
+
requestCommInfo(
|
|
915
|
+
content: KernelMessage.ICommInfoRequestMsg['content'],
|
|
916
|
+
): Promise<KernelMessage.ICommInfoReplyMsg> {
|
|
917
|
+
const msg = KernelMessage.createMessage({
|
|
918
|
+
msgType: 'comm_info_request',
|
|
919
|
+
channel: 'shell',
|
|
920
|
+
username: this._username,
|
|
921
|
+
session: this._clientId,
|
|
922
|
+
content,
|
|
923
|
+
});
|
|
924
|
+
return Private.handleShellMessage(
|
|
925
|
+
this,
|
|
926
|
+
msg,
|
|
927
|
+
) as Promise<KernelMessage.ICommInfoReplyMsg>;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Send an `input_reply` message.
|
|
932
|
+
*
|
|
933
|
+
* #### Notes
|
|
934
|
+
* See [Messaging in Jupyter](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messages-on-the-stdin-router-dealer-sockets).
|
|
935
|
+
*/
|
|
936
|
+
sendInputReply(
|
|
937
|
+
content: KernelMessage.IInputReplyMsg['content'],
|
|
938
|
+
parent_header: KernelMessage.IInputReplyMsg['parent_header'],
|
|
939
|
+
): void {
|
|
940
|
+
const msg = KernelMessage.createMessage({
|
|
941
|
+
msgType: 'input_reply',
|
|
942
|
+
channel: 'stdin',
|
|
943
|
+
username: this._username,
|
|
944
|
+
session: this._clientId,
|
|
945
|
+
content,
|
|
946
|
+
});
|
|
947
|
+
msg.parent_header = parent_header;
|
|
948
|
+
|
|
949
|
+
this._sendMessage(msg);
|
|
950
|
+
this.anyMessageEmitter.fire({ msg, direction: 'send' });
|
|
951
|
+
|
|
952
|
+
this.hasPendingInput = false;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Create a new comm.
|
|
957
|
+
*
|
|
958
|
+
* #### Notes
|
|
959
|
+
* If a client-side comm already exists with the given commId, an error is thrown.
|
|
960
|
+
* If the kernel does not handle comms, an error is thrown.
|
|
961
|
+
*/
|
|
962
|
+
createComm(targetName: string, commId: string = v4()): IComm {
|
|
963
|
+
if (!this.handleComms) {
|
|
964
|
+
throw new Error('Comms are disabled on this kernel connection');
|
|
965
|
+
}
|
|
966
|
+
if (this._comms.has(commId)) {
|
|
967
|
+
throw new Error('Comm is already created');
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const comm = new CommHandler(targetName, commId, this, () => {
|
|
971
|
+
this._unregisterComm(commId);
|
|
972
|
+
});
|
|
973
|
+
this._comms.set(commId, comm);
|
|
974
|
+
return comm;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Check if a comm exists.
|
|
979
|
+
*/
|
|
980
|
+
hasComm(commId: string): boolean {
|
|
981
|
+
return this._comms.has(commId);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Register a comm target handler.
|
|
986
|
+
*
|
|
987
|
+
* @param targetName - The name of the comm target.
|
|
988
|
+
*
|
|
989
|
+
* @param callback - The callback invoked for a comm open message.
|
|
990
|
+
*
|
|
991
|
+
* @returns A disposable used to unregister the comm target.
|
|
992
|
+
*
|
|
993
|
+
* #### Notes
|
|
994
|
+
* Only one comm target can be registered to a target name at a time, an
|
|
995
|
+
* existing callback for the same target name will be overridden. A registered
|
|
996
|
+
* comm target handler will take precedence over a comm which specifies a
|
|
997
|
+
* `target_module`.
|
|
998
|
+
*
|
|
999
|
+
* If the callback returns a promise, kernel message processing will pause
|
|
1000
|
+
* until the returned promise is fulfilled.
|
|
1001
|
+
*/
|
|
1002
|
+
registerCommTarget(
|
|
1003
|
+
targetName: string,
|
|
1004
|
+
callback: (
|
|
1005
|
+
comm: IComm,
|
|
1006
|
+
msg: KernelMessage.ICommOpenMsg,
|
|
1007
|
+
) => void | PromiseLike<void>,
|
|
1008
|
+
): void {
|
|
1009
|
+
if (!this.handleComms) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
this._targetRegistry[targetName] = callback;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Remove a comm target handler.
|
|
1018
|
+
*
|
|
1019
|
+
* @param targetName - The name of the comm target to remove.
|
|
1020
|
+
*
|
|
1021
|
+
* @param callback - The callback to remove.
|
|
1022
|
+
*
|
|
1023
|
+
* #### Notes
|
|
1024
|
+
* The comm target is only removed if the callback argument matches.
|
|
1025
|
+
*/
|
|
1026
|
+
removeCommTarget(
|
|
1027
|
+
targetName: string,
|
|
1028
|
+
callback: (
|
|
1029
|
+
comm: IComm,
|
|
1030
|
+
msg: KernelMessage.ICommOpenMsg,
|
|
1031
|
+
) => void | PromiseLike<void>,
|
|
1032
|
+
): void {
|
|
1033
|
+
if (!this.handleComms) {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (!this.isDisposed && this._targetRegistry[targetName] === callback) {
|
|
1038
|
+
delete this._targetRegistry[targetName];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Register an IOPub message hook.
|
|
1044
|
+
*
|
|
1045
|
+
* @param msg_id - The parent_header message id the hook will intercept.
|
|
1046
|
+
*
|
|
1047
|
+
* @param hook - The callback invoked for the message.
|
|
1048
|
+
*
|
|
1049
|
+
* #### Notes
|
|
1050
|
+
* The IOPub hook system allows you to preempt the handlers for IOPub
|
|
1051
|
+
* messages that are responses to a given message id.
|
|
1052
|
+
*
|
|
1053
|
+
* The most recently registered hook is run first. A hook can return a
|
|
1054
|
+
* boolean or a promise to a boolean, in which case all kernel message
|
|
1055
|
+
* processing pauses until the promise is fulfilled. If a hook return value
|
|
1056
|
+
* resolves to false, any later hooks will not run and the function will
|
|
1057
|
+
* return a promise resolving to false. If a hook throws an error, the error
|
|
1058
|
+
* is logged to the console and the next hook is run. If a hook is
|
|
1059
|
+
* registered during the hook processing, it will not run until the next
|
|
1060
|
+
* message. If a hook is removed during the hook processing, it will be
|
|
1061
|
+
* deactivated immediately.
|
|
1062
|
+
*
|
|
1063
|
+
* See also [[IFuture.registerMessageHook]].
|
|
1064
|
+
*/
|
|
1065
|
+
registerMessageHook(
|
|
1066
|
+
msgId: string,
|
|
1067
|
+
hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>,
|
|
1068
|
+
): void {
|
|
1069
|
+
const future = this._futures?.get(msgId);
|
|
1070
|
+
if (future) {
|
|
1071
|
+
future.registerMessageHook(hook);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Remove an IOPub message hook.
|
|
1077
|
+
*
|
|
1078
|
+
* @param msg_id - The parent_header message id the hook intercepted.
|
|
1079
|
+
*
|
|
1080
|
+
* @param hook - The callback invoked for the message.
|
|
1081
|
+
*
|
|
1082
|
+
*/
|
|
1083
|
+
removeMessageHook(
|
|
1084
|
+
msgId: string,
|
|
1085
|
+
hook: (msg: KernelMessage.IIOPubMessage) => boolean | PromiseLike<boolean>,
|
|
1086
|
+
): void {
|
|
1087
|
+
const future = this._futures?.get(msgId);
|
|
1088
|
+
if (future) {
|
|
1089
|
+
future.removeMessageHook(hook);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Remove the input guard, if any.
|
|
1095
|
+
*/
|
|
1096
|
+
removeInputGuard() {
|
|
1097
|
+
this.hasPendingInput = false;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Handle a message with a display id.
|
|
1102
|
+
*
|
|
1103
|
+
* @returns Whether the message was handled.
|
|
1104
|
+
*/
|
|
1105
|
+
protected async _handleDisplayId(
|
|
1106
|
+
displayId: string,
|
|
1107
|
+
msg: KernelMessage.IMessage,
|
|
1108
|
+
): Promise<boolean> {
|
|
1109
|
+
const msgId = (msg.parent_header as KernelMessage.IHeader).msg_id;
|
|
1110
|
+
let parentIds = this._displayIdToParentIds.get(displayId);
|
|
1111
|
+
if (parentIds) {
|
|
1112
|
+
// We've seen it before, update existing outputs with same display_id
|
|
1113
|
+
// by handling display_data as update_display_data.
|
|
1114
|
+
const updateMsg: KernelMessage.IMessage = {
|
|
1115
|
+
header: deepCopy(
|
|
1116
|
+
msg.header as unknown as JSONObject,
|
|
1117
|
+
) as unknown as KernelMessage.IHeader,
|
|
1118
|
+
parent_header: deepCopy(
|
|
1119
|
+
msg.parent_header as unknown as JSONObject,
|
|
1120
|
+
) as unknown as KernelMessage.IHeader,
|
|
1121
|
+
metadata: deepCopy(msg.metadata),
|
|
1122
|
+
content: deepCopy(msg.content as JSONObject),
|
|
1123
|
+
channel: msg.channel,
|
|
1124
|
+
buffers: msg.buffers ? msg.buffers.slice() : [],
|
|
1125
|
+
};
|
|
1126
|
+
(updateMsg.header as any).msg_type = 'update_display_data';
|
|
1127
|
+
|
|
1128
|
+
await Promise.all(
|
|
1129
|
+
parentIds.map(async (parentId) => {
|
|
1130
|
+
const future = this._futures && this._futures.get(parentId);
|
|
1131
|
+
if (future) {
|
|
1132
|
+
await future.handleMsg(updateMsg);
|
|
1133
|
+
}
|
|
1134
|
+
}),
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// We're done here if it's update_display.
|
|
1139
|
+
if (msg.header.msg_type === 'update_display_data') {
|
|
1140
|
+
// It's an update, don't proceed to the normal display.
|
|
1141
|
+
return true;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Regular display_data with id, record it for future updating
|
|
1145
|
+
// in _displayIdToParentIds for future lookup.
|
|
1146
|
+
parentIds = this._displayIdToParentIds.get(displayId) ?? [];
|
|
1147
|
+
if (parentIds.indexOf(msgId) === -1) {
|
|
1148
|
+
parentIds.push(msgId);
|
|
1149
|
+
}
|
|
1150
|
+
this._displayIdToParentIds.set(displayId, parentIds);
|
|
1151
|
+
|
|
1152
|
+
// Add to our map of display ids for this message.
|
|
1153
|
+
const displayIds = this._msgIdToDisplayIds.get(msgId) ?? [];
|
|
1154
|
+
if (displayIds.indexOf(msgId) === -1) {
|
|
1155
|
+
displayIds.push(msgId);
|
|
1156
|
+
}
|
|
1157
|
+
this._msgIdToDisplayIds.set(msgId, displayIds);
|
|
1158
|
+
|
|
1159
|
+
// Let the message propagate to the intended recipient.
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Forcefully clear the socket state.
|
|
1165
|
+
*
|
|
1166
|
+
* #### Notes
|
|
1167
|
+
* This will clear all socket state without calling any handlers and will
|
|
1168
|
+
* not update the connection status. If you call this method, you are
|
|
1169
|
+
* responsible for updating the connection status as needed and recreating
|
|
1170
|
+
* the socket if you plan to reconnect.
|
|
1171
|
+
*/
|
|
1172
|
+
protected _clearSocket = (): void => {
|
|
1173
|
+
if (this._ws !== null) {
|
|
1174
|
+
// Clear the websocket event handlers and the socket itself.
|
|
1175
|
+
this._ws.onopen = this._noOp;
|
|
1176
|
+
this._ws.onclose = this._noOp;
|
|
1177
|
+
this._ws.onerror = this._noOp;
|
|
1178
|
+
this._ws.onmessage = this._noOp;
|
|
1179
|
+
this._ws.close();
|
|
1180
|
+
this._ws = null;
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Handle status iopub messages from the kernel.
|
|
1186
|
+
*/
|
|
1187
|
+
protected _updateStatus(status: KernelMessage.Status): void {
|
|
1188
|
+
if (this._status === status || this._status === 'dead') {
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
this._status = status;
|
|
1193
|
+
Private.logKernelStatus(this);
|
|
1194
|
+
this.statusChangedEmitter.fire(status);
|
|
1195
|
+
if (status === 'dead') {
|
|
1196
|
+
this.dispose();
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Send pending messages to the kernel.
|
|
1202
|
+
*/
|
|
1203
|
+
protected _sendPending(): void {
|
|
1204
|
+
// We check to make sure we are still connected each time. For
|
|
1205
|
+
// example, if a websocket buffer overflows, it may close, so we should
|
|
1206
|
+
// stop sending messages.
|
|
1207
|
+
while (
|
|
1208
|
+
this.connectionStatus === 'connected' &&
|
|
1209
|
+
this._kernelSession !== RESTARTING_KERNEL_SESSION &&
|
|
1210
|
+
this._pendingMessages.length > 0
|
|
1211
|
+
) {
|
|
1212
|
+
this._sendMessage(this._pendingMessages[0], false);
|
|
1213
|
+
|
|
1214
|
+
// We shift the message off the queue after the message is sent so that
|
|
1215
|
+
// if there is an exception, the message is still pending.
|
|
1216
|
+
this._pendingMessages.shift();
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Clear the internal state.
|
|
1222
|
+
*/
|
|
1223
|
+
protected _clearKernelState(): void {
|
|
1224
|
+
this._kernelSession = '';
|
|
1225
|
+
this._pendingMessages = [];
|
|
1226
|
+
this._futures.forEach((future) => {
|
|
1227
|
+
future.dispose();
|
|
1228
|
+
});
|
|
1229
|
+
this._comms.forEach((comm) => {
|
|
1230
|
+
comm.dispose();
|
|
1231
|
+
});
|
|
1232
|
+
this._msgChain = Promise.resolve();
|
|
1233
|
+
this._futures = new Map<
|
|
1234
|
+
string,
|
|
1235
|
+
KernelFutureHandler<
|
|
1236
|
+
KernelMessage.IShellControlMessage,
|
|
1237
|
+
KernelMessage.IShellControlMessage
|
|
1238
|
+
>
|
|
1239
|
+
>();
|
|
1240
|
+
this._comms = new Map<string, IComm>();
|
|
1241
|
+
this._displayIdToParentIds.clear();
|
|
1242
|
+
this._msgIdToDisplayIds.clear();
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Check to make sure it is okay to proceed to handle a message.
|
|
1247
|
+
*
|
|
1248
|
+
* #### Notes
|
|
1249
|
+
* Because we handle messages asynchronously, before a message is handled the
|
|
1250
|
+
* kernel might be disposed or restarted (and have a different session id).
|
|
1251
|
+
* This function throws an error in each of these cases. This is meant to be
|
|
1252
|
+
* called at the start of an asynchronous message handler to cancel message
|
|
1253
|
+
* processing if the message no longer is valid.
|
|
1254
|
+
*/
|
|
1255
|
+
protected _assertCurrentMessage(msg: KernelMessage.IMessage) {
|
|
1256
|
+
this._errorIfDisposed();
|
|
1257
|
+
|
|
1258
|
+
if (msg.header.session !== this._kernelSession) {
|
|
1259
|
+
throw new Error(`Canceling handling of old message: ${msg.header.msg_type}`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Handle a `comm_open` kernel message.
|
|
1265
|
+
*/
|
|
1266
|
+
protected async _handleCommOpen(msg: KernelMessage.ICommOpenMsg): Promise<void> {
|
|
1267
|
+
this._assertCurrentMessage(msg);
|
|
1268
|
+
const content = msg.content;
|
|
1269
|
+
const comm = new CommHandler(content.target_name, content.comm_id, this, () => {
|
|
1270
|
+
this._unregisterComm(content.comm_id);
|
|
1271
|
+
});
|
|
1272
|
+
this._comms.set(content.comm_id, comm);
|
|
1273
|
+
|
|
1274
|
+
try {
|
|
1275
|
+
const target = await Private.loadObject(
|
|
1276
|
+
content.target_name,
|
|
1277
|
+
content.target_module,
|
|
1278
|
+
this._targetRegistry,
|
|
1279
|
+
);
|
|
1280
|
+
await target(comm, msg);
|
|
1281
|
+
} catch (e) {
|
|
1282
|
+
// Close the comm asynchronously. We cannot block message processing on
|
|
1283
|
+
// kernel messages to wait for another kernel message.
|
|
1284
|
+
comm.close();
|
|
1285
|
+
console.error('Exception opening new comm');
|
|
1286
|
+
throw e;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Handle 'comm_close' kernel message.
|
|
1292
|
+
*/
|
|
1293
|
+
protected async _handleCommClose(msg: KernelMessage.ICommCloseMsg): Promise<void> {
|
|
1294
|
+
this._assertCurrentMessage(msg);
|
|
1295
|
+
const content = msg.content;
|
|
1296
|
+
const comm = this._comms.get(content.comm_id);
|
|
1297
|
+
if (!comm) {
|
|
1298
|
+
console.error('Comm not found for comm id ' + content.comm_id);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
this._unregisterComm(comm.commId);
|
|
1302
|
+
const onClose = comm.onClose;
|
|
1303
|
+
if (onClose) {
|
|
1304
|
+
// tslint:disable-next-line:await-promise
|
|
1305
|
+
await onClose(msg);
|
|
1306
|
+
}
|
|
1307
|
+
(comm as CommHandler).dispose();
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Handle a 'comm_msg' kernel message.
|
|
1312
|
+
*/
|
|
1313
|
+
protected async _handleCommMsg(msg: KernelMessage.ICommMsgMsg): Promise<void> {
|
|
1314
|
+
this._assertCurrentMessage(msg);
|
|
1315
|
+
const content = msg.content;
|
|
1316
|
+
const comm = this._comms.get(content.comm_id);
|
|
1317
|
+
if (!comm) {
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
const onMsg = comm.onMsg;
|
|
1321
|
+
if (onMsg) {
|
|
1322
|
+
// tslint:disable-next-line:await-promise
|
|
1323
|
+
await onMsg(msg);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Unregister a comm instance.
|
|
1329
|
+
*/
|
|
1330
|
+
protected _unregisterComm(commId: string) {
|
|
1331
|
+
this._comms.delete(commId);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Create the kernel websocket connection and add socket status handlers.
|
|
1336
|
+
*/
|
|
1337
|
+
protected _createSocket = (useProtocols = false) => {
|
|
1338
|
+
this._errorIfDisposed();
|
|
1339
|
+
|
|
1340
|
+
// Make sure the socket is clear
|
|
1341
|
+
this._clearSocket();
|
|
1342
|
+
|
|
1343
|
+
// Update the connection status to reflect opening a new connection.
|
|
1344
|
+
this._updateConnectionStatus('connecting');
|
|
1345
|
+
|
|
1346
|
+
const settings = this.serverSettings;
|
|
1347
|
+
|
|
1348
|
+
const partialUrl = URL.join(
|
|
1349
|
+
settings.wsUrl,
|
|
1350
|
+
restapi.KERNEL_SERVICE_URL,
|
|
1351
|
+
encodeURIComponent(this._id),
|
|
1352
|
+
);
|
|
1353
|
+
|
|
1354
|
+
// Strip any authentication from the display string.
|
|
1355
|
+
// eslint-disable-next-line no-useless-escape
|
|
1356
|
+
const display = partialUrl.replace(/^((?:\w+:)?\/\/)(?:[^@\/]+@)/, '$1');
|
|
1357
|
+
// eslint-disable-next-line no-console
|
|
1358
|
+
console.debug(`Starting WebSocket: ${display}`);
|
|
1359
|
+
|
|
1360
|
+
let url = URL.join(
|
|
1361
|
+
partialUrl,
|
|
1362
|
+
'channels?session_id=' + encodeURIComponent(this._clientId),
|
|
1363
|
+
);
|
|
1364
|
+
|
|
1365
|
+
// If token authentication is in use.
|
|
1366
|
+
const token = settings.token;
|
|
1367
|
+
if (settings.appendToken && token !== '') {
|
|
1368
|
+
url = url + `&token=${encodeURIComponent(token)}`;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Try opening the websocket with our list of subprotocols.
|
|
1372
|
+
// If the server doesn't handle subprotocols,
|
|
1373
|
+
// the accepted protocol will be ''.
|
|
1374
|
+
// But we cannot send '' as a subprotocol, so if connection fails,
|
|
1375
|
+
// reconnect without subprotocols.
|
|
1376
|
+
const supportedProtocols = useProtocols ? this._supportedProtocols : [];
|
|
1377
|
+
this._ws = new settings.WebSocket(url, supportedProtocols);
|
|
1378
|
+
|
|
1379
|
+
// Ensure incoming binary messages are not Blobs
|
|
1380
|
+
this._ws.binaryType = 'arraybuffer';
|
|
1381
|
+
|
|
1382
|
+
let alreadyCalledOnclose = false;
|
|
1383
|
+
|
|
1384
|
+
const getKernelModel = async (evt: Event) => {
|
|
1385
|
+
if (this._isDisposed) {
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
this._reason = '';
|
|
1389
|
+
this._model = undefined;
|
|
1390
|
+
try {
|
|
1391
|
+
const model = await this.kernelRestAPI.getKernelModel(this._id, settings);
|
|
1392
|
+
this._model = model;
|
|
1393
|
+
if (model?.execution_state === 'dead') {
|
|
1394
|
+
this._updateStatus('dead');
|
|
1395
|
+
} else {
|
|
1396
|
+
this._onWSClose(evt);
|
|
1397
|
+
}
|
|
1398
|
+
} catch (err: any) {
|
|
1399
|
+
// Try again, if there is a network failure
|
|
1400
|
+
// Handle network errors, as well as cases where we are on a
|
|
1401
|
+
// JupyterHub and the server is not running. JupyterHub returns a
|
|
1402
|
+
// 503 (<2.0) or 424 (>2.0) in that case.
|
|
1403
|
+
if (
|
|
1404
|
+
err instanceof NetworkError ||
|
|
1405
|
+
err.response?.status === 503 ||
|
|
1406
|
+
err.response?.status === 424
|
|
1407
|
+
) {
|
|
1408
|
+
const timeout = Private.getRandomIntInclusive(10, 30) * 1e3;
|
|
1409
|
+
setTimeout(getKernelModel, timeout, evt);
|
|
1410
|
+
} else {
|
|
1411
|
+
this._reason = 'Kernel died unexpectedly';
|
|
1412
|
+
this._updateStatus('dead');
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return;
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
const earlyClose = async (evt: Event) => {
|
|
1419
|
+
// If the websocket was closed early, that could mean
|
|
1420
|
+
// that the kernel is actually dead. Try getting
|
|
1421
|
+
// information about the kernel from the API call,
|
|
1422
|
+
// if that fails, then assume the kernel is dead,
|
|
1423
|
+
// otherwise just follow the typical websocket closed
|
|
1424
|
+
// protocol.
|
|
1425
|
+
if (alreadyCalledOnclose) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
alreadyCalledOnclose = true;
|
|
1429
|
+
await getKernelModel(evt);
|
|
1430
|
+
|
|
1431
|
+
return;
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
this._ws.onmessage = this._onWSMessage;
|
|
1435
|
+
this._ws.onopen = this._onWSOpen;
|
|
1436
|
+
this._ws.onclose = earlyClose;
|
|
1437
|
+
this._ws.onerror = earlyClose;
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* Handle connection status changes.
|
|
1442
|
+
*/
|
|
1443
|
+
protected _updateConnectionStatus(connectionStatus: ConnectionStatus): void {
|
|
1444
|
+
if (this._connectionStatus === connectionStatus) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
this._connectionStatus = connectionStatus;
|
|
1449
|
+
|
|
1450
|
+
// If we are not 'connecting', reset any reconnection attempts.
|
|
1451
|
+
if (connectionStatus !== 'connecting') {
|
|
1452
|
+
this._reconnectAttempt = 0;
|
|
1453
|
+
clearTimeout(this._reconnectTimeout);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (this.status !== 'dead') {
|
|
1457
|
+
if (connectionStatus === 'connected') {
|
|
1458
|
+
const restarting = this._kernelSession === RESTARTING_KERNEL_SESSION;
|
|
1459
|
+
|
|
1460
|
+
// Send a kernel info request to make sure we send at least one
|
|
1461
|
+
// message to get kernel status back. Always request kernel info
|
|
1462
|
+
// first, to get kernel status back and ensure iopub is fully
|
|
1463
|
+
// established. If we are restarting, this message will skip the queue
|
|
1464
|
+
// and be sent immediately.
|
|
1465
|
+
const p = this.requestKernelInfo();
|
|
1466
|
+
|
|
1467
|
+
// Send any pending messages after the kernelInfo resolves, or after a
|
|
1468
|
+
// timeout as a failsafe.
|
|
1469
|
+
|
|
1470
|
+
let sendPendingCalled = false;
|
|
1471
|
+
let timeoutHandle: any = null;
|
|
1472
|
+
const sendPendingOnce = () => {
|
|
1473
|
+
if (sendPendingCalled) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
sendPendingCalled = true;
|
|
1477
|
+
if (restarting && this._kernelSession === RESTARTING_KERNEL_SESSION) {
|
|
1478
|
+
// We were restarting and a message didn't arrive to set the
|
|
1479
|
+
// session, but we just assume the restart succeeded and send any
|
|
1480
|
+
// pending messages.
|
|
1481
|
+
|
|
1482
|
+
// FIXME: it would be better to retry the kernel_info_request here
|
|
1483
|
+
this._kernelSession = '';
|
|
1484
|
+
}
|
|
1485
|
+
if (timeoutHandle) {
|
|
1486
|
+
clearTimeout(timeoutHandle);
|
|
1487
|
+
}
|
|
1488
|
+
if (this._pendingMessages.length > 0) {
|
|
1489
|
+
this._sendPending();
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
void p.then(sendPendingOnce);
|
|
1493
|
+
// FIXME: if sent while zmq subscriptions are not established,
|
|
1494
|
+
// kernelInfo may not resolve, so use a timeout to ensure we don't hang forever.
|
|
1495
|
+
// It may be preferable to retry kernelInfo rather than give up after one timeout.
|
|
1496
|
+
timeoutHandle = setTimeout(sendPendingOnce, KERNEL_INFO_TIMEOUT);
|
|
1497
|
+
} else {
|
|
1498
|
+
// If the connection is down, then we do not know what is happening
|
|
1499
|
+
// with the kernel, so set the status to unknown.
|
|
1500
|
+
this._updateStatus('unknown');
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Notify others that the connection status changed.
|
|
1505
|
+
this.connectionStatusChangedEmitter.fire(connectionStatus);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
protected async _handleMessage(msg: KernelMessage.IMessage): Promise<void> {
|
|
1509
|
+
let handled = false;
|
|
1510
|
+
|
|
1511
|
+
// Check to see if we have a display_id we need to reroute.
|
|
1512
|
+
if (
|
|
1513
|
+
msg.parent_header &&
|
|
1514
|
+
msg.channel === 'iopub' &&
|
|
1515
|
+
(isDisplayDataMsg(msg) || isUpdateDisplayDataMsg(msg) || isExecuteResultMsg(msg))
|
|
1516
|
+
) {
|
|
1517
|
+
// display_data messages may re-route based on their display_id.
|
|
1518
|
+
const _transient = (msg.content.transient ?? {}) as JSONObject;
|
|
1519
|
+
const displayId = _transient['display_id'] as string;
|
|
1520
|
+
if (displayId) {
|
|
1521
|
+
handled = await this._handleDisplayId(displayId, msg);
|
|
1522
|
+
// The await above may make this message out of date, so check again.
|
|
1523
|
+
this._assertCurrentMessage(msg);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (!handled && msg.parent_header) {
|
|
1528
|
+
const parentHeader = msg.parent_header as KernelMessage.IHeader;
|
|
1529
|
+
const future = this._futures?.get(parentHeader.msg_id);
|
|
1530
|
+
if (future) {
|
|
1531
|
+
await future.handleMsg(msg);
|
|
1532
|
+
this._assertCurrentMessage(msg);
|
|
1533
|
+
} else {
|
|
1534
|
+
// If the message was sent by us and was not iopub, it is orphaned.
|
|
1535
|
+
const owned = parentHeader.session === this.clientId;
|
|
1536
|
+
if (msg.channel !== 'iopub' && owned) {
|
|
1537
|
+
this.unhandledMessageEmitter.fire(msg);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (msg.channel === 'iopub') {
|
|
1542
|
+
switch (msg.header.msg_type) {
|
|
1543
|
+
case 'status': {
|
|
1544
|
+
// Updating the status is synchronous, and we call no async user code
|
|
1545
|
+
const executionState = (msg as KernelMessage.IStatusMsg).content
|
|
1546
|
+
.execution_state;
|
|
1547
|
+
if (executionState === 'restarting') {
|
|
1548
|
+
// The kernel has been auto-restarted by the server. After
|
|
1549
|
+
// processing for this message is completely done, we want to
|
|
1550
|
+
// handle this restart, so we don't await, but instead schedule
|
|
1551
|
+
// the work as a microtask (i.e., in a promise resolution). We
|
|
1552
|
+
// schedule this here so that it comes before any microtasks that
|
|
1553
|
+
// might be scheduled in the status signal emission below.
|
|
1554
|
+
void Promise.resolve().then(async () => {
|
|
1555
|
+
this._updateStatus('autorestarting');
|
|
1556
|
+
this._clearKernelState();
|
|
1557
|
+
|
|
1558
|
+
// We must reconnect since the kernel connection information may have
|
|
1559
|
+
// changed, and the server only refreshes its zmq connection when a new
|
|
1560
|
+
// websocket is opened.
|
|
1561
|
+
await this.reconnect();
|
|
1562
|
+
return;
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
this._updateStatus(executionState);
|
|
1566
|
+
break;
|
|
1567
|
+
}
|
|
1568
|
+
case 'comm_open':
|
|
1569
|
+
if (this.handleComms) {
|
|
1570
|
+
await this._handleCommOpen(msg as KernelMessage.ICommOpenMsg);
|
|
1571
|
+
}
|
|
1572
|
+
break;
|
|
1573
|
+
case 'comm_msg':
|
|
1574
|
+
if (this.handleComms) {
|
|
1575
|
+
await this._handleCommMsg(msg as KernelMessage.ICommMsgMsg);
|
|
1576
|
+
}
|
|
1577
|
+
break;
|
|
1578
|
+
case 'comm_close':
|
|
1579
|
+
if (this.handleComms) {
|
|
1580
|
+
await this._handleCommClose(msg as KernelMessage.ICommCloseMsg);
|
|
1581
|
+
}
|
|
1582
|
+
break;
|
|
1583
|
+
default:
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
// If the message was a status dead message, we might have disposed ourselves.
|
|
1587
|
+
if (!this.isDisposed) {
|
|
1588
|
+
this._assertCurrentMessage(msg);
|
|
1589
|
+
// the message wouldn't be emitted if we were disposed anyway.
|
|
1590
|
+
this.iopubMessageEmitter.fire(msg as KernelMessage.IIOPubMessage);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* Attempt a connection if we have not exhausted connection attempts.
|
|
1597
|
+
*/
|
|
1598
|
+
protected _reconnect() {
|
|
1599
|
+
this._errorIfDisposed();
|
|
1600
|
+
|
|
1601
|
+
// Clear any existing reconnection attempt
|
|
1602
|
+
clearTimeout(this._reconnectTimeout);
|
|
1603
|
+
|
|
1604
|
+
// Update the connection status and schedule a possible reconnection.
|
|
1605
|
+
if (this._reconnectAttempt < this._reconnectLimit) {
|
|
1606
|
+
this._updateConnectionStatus('connecting');
|
|
1607
|
+
|
|
1608
|
+
// The first reconnect attempt should happen immediately, and subsequent
|
|
1609
|
+
// attempts should pick a random number in a growing range so that we
|
|
1610
|
+
// don't overload the server with synchronized reconnection attempts
|
|
1611
|
+
// across multiple kernels.
|
|
1612
|
+
const timeout = Private.getRandomIntInclusive(
|
|
1613
|
+
0,
|
|
1614
|
+
1e3 * (Math.pow(2, this._reconnectAttempt) - 1),
|
|
1615
|
+
);
|
|
1616
|
+
console.warn(
|
|
1617
|
+
`Connection lost, reconnecting in ${Math.floor(timeout / 1000)} seconds.`,
|
|
1618
|
+
);
|
|
1619
|
+
// Try reconnection with subprotocols if the server had supported them.
|
|
1620
|
+
// Otherwise, try reconnection without subprotocols.
|
|
1621
|
+
const useProtocols = this._selectedProtocol !== '' ? true : false;
|
|
1622
|
+
this._reconnectTimeout = setTimeout(this._createSocket, timeout, useProtocols);
|
|
1623
|
+
this._reconnectAttempt += 1;
|
|
1624
|
+
} else {
|
|
1625
|
+
this._updateConnectionStatus('disconnected');
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Clear the websocket event handlers and the socket itself.
|
|
1629
|
+
this._clearSocket();
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* Utility function to throw an error if this instance is disposed.
|
|
1634
|
+
*/
|
|
1635
|
+
protected _errorIfDisposed() {
|
|
1636
|
+
if (this.isDisposed) {
|
|
1637
|
+
throw new Error('Kernel connection is disposed');
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Make websocket callbacks arrow functions so they bind `this`.
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Handle a websocket open event.
|
|
1645
|
+
*/
|
|
1646
|
+
protected _onWSOpen = () => {
|
|
1647
|
+
if (
|
|
1648
|
+
this._ws!.protocol !== '' &&
|
|
1649
|
+
!this._supportedProtocols.includes(this._ws!.protocol)
|
|
1650
|
+
) {
|
|
1651
|
+
console.warn('Server selected unknown kernel wire protocol:', this._ws!.protocol);
|
|
1652
|
+
this._updateStatus('dead');
|
|
1653
|
+
throw new Error(`Unknown kernel wire protocol: ${this._ws!.protocol}`);
|
|
1654
|
+
}
|
|
1655
|
+
// Remember the kernel wire protocol selected by the server.
|
|
1656
|
+
this._selectedProtocol = this._ws!.protocol;
|
|
1657
|
+
this._ws!.onclose = this._onWSClose;
|
|
1658
|
+
this._ws!.onerror = this._onWSClose;
|
|
1659
|
+
this._updateConnectionStatus('connected');
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Handle a websocket message, validating and routing appropriately.
|
|
1664
|
+
*/
|
|
1665
|
+
protected _onWSMessage = (evt: MessageEvent) => {
|
|
1666
|
+
// Notify immediately if there is an error with the message.
|
|
1667
|
+
let msg: KernelMessage.IMessage;
|
|
1668
|
+
try {
|
|
1669
|
+
msg = deserialize(evt.data, this._ws!.protocol);
|
|
1670
|
+
validate.validateMessage(msg);
|
|
1671
|
+
} catch (error: any) {
|
|
1672
|
+
error.message = `Kernel message validation error: ${error.message}`;
|
|
1673
|
+
// We throw the error so that it bubbles up to the top, and displays the right stack.
|
|
1674
|
+
throw error;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Update the current kernel session id
|
|
1678
|
+
this._kernelSession = msg.header.session;
|
|
1679
|
+
|
|
1680
|
+
// Handle the message asynchronously, in the order received.
|
|
1681
|
+
this._msgChain = this._msgChain
|
|
1682
|
+
.then(() => {
|
|
1683
|
+
// Return so that any promises from handling a message are fulfilled
|
|
1684
|
+
// before proceeding to the next message.
|
|
1685
|
+
return this._handleMessage(msg);
|
|
1686
|
+
})
|
|
1687
|
+
.catch((error) => {
|
|
1688
|
+
// Log any errors in handling the message, thus resetting the _msgChain
|
|
1689
|
+
// promise so we can process more messages.
|
|
1690
|
+
// Ignore the "Canceled" errors that are thrown during kernel dispose.
|
|
1691
|
+
if (error.message.startsWith('Canceled future for ')) {
|
|
1692
|
+
console.error(error);
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
// Emit the message receive signal
|
|
1697
|
+
this.anyMessageEmitter.fire({ msg, direction: 'recv' });
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Handle a websocket close event.
|
|
1702
|
+
*/
|
|
1703
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1704
|
+
protected _onWSClose = (_evt: Event) => {
|
|
1705
|
+
if (!this.isDisposed) {
|
|
1706
|
+
this._reconnect();
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
get hasPendingInput(): boolean {
|
|
1711
|
+
return this._hasPendingInput;
|
|
1712
|
+
}
|
|
1713
|
+
set hasPendingInput(value: boolean) {
|
|
1714
|
+
this._hasPendingInput = value;
|
|
1715
|
+
this.pendingInputEmitter.fire(value);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
protected _id = '';
|
|
1719
|
+
protected _name = '';
|
|
1720
|
+
protected _model: IKernelModel | undefined;
|
|
1721
|
+
@prop()
|
|
1722
|
+
protected _status: KernelMessage.Status = 'unknown';
|
|
1723
|
+
protected _connectionStatus: ConnectionStatus = 'connecting';
|
|
1724
|
+
protected _kernelSession = '';
|
|
1725
|
+
protected _clientId: string;
|
|
1726
|
+
protected _isDisposed = false;
|
|
1727
|
+
/**
|
|
1728
|
+
* Websocket to communicate with kernel.
|
|
1729
|
+
*/
|
|
1730
|
+
protected _ws: WebSocket | null = null;
|
|
1731
|
+
protected _username = '';
|
|
1732
|
+
protected _reconnectLimit = 7;
|
|
1733
|
+
protected _reconnectAttempt = 0;
|
|
1734
|
+
protected _reconnectTimeout: any = null;
|
|
1735
|
+
protected _supportedProtocols: string[] = Object.values(
|
|
1736
|
+
KernelMessage.supportedKernelWebSocketProtocols,
|
|
1737
|
+
);
|
|
1738
|
+
protected _selectedProtocol = '';
|
|
1739
|
+
|
|
1740
|
+
protected _futures = new Map<
|
|
1741
|
+
string,
|
|
1742
|
+
KernelFutureHandler<
|
|
1743
|
+
KernelMessage.IShellControlMessage,
|
|
1744
|
+
KernelMessage.IShellControlMessage
|
|
1745
|
+
>
|
|
1746
|
+
>();
|
|
1747
|
+
protected _comms = new Map<string, IComm>();
|
|
1748
|
+
protected _targetRegistry: Record<
|
|
1749
|
+
string,
|
|
1750
|
+
(comm: IComm, msg: KernelMessage.ICommOpenMsg) => void
|
|
1751
|
+
> = Object.create(null);
|
|
1752
|
+
protected _info = new Deferred<KernelMessage.IInfoReply>();
|
|
1753
|
+
protected _pendingMessages: KernelMessage.IMessage[] = [];
|
|
1754
|
+
protected _specPromise: Promise<ISpecModel | undefined>;
|
|
1755
|
+
protected statusChangedEmitter = new Emitter<KernelMessage.Status>();
|
|
1756
|
+
protected connectionStatusChangedEmitter = new Emitter<ConnectionStatus>();
|
|
1757
|
+
protected onDisposedEmitter = new Emitter<void>();
|
|
1758
|
+
protected iopubMessageEmitter = new Emitter<KernelMessage.IIOPubMessage>();
|
|
1759
|
+
protected anyMessageEmitter = new Emitter<IAnyMessageArgs>();
|
|
1760
|
+
protected pendingInputEmitter = new Emitter<boolean>();
|
|
1761
|
+
protected unhandledMessageEmitter = new Emitter<KernelMessage.IMessage>();
|
|
1762
|
+
protected _displayIdToParentIds = new Map<string, string[]>();
|
|
1763
|
+
protected _msgIdToDisplayIds = new Map<string, string[]>();
|
|
1764
|
+
protected _msgChain: Promise<void> = Promise.resolve();
|
|
1765
|
+
protected _hasPendingInput = false;
|
|
1766
|
+
protected _reason = '';
|
|
1767
|
+
protected _noOp = () => {
|
|
1768
|
+
/* no-op */
|
|
1769
|
+
};
|
|
1770
|
+
}
|