Skip to content

Commit d8e7fb6

Browse files
committed
Fix #3469 by making a copy of the callbacks dict.
This partially reverts changes from #3470. I think the underlying assumptions around send_sync_message and callbacks are: 1. send_sync_message is only ever called once per message. When the original sync happens, either we put the message on the queue or we send it immediately, and the callback modifications happen only when we actually send the message. 2. There was an implicit assumption in the send_sync_message code is that the callbacks object passed in is a new object unique to this message, i.e., we aren't modifying a global object, that then we'll modify again later. I think assumption 2 is sometimes not true, so this guards against it by making a (2-deep) copy of the callbacks object before modifying and using it.
1 parent d1491e9 commit d8e7fb6

File tree

1 file changed

+12
-10
lines changed

1 file changed

+12
-10
lines changed

packages/base/src/widget.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,10 @@ export class WidgetModel extends Backbone.Model {
334334
if (this.comm !== void 0) {
335335
if (msg.content.execution_state === 'idle') {
336336
this._pending_msgs--;
337+
// Sanity check for logic errors that may push this below zero.
337338
if (this._pending_msgs < 0) {
338339
console.error(
339-
`Pending messages < 0 (=${this._pending_msgs}), which is unexpected`
340+
`Jupyter Widgets message throttle: Pending messages < 0 (=${this._pending_msgs}), which is unexpected. Resetting to 0 to continue.`
340341
);
341342
this._pending_msgs = 0; // do not break message throttling in case of unexpected errors
342343
}
@@ -553,17 +554,18 @@ export class WidgetModel extends Backbone.Model {
553554
return '';
554555
}
555556
try {
556-
callbacks.iopub = callbacks.iopub || {};
557-
if (callbacks.iopub.previouslyUsedByJupyterWidgets === undefined) {
558-
// Do not break other code that also wants to listen to status updates
559-
callbacks.iopub.statusPrevious = callbacks.iopub.status;
560-
}
561-
// else callbacks.iopub.status is the old handler, that we should not reuse
562-
callbacks.iopub.previouslyUsedByJupyterWidgets = true;
557+
// Make a 2-deep copy so we don't modify the caller's callbacks object.
558+
callbacks = {
559+
shell: { ...callbacks.shell },
560+
iopub: { ...callbacks.iopub },
561+
input: callbacks.input,
562+
};
563+
// Save the caller's status callback so we can call it after we handle the message.
564+
const statuscb = callbacks.iopub.status;
563565
callbacks.iopub.status = (msg: KernelMessage.IStatusMsg): void => {
564566
this._handle_status(msg);
565-
if (callbacks.iopub.statusPrevious) {
566-
callbacks.iopub.statusPrevious(msg);
567+
if (statuscb) {
568+
statuscb(msg);
567569
}
568570
};
569571

0 commit comments

Comments
 (0)