vatlab / sos-notebook

Multi-language Jupyter Notebook
http://vatlab.github.io/SoS
BSD 3-Clause "New" or "Revised" License
176 stars 17 forks source link

Widget support for SoS Notebook. #234

Closed BoPeng closed 5 years ago

BoPeng commented 5 years ago

SoS kernel blocks communication between kernels and frontend so that progress bar and other widgets from subkernels stopped working. We ignored this problem before but should try to address it now.

The following is just an example from tqdm webpage:

from tqdm import tnrange, tqdm_notebook
from time import sleep

for i in tnrange(3, desc='1st loop'):
    for j in tqdm_notebook(range(100), desc='2nd loop'):
        sleep(0.01)

image

BoPeng commented 5 years ago

Similar effect in

import ipywidgets as widgets
widgets.IntSlider()

image

BoPeng commented 5 years ago

This is the illustration and we need to think how to insert the SoS kernel in it.

https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Low%20Level.html

image

BoPeng commented 5 years ago

image

Output of widgets works, but interactions still not (frontend sending message to subkernel directly).

BoPeng commented 5 years ago

Note that ipywidget saves parent_header's msg_id for internal use, and we will need to keep msg_id consistent.

https://github.com/jupyter-widgets/ipywidgets/blob/67af059687205f10a339472f72cd86d2b34948c2/ipywidgets/widgets/widget_output.py#L50-L55

BoPeng commented 5 years ago

Try to organize my thought on what is happening:

  1. When SoS execute a cell, an execute_request is sent with a msg_id (let us name it sos_msg_id). This is the "master message" for the cell, and should be the parent_header of all messages if anything needs to be displayed in the notebook. Note that this msg_id appears to be created by the frontend, and used by the frontend to display only relevant messages.

  2. SoS calls run_cell and send the code to the subkernel with another execute_request message, which returns another msg_id (named subkernel_msg_id).

  3. SoS monitors output from the kernel. All messages have a parent_header of the subkernel_msg_id. SoS resend the messages with sos_msg_id so that they can be displayed in SoS notebook.

The problem with widgets is that the widgets are created with the subkernel_msg_id. Here are what will happen

  1. If we send comm_open messages with sos_msg_id, the widget will not work because the sos kernel does not handle widget.

  2. If we send comm_open messages with subkernel_msg_id, the widget will not be displayed because the header IDs are different.

It seems necessary to translate the IDs or force the use of sos_msg_id in subkernel_msg_id.

BoPeng commented 5 years ago

OK, the frontend comm_msg works like this:

  1. When the subkernel sends comm_open message, the SoS kernel can record who is creating what comm.
  2. When the user presses a frontend widget, some comm_msg will be sent to the SoS kernel.
  3. The SoS kernel looks up the subkernel and forward the comm_msg to the subkernel.
  4. The subkernel sometimes sends some responses to ioPub
  5. The SoS kernel catches and forward it to the frontend.

The problem is with 4, because SoS does not actively listen to comm_msg messages from iopub subkernels (it only checks it when the subkernel is being executed). Therefore the SoS will not be able to process the comm_msg in time.

To solve this problem, the SoS kernel will have to actively check ioPub channel of all subkernels and forward comm_msg to the frontend.

BoPeng commented 5 years ago

Finally, the solution is to add a function that forward all ioPub messages to frontend even when no cell is being evaluated. This allows frontend actions to be handled by the SoS kernel.

image

The function is called by the controller in the controller's messaging loop in a separate thread so it responds a bit slower than the native widgets.

ktaletsk commented 5 years ago

Hi @BoPeng , thank you for this fix! I can't make it fully working, though. I am able to create the widget in Python3 subkernel and interact with it, but the number below is not updated. It is working fine in the SoS kernel or in vanilla Python3 notebook. It is possible that I am not installing something properly, so I give the sequence of steps below:

  1. Use the latest jupyter-minimal container: docker run -it -p 8888:8888 jupyter/minimal-notebook:307ad2bb5fce /bin/bash

  2. Install ipywidgets, verify they work

    a. conda install -c conda-forge ipywidgets b. jupyter labextension install @jupyter-widgets/jupyterlab-manager c. Start Jupyter Lab d. Create a notebook with the following cell:

    from ipywidgets import interact, interactive, fixed, interact_manual
    import ipywidgets as widgets
    def f(x):
            return x
    interact(f, x=10);
  3. Install SoS and try interactive widgets again a. pip install sos==0.19.14 sos-notebook==0.19.14 sos-python==0.18.1 b. python -m sos_notebook.install c. jupyter labextension install jupyterlab-sos d. Start Jupyter, choose SoS kernel, choose Python3 subkernel e. Run the same cell f. The number below is not updated and stays at 10

    Screen Shot 2019-07-31 at 1 42 49 PM
ktaletsk commented 5 years ago

Update: the same problem if installing from the latest GitHub version (which I should have used):

pip install git+https://github.com/vatlab/sos
pip install git+https://github.com/vatlab/sos-notebook.git
pip install git+https://github.com/vatlab/sos-python.git
BoPeng commented 5 years ago

I only tested for classic jupyter and I am testing JupyterLab now. It is likely that jupyterlab-sos is not playing well with @jupyter-widgets.

ktaletsk commented 5 years ago

@BoPeng you are right indeed. If I do not install @jupyter-widgets, widgets are fully working in the classic notebook, but obviously not in the JupyterLab. If I install @jupyter-widgets, it does not work anywhere, not classic, not JupyterLab

BoPeng commented 5 years ago

Here with @jupyterlab-widgets installed widgets in classic jupyter works... As far as I can tell the sos kernel is sending the correct messages to the frontend and I am trying to figure out why the linked number is not updated.

BoPeng commented 5 years ago

After hours of message tracing, I finally understand what is going on here but I am not sure if I can find a solution. Allow me to explain the problem in detail:

image

  1. When the slider is clicked, a comm_msg is generated, and is forwarded to the subkernel by SoS.
  2. The subkernel respond with a series of messages, status (busy), comm_msg, clear_output, display_data, comm_msg, and status (idle). These messages are forwarded by SoS to the frontend, and updates the widget.

This is how widget is supported by SoS Notebook.

In JupyterLab, the same messages are sent but the frontend is not updated. Under the hood the following has happened:

  1. When the slide is clicked, a comm_msg is generated, and is forwarded to the subkernel by SoS.
  2. The SoS kernel sent messages status (busy) and status (idle) to the frontend. Because the future set for comm_msg is resolved by the appearance of status (idle), the comm_msg future is resolved and removed from this._futures.
  3. When a series of messages are forwarded from subkernel to frontend, they are ignored because the parent message comm_msg has already been resolved.

So the difference between classic Jupyter and JupyterLab is the status response of the SoS kernel to the comm_msg. The only "proper" solution seems to put the SoS kernel in busy mode with comm_msg.

BoPeng commented 5 years ago

@ktaletsk I have submitted a patch. Please let me know if widget support is still not working on your side.

ktaletsk commented 5 years ago

@BoPeng it is working, thank you!