Bokeh server: Is it possible to push updates to JS in the middle of a python callback?

I have a use case where I want the change of a Select widget to trigger changes in two other widgets in my layout. However, one of the updates can happen immediately, and the other requires calculations which take a few seconds. I would like to be able to have the update to the first element happen immediately, and the second happen later, when it’s ready. However, because Bokeh server batches updates, the update first update does not display until the second is ready. Here’s a toy example:

from time import sleep

from bokeh.io import curdoc

from bokeh.layouts import column

from bokeh.models.widgets import Select, Div

INITIAL = ‘Option1’

select = Select(

options=[‘Option1’, ‘Option2’, ‘Option3’],

value=INITIAL

)

div_1 = Div(text=INITIAL)

div_2 = Div(text=INITIAL)

def select_change(attr, old, new):

div_1.text = ‘{} (fast update)’.format(new)

stand-in for a long-running calculation

sleep(2)

div_2.text = ‘{} (slow update)’.format(new)

select.on_change(‘value’, select_change)

layout = column(select, div_1, div_2)

curdoc().add_root(layout)

``

Is there a way to force Bokeh to push the update to div_1 immediately and not wait for the sleep(2) line to finish?

Thanks,

Linus

Hi,

Please provide version/platform information. When I try this code on master (1.1.1dev) on OSX/Safari I see the fast update immediately, and the slow update two seconds later.

Thanks,

Bryan

···

On Apr 30, 2019, at 12:13 PM, [email protected] wrote:

from time import sleep

from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models.widgets import Select, Div

INITIAL = 'Option1'

select = Select(
    options=['Option1', 'Option2', 'Option3'],
    value=INITIAL
)

div_1 = Div(text=INITIAL)
div_2 = Div(text=INITIAL)

def select_change(attr, old, new):
    div_1.text = '{} (fast update)'.format(new)

    # stand-in for a long-running calculation
    sleep(2)

    div_2.text = '{} (slow update)'.format(new)

select.on_change('value', select_change)

layout = column(select, div_1, div_2)

curdoc().add_root(layout)

Hi Bryan,

Thanks for the response. This was originally happening on Windows 10 with Python 3.6 and Bokeh 1.0.4. I have also been able to replicate same issue with the above example on Windows 10, Python 3.7 and Bokeh 1.1.0 and on (k)ubuntu 19.04, Python 3.7, Bokeh 1.1.0.

This happens when I use either Chrome, Firefox or Edge, although I don’t think it’s a front end problem. Looking at the web socket messages, the server sends the fast and slow updates at (essentially) the same time (see attached image):

Does Bokeh use some os-level interface to batch events? That would explain why OSX would be doing something different that Windows and Linux. I couldn’t find where in the source code that the batching happens, but if you point me to it I can help explore this.

Thanks,

Linus

···

On Wednesday, May 1, 2019 at 4:56:10 PM UTC-4, Bryan Van de Ven wrote:

Hi,

Please provide version/platform information. When I try this code on master (1.1.1dev) on OSX/Safari I see the fast update immediately, and the slow update two seconds later.

Thanks,

Bryan

On Apr 30, 2019, at 12:13 PM, [email protected] wrote:

from time import sleep

from bokeh.io import curdoc

from bokeh.layouts import column

from bokeh.models.widgets import Select, Div

INITIAL = ‘Option1’

select = Select(

options=['Option1', 'Option2', 'Option3'],
value=INITIAL

)

div_1 = Div(text=INITIAL)

div_2 = Div(text=INITIAL)

def select_change(attr, old, new):

div_1.text = '{} (fast update)'.format(new)
# stand-in for a long-running calculation
sleep(2)
div_2.text = '{} (slow update)'.format(new)

select.on_change(‘value’, select_change)

layout = column(select, div_1, div_2)

curdoc().add_root(layout)

Hi,

In the process of writing some notes below for you I think I see why this may be happening, tho I am not all sure then why it would seem not to happen for me on OSX. Will take some investigation and I am not sure what we might be able to do about it.

TLDR: This decorator:

  https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L47

collects all the pending writes that would happen as a result of a callback, then yields them all in sequence after the callback is executed. See below for the gory details.

### Notes (started writing these first then added the note above)

