Skip to content

Commit 0296e03

Browse files
committed
Refine loadFromKernel function to handle asynchronous issues
- We need the models registered synchronously before they are actually created, so I broke the comm/model instantiation into two steps to make sure that new_model is the first synchronous call in its function. - renamed loadFromKernelSlow to loadFromKernelModels to be more descriptive - clean up a few obsolete comments - Make sure we are using the appropriate buffers for a given widget's state.
1 parent a8a9a35 commit 0296e03

File tree

2 files changed

+76
-43
lines changed

2 files changed

+76
-43
lines changed

jupyterlab_widgets/src/manager.ts

-2
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,6 @@ class WidgetManager extends ManagerBase<Widget> implements IDisposable {
216216
return;
217217
}
218218
await this.context.sessionContext.ready;
219-
// TODO: when we upgrade to @jupyterlab/services 4.1 or later, we can
220-
// remove this 'any' cast.
221219
if (this.context.sessionContext.session?.kernel.handleComms === false) {
222220
return;
223221
}

packages/base/src/manager-base.ts

+76-41
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ abstract class ManagerBase<T> {
385385
/**
386386
* Fetch all widgets states from the kernel using the control comm channel
387387
* If this fails (control comm handler not implemented kernel side),
388-
* it will fallback to `_loadFromKernelSlow`.
388+
* it will fall back to `_loadFromKernelModels`.
389389
*
390390
* This is a utility function that can be used in subclasses.
391391
*/
@@ -438,67 +438,102 @@ abstract class ManagerBase<T> {
438438
initComm.close();
439439
} catch (error) {
440440
console.warn(
441-
'Failed to fetch widgets through the "jupyter.widget.control" comm channel, fallback to slow fetching of widgets. Reason:',
441+
'Failed to fetch ipywidgets through the "jupyter.widget.control" comm channel, fallback to fetching individual model state. Reason:',
442442
error
443443
);
444-
// Fallback to the old implementation for old ipywidgets backend versions (<=7.6)
445-
return this._loadFromKernelSlow();
444+
// Fall back to the old implementation for old ipywidgets backend versions (ipywidgets<=7.6)
445+
return this._loadFromKernelModels();
446446
}
447447

448448
const states: any = data.states;
449-
450-
// Extract buffer paths
451449
const bufferPaths: any = {};
452-
for (const bufferPath of data.buffer_paths) {
453-
if (!bufferPaths[bufferPath[0]]) {
454-
bufferPaths[bufferPath[0]] = [];
450+
const bufferGroups: any = {};
451+
452+
// Group buffers and buffer paths by widget id
453+
for (let i = 0; i < data.buffer_paths.length; i++) {
454+
const [widget_id, ...path] = data.buffer_paths[i];
455+
const b = buffers[i];
456+
if (!bufferPaths[widget_id]) {
457+
bufferPaths[widget_id] = [];
458+
bufferGroups[widget_id] = [];
455459
}
456-
bufferPaths[bufferPath[0]].push(bufferPath.slice(1));
460+
bufferPaths[widget_id].push(path);
461+
bufferGroups[widget_id].push(b);
457462
}
458463

459-
// Start creating all widgets
460-
await Promise.all(
464+
// Create comms for all new widgets.
465+
let widget_comms = await Promise.all(
461466
Object.keys(states).map(async (widget_id) => {
467+
let comm = undefined;
468+
let modelPromise = undefined;
462469
try {
463-
const state = states[widget_id];
464-
const comm = await this._create_comm('jupyter.widget', widget_id);
465-
466-
// Put binary buffers
467-
if (widget_id in bufferPaths) {
468-
const nBuffers = bufferPaths[widget_id].length;
469-
utils.put_buffers(
470-
state,
471-
bufferPaths[widget_id],
472-
buffers.splice(0, nBuffers)
473-
);
470+
modelPromise = this.get_model(widget_id);
471+
if (modelPromise === undefined) {
472+
comm = await this._create_comm('jupyter.widget', widget_id);
473+
} else {
474+
// For JLab, the promise is rejected, so we have to await to
475+
// find out if it is actually a model.
476+
await modelPromise;
477+
}
478+
} catch (e) {
479+
// The JLab widget manager will throw an error with this specific error message.
480+
if (e.message !== 'widget model not found') {
481+
throw e;
474482
}
475-
476-
await this.new_model(
477-
{
478-
model_name: state.model_name,
479-
model_module: state.model_module,
480-
model_module_version: state.model_module_version,
481-
model_id: widget_id,
482-
comm: comm,
483-
},
484-
state.state
485-
);
486-
} catch (error) {
487-
// Failed to create a widget model, we continue creating other models so that
488-
// other widgets can render
489-
console.error(error);
483+
comm = await this._create_comm('jupyter.widget', widget_id);
490484
}
485+
return {widget_id, comm}
491486
})
492-
);
487+
)
488+
489+
await Promise.all(widget_comms.map(async ({widget_id, comm}) => {
490+
const state = states[widget_id];
491+
// Put binary buffers
492+
if (widget_id in bufferPaths) {
493+
utils.put_buffers(
494+
state,
495+
bufferPaths[widget_id],
496+
bufferGroups[widget_id]
497+
);
498+
}
499+
try {
500+
501+
if (comm === undefined) {
502+
// model already exists here
503+
const model = await this.get_model(widget_id);
504+
model!.set_state(state.state);
505+
} else {
506+
// This must be the first await in the code path that
507+
// reaches here so that registering the model promise in
508+
// new_model can register the widget promise before it may
509+
// be required by other widgets.
510+
await this.new_model(
511+
{
512+
model_name: state.model_name,
513+
model_module: state.model_module,
514+
model_module_version: state.model_module_version,
515+
model_id: widget_id,
516+
comm: comm,
517+
},
518+
state.state
519+
);
520+
}
521+
522+
} catch (error) {
523+
// Failed to create a widget model, we continue creating other models so that
524+
// other widgets can render
525+
console.error(error);
526+
}
527+
}));
493528
}
494529

495530
/**
496-
* Old implementation of fetching widgets one by one using
531+
* Old implementation of fetching widget models one by one using
497532
* the request_state message on each comm.
498533
*
499534
* This is a utility function that can be used in subclasses.
500535
*/
501-
protected async _loadFromKernelSlow(): Promise<void> {
536+
protected async _loadFromKernelModels(): Promise<void> {
502537
const comm_ids = await this._get_comm_info();
503538

504539
// For each comm id that we do not know about, create the comm, and request the state.

0 commit comments

Comments
 (0)