Skip to content

Commit 504f6cc

Browse files
committed
widgets: implement using websocket for sending buffers so compute servers fully work too.
- NOTE: we'll benchmark and maybe use http for non-compute servers; not sure.
1 parent b5fe24a commit 504f6cc

File tree

7 files changed

+54
-29
lines changed

7 files changed

+54
-29
lines changed

src/packages/frontend/client/project.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,22 @@ export class ProjectClient {
506506
path: string,
507507
model_id: string,
508508
buffer_path: string,
509+
useHttp?: boolean, // ONLY works for home base, NOT compute servers!
509510
): Promise<ArrayBuffer> {
510-
const url = ipywidgetsGetBufferUrl(project_id, path, model_id, buffer_path);
511-
return await (await fetch(url)).arrayBuffer();
511+
if (useHttp) {
512+
const url = ipywidgetsGetBufferUrl(
513+
project_id,
514+
path,
515+
model_id,
516+
buffer_path,
517+
);
518+
return await (await fetch(url)).arrayBuffer();
519+
}
520+
const actions = redux.getEditorActions(project_id, path);
521+
return await actions.jupyter_actions.ipywidgetsGetBuffer(
522+
model_id,
523+
buffer_path,
524+
);
512525
}
513526

514527
// getting, setting, editing, deleting, etc., the api keys for a project

src/packages/frontend/jupyter/browser-actions.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import {
3535
} from "../misc/local-storage";
3636
import { parse_headings } from "./contents";
3737
import { webapp_client } from "@cocalc/frontend/webapp-client";
38-
import { bufferToBase64 } from "@cocalc/util/base64";
38+
import { bufferToBase64, base64ToBuffer } from "@cocalc/util/base64";
39+
import { reuseInFlight } from "@cocalc/util/reuse-in-flight";
3940

4041
export class JupyterActions extends JupyterActions0 {
4142
public widget_manager?: WidgetManager;
@@ -476,6 +477,16 @@ export class JupyterActions extends JupyterActions0 {
476477
return msg_id;
477478
};
478479

480+
ipywidgetsGetBuffer = reuseInFlight(
481+
async (model_id: string, buffer_path: string): Promise<ArrayBuffer> => {
482+
const { buffer64 } = await this.api_call("ipywidgets-get-buffer", {
483+
model_id,
484+
buffer_path,
485+
});
486+
return base64ToBuffer(buffer64);
487+
},
488+
);
489+
479490
// NOTE: someday move this to the frame-tree actions, since it would
480491
// be generically useful!
481492
// Display a confirmation dialog, then return the chosen option.

src/packages/frontend/jupyter/widgets/manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type DeserializedModelState = { [key: string]: any };
3434

3535
export type SendCommFunction = (string, data) => string;
3636

37-
// const log = console.log;
37+
//const log = console.log;
3838
const log = (..._args) => {};
3939

4040
export class WidgetManager {

src/packages/jupyter/kernel/kernel.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,8 +985,12 @@ class JupyterKernel extends EventEmitter implements JupyterKernelInterface {
985985

986986
public ipywidgetsGetBuffer(
987987
model_id: string,
988-
buffer_path: string,
988+
// buffer_path is the string[] *or* the JSON of that.
989+
buffer_path: string | string[],
989990
): Buffer | undefined {
991+
if (typeof buffer_path != "string") {
992+
buffer_path = JSON.stringify(buffer_path);
993+
}
990994
return this._actions?.syncdb.ipywidgets_state?.getBuffer(
991995
model_id,
992996
buffer_path,

src/packages/jupyter/kernel/websocket-api.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ various messages related to working with Jupyter.
1010

1111
import { get_existing_kernel } from "@cocalc/jupyter/kernel";
1212
import { get_kernel_data } from "@cocalc/jupyter/kernel/kernel-data";
13+
import { bufferToBase64 } from "@cocalc/util/base64";
1314

1415
export async function handleApiRequest(
1516
path: string,
@@ -37,15 +38,20 @@ export async function handleApiRequest(
3738
case "save_ipynb_file":
3839
await kernel.save_ipynb_file();
3940
return {};
41+
4042
case "signal":
4143
kernel.signal(query.signal);
4244
return {};
45+
4346
case "kernel_info":
4447
return await kernel.kernel_info();
48+
4549
case "more_output":
4650
return kernel.more_output(query.id);
51+
4752
case "complete":
4853
return await kernel.complete(get_code_and_cursor_pos(query));
54+
4955
case "introspect":
5056
const { code, cursor_pos } = get_code_and_cursor_pos(query);
5157
let detail_level = 0;
@@ -64,6 +70,7 @@ export async function handleApiRequest(
6470
cursor_pos,
6571
detail_level,
6672
});
73+
6774
case "store":
6875
const { key, value } = query;
6976
if (value === undefined) {
@@ -77,8 +84,21 @@ export async function handleApiRequest(
7784
kernel.store.set(key, value);
7885
return {};
7986
}
87+
8088
case "comm":
8189
return kernel.send_comm_message_to_kernel(query);
90+
91+
case "ipywidgets-get-buffer":
92+
const { model_id, buffer_path } = query;
93+
const buffer = kernel.ipywidgetsGetBuffer(model_id, buffer_path);
94+
if (buffer == null) {
95+
throw Error(
96+
`no buffer for model=${model_id}, buffer_path=${JSON.stringify(
97+
buffer_path,
98+
)}`,
99+
);
100+
}
101+
return { buffer64: bufferToBase64(buffer) };
82102
default:
83103
throw Error(`unknown endpoint "${endpoint}"`);
84104
}

src/packages/project/jupyter/http-server.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,6 @@ function jupyter_kernel_info_handler(router): void {
6464
}
6565
);
6666

67-
router.get(
68-
BASE + "ipywidgets-get-buffer-info",
69-
async function (req, res): Promise<void> {
70-
try {
71-
const { path, model_id, buffer_path } = req.query;
72-
const kernel = get_existing_kernel(path);
73-
if (kernel == null) {
74-
res.status(404).send(`kernel associated to ${path} does not exist`);
75-
return;
76-
}
77-
const buffer = kernel.ipywidgetsGetBuffer(model_id, buffer_path);
78-
res.send({
79-
path,
80-
model_id,
81-
buffer_path,
82-
buffer_length: buffer?.length,
83-
});
84-
} catch (err) {
85-
res.status(500).send(`Error getting ipywidgets buffer info - ${err}`);
86-
}
87-
}
88-
);
89-
9067
// we are only actually using this to serve up the logo.
9168
router.get(BASE + "kernelspecs/*", async function (req, res): Promise<void> {
9269
try {

src/packages/sync/editor/generic/ipywidgets-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ export class IpywidgetsState extends EventEmitter {
315315
};
316316

317317
private clientGetBuffer = async (model_id: string, path: string) => {
318-
// async get of the buffer efficiently via HTTP:
318+
// async get of the buffer from backend
319319
if (this.client.ipywidgetsGetBuffer == null) {
320320
throw Error(
321321
"NotImplementedError: frontend client must implement ipywidgetsGetBuffer in order to support binary buffers",

0 commit comments

Comments
 (0)