It sounds like this will require some investigation. I'm currently most familiar with these code paths, but even I don't have to get into them very often, that part of the project is very well covered in tests and does not change often. I'm traveling currently, so I won't be able to dig in to this immediately, but if you want to poke around, here's a rough idea of the code paths:

* Document._notify_change

  https://github.com/bokeh/bokeh/blob/master/bokeh/document/document.py#L988

  This gets invoked when any model's property changes. This is true and independent of a Document being used with the server, but it is most used/useful in the context of the bokeh server

* Document.on_change_dispatch_to

  https://github.com/bokeh/bokeh/blob/master/bokeh/document/document.py#L667

  The Session calls this on its Document, with itself as the target, to make sure any changes get routed to it to handle.

The exact hook up to the next step is a bit confusing, there is simple multiple dispatch mechanism that obscures the codepaths somewhat. But the end result is that those change events trigger this method:

* Session._document_patched:

  https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L210

  The session is basically the bridge between a Document and the connection to the browser. This method tells the connection to generate a wire protocol message for the changes (to send to BokehJS) and appends a future that can send that message, to a list of pending writes

* Connection.send_patch_document

  https://github.com/bokeh/bokeh/blob/master/bokeh/server/connection.py#L74

  This is where there protocol message generation and the yield message.send come from

So where/when do those pending writes actually get processed? There is a "document lock" decorator that was applied to all the session callbacks:

  https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L47

  That decorator collects all the writes during a callback, and then yields them...Oh that actually seems like it would explain what you are seeing.. I will add a note up top...

For reference, that decorator gets applied via here:

  https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L123

Thanks,

Bryan

···

On May 2, 2019, at 9:12 AM, [email protected] wrote:

Hi Bryan,

Thanks for the response. This was originally happening on Windows 10 with Python 3.6 and Bokeh 1.0.4. I have also been able to replicate same issue with the above example on Windows 10, Python 3.7 and Bokeh 1.1.0 and on (k)ubuntu 19.04, Python 3.7, Bokeh 1.1.0.

This happens when I use either Chrome, Firefox or Edge, although I don't think it's a front end problem. Looking at the web socket messages, the server sends the fast and slow updates at (essentially) the same time (see attached image):

Does Bokeh use some os-level interface to batch events? That would explain why OSX would be doing something different that Windows and Linux. I couldn't find where in the source code that the batching happens, but if you point me to it I can help explore this.

Thanks,
Linus

On Wednesday, May 1, 2019 at 4:56:10 PM UTC-4, Bryan Van de Ven wrote:
Hi,

Please provide version/platform information. When I try this code on master (1.1.1dev) on OSX/Safari I see the fast update immediately, and the slow update two seconds later.

Thanks,

Bryan

> On Apr 30, 2019, at 12:13 PM, lmar...@gmail.com wrote:
>
> from time import sleep
>
> from bokeh.io import curdoc
> from bokeh.layouts import column
> from bokeh.models.widgets import Select, Div
>
> INITIAL = 'Option1'
>
> select = Select(
> options=['Option1', 'Option2', 'Option3'],
> value=INITIAL
> )
>
> div_1 = Div(text=INITIAL)
> div_2 = Div(text=INITIAL)
>
>
> def select_change(attr, old, new):
> div_1.text = '{} (fast update)'.format(new)
>
> # stand-in for a long-running calculation
> sleep(2)
>
> div_2.text = '{} (slow update)'.format(new)
>
>
> select.on_change('value', select_change)
>
> layout = column(select, div_1, div_2)
>
> curdoc().add_root(layout)

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/d024a41c-9719-47e5-902c-cf537ec4c406%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
<Bokeh ws.PNG>

Thanks for the detailed notes! I will try to look into this.

···

On Thursday, May 2, 2019 at 12:28:18 PM UTC-4, Bryan Van de Ven wrote:

Hi,

In the process of writing some notes below for you I think I see why this may be happening, tho I am not all sure then why it would seem not to happen for me on OSX. Will take some investigation and I am not sure what we might be able to do about it.

TLDR: This decorator:

    [https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L47](https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L47)

collects all the pending writes that would happen as a result of a callback, then yields them all in sequence after the callback is executed. See below for the gory details.

Notes (started writing these first then added the note above)

