Race condition with ipykernel / nbextension comms


#1

Hi, I have a question about best practices with notebook extensions and ipykernel comms.

I have an nbextension and a corresponding Python library which communicates with it via comms. When Jupyter loads the notebook page, it also loads the extension, which registers a comm.
Then when the user runs the Python library, the kernel sends a message which is picked up by the comm and activates the nbextension’s display logic.

However, there’s a race condition that happens when the user runs the Python library before the nbextension is loaded. In that case, the nbextension hasn’t registered the comm (since it hasn’t yet loaded). Once the kernel sends the message, I get this JS console error in Jupyter:

utils.js:930 Could not open comm  --  Error: Class react.featuresapp not found in registry 
    at utils.js:894
    at new Promise (<anonymous>)
    at Object.load_class (utils.js:879)
    at CommManager.comm_open (comm.js:84)
    at i (jquery.min.js:2)
    at Kernel._handle_iopub_message (kernel.js:1223)
    at Kernel._finish_ws_message (kernel.js:1015)
    at kernel.js:1006

My overall question is: what’s the best-practice way to fix this? Some ideas:

  • have the Python backend somehow detect that the comm failed and retry the message (this would still have an ugly console error though)
  • maybe some way for the backend to register the extension before running it?

Also, minor suggestion: could we create a category on discourse.jupyter.org for ipykernel/nbextension issues? Or is there already a good forum for that?

Thanks,

Vaughn


#2

I believe you can make sure your extension is loaded with:

require(["nbextensions/myextension"], function (myext) {
    ...

to ensure that when the javascript is displayed, it doesn’t execute until the extension is loaded.

could we create a category on discourse.jupyter.org for ipykernel/nbextension issues? Or is there already a good forum for that?

We’ve been prototyping this discourse by focusing on the JupyterHub/Binder subset of the community so far (not strictly excluding questions on other topics like this one), but as we expand, we should add such labels, definitely.


#3

Hey thanks @minrk. I think I’m not understanding your suggestion though. The problem I’m seeing is that the Python code is sending a comm message before the extension has a chance to load. Are you saying that I can prevent CommManager.comm_open from running and hitting that exception?

One way to prevent this bug would be to have CommManager store the message if it can’t find a comm recipient, and when that recipient is added, send it the comm message after a brief delay, but that requires a fix to the comms code in Jupyter.

Here’s the code I’m using to handle comms:

// When Jupyter loads, it has a list of registered extensions.
// jupyter-react-js looks for this function signature and runs it to initialize the react window.
// See https://github.com/timbr-io/jupyter-react-js
function load_ipython_extension () {
  console.log('Features: react.featuresapp extension loaded into notebook.')
  // eslint-disable-next-line no-undef
  requirejs([
    'base/js/namespace',
    'base/js/events',
  ], function( Jupyter, events ) {
    // This scope is run when the page loads.
    console.log('Registering react.featuresapp with JupyterReact')
    JupyterReact.init( Jupyter, events, 'react.featuresapp', { components, save: false } )
  })
}

which itself calls:

function init( Jupyter, events, commTarget, componentParams ) {

  requirejs([ "services/kernels/comm" ], function( Comm ) {
    /**
     * handle_kernel
     * registers comm targets with the kernel comm_manager
     * when new comms are open, renders a Parent component that takes over rendering of actual components
     */
    const handle_kernel = ( Jupyter, kernel ) => {
      // register the target comm / listens for new comms
      kernel.comm_manager.register_target( commTarget, ( comm, msg ) => {
      ...