It sounds like this will require some investigation. I’m currently most familiar with these code paths, but even I don’t have to get into them very often, that part of the project is very well covered in tests and does not change often. I’m traveling currently, so I won’t be able to dig in to this immediately, but if you want to poke around, here’s a rough idea of the code paths:

  • Document._notify_change

      [https://github.com/bokeh/bokeh/blob/master/bokeh/document/document.py#L988](https://github.com/bokeh/bokeh/blob/master/bokeh/document/document.py#L988)
    
    
    
      This gets invoked when any model's property changes. This is true and independent of a Document being used with the server, but it is most used/useful in the context of the bokeh server
    
  • Document.on_change_dispatch_to

      [https://github.com/bokeh/bokeh/blob/master/bokeh/document/document.py#L667](https://github.com/bokeh/bokeh/blob/master/bokeh/document/document.py#L667)
    
    
    
      The Session calls this on its Document, with itself as the target, to make sure any changes get routed to it to handle.
    

The exact hook up to the next step is a bit confusing, there is simple multiple dispatch mechanism that obscures the codepaths somewhat. But the end result is that those change events trigger this method:

  • Session._document_patched:

      [https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L210](https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L210)
    
    
    
      The session is basically the bridge between a Document and the connection to the browser. This method tells the connection to generate a wire protocol message for the changes (to send to BokehJS) and appends a future that can send that message, to a list of pending writes
    
  • Connection.send_patch_document

      [https://github.com/bokeh/bokeh/blob/master/bokeh/server/connection.py#L74](https://github.com/bokeh/bokeh/blob/master/bokeh/server/connection.py#L74)
    
    
    
      This is where there protocol message generation and the yield message.send  come from
    

So where/when do those pending writes actually get processed? There is a “document lock” decorator that was applied to all the session callbacks:

    [https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L47](https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L47)



    That decorator collects all the writes during a callback, and then yields them...Oh that actually seems like it would explain what you are seeing.. I will add a note up top...

For reference, that decorator gets applied via here:

    [https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L123](https://github.com/bokeh/bokeh/blob/master/bokeh/server/session.py#L123)

Thanks,

Bryan

On May 2, 2019, at 9:12 AM, [email protected] wrote:

Hi Bryan,

Thanks for the response. This was originally happening on Windows 10 with Python 3.6 and Bokeh 1.0.4. I have also been able to replicate same issue with the above example on Windows 10, Python 3.7 and Bokeh 1.1.0 and on (k)ubuntu 19.04, Python 3.7, Bokeh 1.1.0.

This happens when I use either Chrome, Firefox or Edge, although I don’t think it’s a front end problem. Looking at the web socket messages, the server sends the fast and slow updates at (essentially) the same time (see attached image):

Does Bokeh use some os-level interface to batch events? That would explain why OSX would be doing something different that Windows and Linux. I couldn’t find where in the source code that the batching happens, but if you point me to it I can help explore this.

Thanks,

Linus

On Wednesday, May 1, 2019 at 4:56:10 PM UTC-4, Bryan Van de Ven wrote:

Hi,

Please provide version/platform information. When I try this code on master (1.1.1dev) on OSX/Safari I see the fast update immediately, and the slow update two seconds later.

Thanks,

Bryan

On Apr 30, 2019, at 12:13 PM, [email protected] wrote:

from time import sleep

from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models.widgets import Select, Div

INITIAL = ‘Option1’

select = Select(
options=[‘Option1’, ‘Option2’, ‘Option3’],
value=INITIAL
)

div_1 = Div(text=INITIAL)
div_2 = Div(text=INITIAL)

def select_change(attr, old, new):
div_1.text = ‘{} (fast update)’.format(new)

# stand-in for a long-running calculation
sleep(2)

div_2.text = '{} (slow update)'.format(new)

select.on_change(‘value’, select_change)

layout = column(select, div_1, div_2)

curdoc().add_root(layout)


You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/d024a41c-9719-47e5-902c-cf537ec4c406%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

i can confirm that the fast and slow updates in the OP’s example occur simultaneously on an ubuntu 18.04 singularity 3.4 container with bokeh 1.4.0. is there a workaround?

@bjarthur70 You should use a next_tick_callback as described here:

Although there was some question previously, I would say now that the expected behavior is firmly established that changes are only ever synchronized at the end of the function. Now that Bokeh 2.0 will soon be python >= 3.6 only, would like to explore the possibility of allowing await statements in the middle of calbacks for situations like this. But for now, next_tick_callback is the answer.

1 Like