Handling exceptions in application

Hi,

I have a question about handling exceptions in a Bokeh application. We have a “bokeh serve” application which, if it has an unhandled exception, prevents all later sessions from working. This is different from other frameworks, in which a request exception affects only that request, but the application can continue to serve traffic after an exception.

This is the same issue raised in https://groups.google.com/a/continuum.io/d/topic/bokeh/nqiMn756zq0/discussion, in which Bryan said the described behaviour was unexpected. But no example code was produced. So I will produce some, copying from Bokeh server — Bokeh 3.3.2 Documentation

myapp.py

from random import random

from bokeh.layouts import column

from bokeh.models import Button

from bokeh.palettes import RdYlBu3

from bokeh.plotting import figure, curdoc

create a plot and style its properties

p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)

p.border_fill_color = ‘black’

p.background_fill_color = ‘black’

p.outline_line_color = None

p.grid.grid_line_color = None

add a text renderer to our plot (no data yet)

r = p.text(x=, y=, text=, text_color=, text_font_size=“20pt”,

       text_baseline="middle", text_align="center")

i = 0

ds = r.data_source

create a callback that will add a number in a random location

def callback():

global i
# BEST PRACTICE --- update .data in one step with a new dict
new_data = dict()
new_data['x'] = ds.data['x'] + [random()*70 + 15]
new_data['y'] = ds.data['y'] + [random()*70 + 15]
new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
new_data['text'] = ds.data['text'] + [str(i)]
ds.data = new_data
i = i + 1

add a button widget and configure with the call back

button = Button(label=“Press Me”)

button.on_click(callback)

boom = random()

print(boom)

if boom < 0.3333:

raise Exception(‘boom!’)

put the button and plot in a layout and add to the document

curdoc().add_root(column(button, p))

This is exactly the same code as from the user guide, except that 33% of the time it will raise an exception.

To duplicate the issue, do:

  1. Run “bokeh serve myapp.py”

  2. Open a browser to http://localhost:5006/boom. If you don’t see “Exception: boom!” in the console, you can click the “Press Me” button to see numbers appear.

  3. Refresh the page until you see “Exception: boom!” in the console. The Bokeh app won’t appear in the browser.

  4. Refresh the page again. The app still doesn’t appear.

  5. Restart the server and refresh the page. If the exception isn’t raised, the app will load. It will work until a new session triggers the exception.

This makes it difficult to run a reliable Bokeh service, because crashes aren’t isolated to the request that caused the crash.

I’m not familiar with Bokeh internals, but if I had to guess, could this be due to how Applications seem to use a single ScriptHandler/DirectoryHandler (not one per session), which has a failed attribute that prevents the Bokeh user code from running when the attribute is set? See https://github.com/bokeh/bokeh/blob/master/bokeh/application/handlers/code.py#L138

If this is unexpected behaviour, I’ll make a GitHub issue, but some direction on a workaround would be appreciated.

I’d also like to capture exceptions and report to Sentry.io, but that seems to be closely related, so I’ll wait for the resolution of this.

Thanks!

Matt Fox

Hi,

I think I see the distinction now. In my experience, exceptions *in callbacks* do not cause any problem with new sessions (and in fact if you move the "boom" below into the callback you can see new sessions are created just fine). But this example demonstrates a different modality, namely an exception happening during the initial document creation. I'd say this is a legitimate issue that should be fixed/improved, so please make a GH issue with this example.

Thanks,

Bryan

···

On Jun 26, 2018, at 19:34, [email protected] wrote:

# myapp.py

from random import random

from bokeh.layouts import column
from bokeh.models import Button
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc

# create a plot and style its properties
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None

# add a text renderer to our plot (no data yet)
r = p.text(x=, y=, text=, text_color=, text_font_size="20pt",
           text_baseline="middle", text_align="center")

i = 0

ds = r.data_source

# create a callback that will add a number in a random location
def callback():
    global i
    
    # BEST PRACTICE --- update .data in one step with a new dict
    new_data = dict()
    new_data['x'] = ds.data['x'] + [random()*70 + 15]
    new_data['y'] = ds.data['y'] + [random()*70 + 15]
    new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
    new_data['text'] = ds.data['text'] + [str(i)]
    ds.data = new_data

    i = i + 1

# add a button widget and configure with the call back
button = Button(label="Press Me")
button.on_click(callback)

boom = random()
print(boom)
if boom < 0.3333:
  raise Exception('boom!')
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))

Thanks Bryan.

Now for my own information, could you describe the best practice for loading data in Bokeh? I have an app that loads different datasets depending on an argument in the URL. It could be that one dataset loads fine and one dataset triggers an exception, and this impacts all new sessions because currently the user data is fetched and handled in main.py (supposing a directory-structured app). Is it preferable to set up the plots and widgets in main.py and then load all user data and push to the document in an on_session_created hook? This would put the code most likely to encounter errors in a place I could handle them and keep the errors isolated. Or is there a different accepted practice for this?

Regards,

Matt

···

On Tuesday, June 26, 2018 at 5:35:17 PM UTC-7, ma…@kerkhofftech.ca wrote:

Hi,

I have a question about handling exceptions in a Bokeh application. We have a “bokeh serve” application which, if it has an unhandled exception, prevents all later sessions from working. This is different from other frameworks, in which a request exception affects only that request, but the application can continue to serve traffic after an exception.

This is the same issue raised in https://groups.google.com/a/continuum.io/d/topic/bokeh/nqiMn756zq0/discussion, in which Bryan said the described behaviour was unexpected. But no example code was produced. So I will produce some, copying from https://bokeh.pydata.org/en/latest/docs/user_guide/server.html#single-module-format:

myapp.py

from random import random

from bokeh.layouts import column

from bokeh.models import Button

from bokeh.palettes import RdYlBu3

from bokeh.plotting import figure, curdoc

create a plot and style its properties

p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)

p.border_fill_color = ‘black’

p.background_fill_color = ‘black’

p.outline_line_color = None

p.grid.grid_line_color = None

add a text renderer to our plot (no data yet)

r = p.text(x=, y=, text=, text_color=, text_font_size=“20pt”,

       text_baseline="middle", text_align="center")

i = 0

ds = r.data_source

create a callback that will add a number in a random location

def callback():

global i
# BEST PRACTICE --- update .data in one step with a new dict
new_data = dict()
new_data['x'] = ds.data['x'] + [random()*70 + 15]
new_data['y'] = ds.data['y'] + [random()*70 + 15]
new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
new_data['text'] = ds.data['text'] + [str(i)]
ds.data = new_data
i = i + 1

add a button widget and configure with the call back

button = Button(label=“Press Me”)

button.on_click(callback)

boom = random()

print(boom)

if boom < 0.3333:

raise Exception(‘boom!’)

put the button and plot in a layout and add to the document

curdoc().add_root(column(button, p))

This is exactly the same code as from the user guide, except that 33% of the time it will raise an exception.

To duplicate the issue, do:

  1. Run “bokeh serve myapp.py”
  1. Open a browser to http://localhost:5006/boom. If you don’t see “Exception: boom!” in the console, you can click the “Press Me” button to see numbers appear.
  1. Refresh the page until you see “Exception: boom!” in the console. The Bokeh app won’t appear in the browser.
  1. Refresh the page again. The app still doesn’t appear.
  1. Restart the server and refresh the page. If the exception isn’t raised, the app will load. It will work until a new session triggers the exception.

This makes it difficult to run a reliable Bokeh service, because crashes aren’t isolated to the request that caused the crash.

I’m not familiar with Bokeh internals, but if I had to guess, could this be due to how Applications seem to use a single ScriptHandler/DirectoryHandler (not one per session), which has a failed attribute that prevents the Bokeh user code from running when the attribute is set? See https://github.com/bokeh/bokeh/blob/master/bokeh/application/handlers/code.py#L138

If this is unexpected behaviour, I’ll make a GitHub issue, but some direction on a workaround would be appreciated.

I’d also like to capture exceptions and report to Sentry.io, but that seems to be closely related, so I’ll wait for the resolution of this.

Thanks!

Matt Fox

Hi,

I don't have any best practices to recommend. As I said I think this is a bug to be fixed, and an exception in one session's document creation should not affect other sessions. That said:

* if the data to be loaded is always the same, putting it in a lifecycle hook might be a way to avoid paying unnecessary overhead of loading the same thing repeatedly

* in any case putting code they may raise exceptions in a lifecycle hook seems like it might be an immediate workaround for this issue (but I have not confirmed)

Otherwise, the only other workaround I can think if is to employ try/except blocks in the app code to prevent any exceptions from escaping.

Thanks,

Bryan

···

On Jun 27, 2018, at 13:53, [email protected] wrote:

Thanks Bryan.

Now for my own information, could you describe the best practice for loading data in Bokeh? I have an app that loads different datasets depending on an argument in the URL. It could be that one dataset loads fine and one dataset triggers an exception, and this impacts all new sessions because currently the user data is fetched and handled in main.py (supposing a directory-structured app). Is it preferable to set up the plots and widgets in main.py and then load all user data and push to the document in an on_session_created hook? This would put the code most likely to encounter errors in a place I could handle them and keep the errors isolated. Or is there a different accepted practice for this?

Regards,
Matt

On Tuesday, June 26, 2018 at 5:35:17 PM UTC-7, ma...@kerkhofftech.ca wrote:
Hi,

I have a question about handling exceptions in a Bokeh application. We have a "bokeh serve" application which, if it has an unhandled exception, prevents all later sessions from working. This is different from other frameworks, in which a request exception affects only that request, but the application can continue to serve traffic after an exception.

This is the same issue raised in https://groups.google.com/a/continuum.io/d/topic/bokeh/nqiMn756zq0/discussion, in which Bryan said the described behaviour was unexpected. But no example code was produced. So I will produce some, copying from Bokeh server — Bokeh 3.3.2 Documentation

# myapp.py

from random import random

from bokeh.layouts import column
from bokeh.models import Button
from bokeh.palettes import RdYlBu3
from bokeh.plotting import figure, curdoc

# create a plot and style its properties
p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)
p.border_fill_color = 'black'
p.background_fill_color = 'black'
p.outline_line_color = None
p.grid.grid_line_color = None

# add a text renderer to our plot (no data yet)
r = p.text(x=, y=, text=, text_color=, text_font_size="20pt",
           text_baseline="middle", text_align="center")

i = 0

ds = r.data_source

# create a callback that will add a number in a random location
def callback():
    global i
    
    # BEST PRACTICE --- update .data in one step with a new dict
    new_data = dict()
    new_data['x'] = ds.data['x'] + [random()*70 + 15]
    new_data['y'] = ds.data['y'] + [random()*70 + 15]
    new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
    new_data['text'] = ds.data['text'] + [str(i)]
    ds.data = new_data

    i = i + 1

# add a button widget and configure with the call back
button = Button(label="Press Me")
button.on_click(callback)

boom = random()
print(boom)
if boom < 0.3333:
  raise Exception('boom!')
# put the button and plot in a layout and add to the document
curdoc().add_root(column(button, p))

This is exactly the same code as from the user guide, except that 33% of the time it will raise an exception.

To duplicate the issue, do:
1. Run "bokeh serve myapp.py"
2. Open a browser to http://localhost:5006/boom\. If you don't see "Exception: boom!" in the console, you can click the "Press Me" button to see numbers appear.
3. Refresh the page until you see "Exception: boom!" in the console. The Bokeh app won't appear in the browser.
4. Refresh the page again. The app still doesn't appear.
5. Restart the server and refresh the page. If the exception isn't raised, the app will load. It will work until a new session triggers the exception.

This makes it difficult to run a reliable Bokeh service, because crashes aren't isolated to the request that caused the crash.

I'm not familiar with Bokeh internals, but if I had to guess, could this be due to how Applications seem to use a single ScriptHandler/DirectoryHandler (not one per session), which has a failed attribute that prevents the Bokeh user code from running when the attribute is set? See https://github.com/bokeh/bokeh/blob/master/bokeh/application/handlers/code.py#L138

If this is unexpected behaviour, I'll make a GitHub issue, but some direction on a workaround would be appreciated.

I'd also like to capture exceptions and report to Sentry.io, but that seems to be closely related, so I'll wait for the resolution of this.

Thanks!

Matt Fox

--
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/32a99cec-b3dc-4dc5-9f00-3af424fb1a0e%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Thanks, I appreciate it.

I’ve posted an issue: Unhandled exception in application causes all later sessions to fail · Issue #8034 · bokeh/bokeh · GitHub.

-MF

···

On Wednesday, June 27, 2018 at 11:58:41 AM UTC-7, Bryan Van de ven wrote:

Hi,

I don’t have any best practices to recommend. As I said I think this is a bug to be fixed, and an exception in one session’s document creation should not affect other sessions. That said:

  • if the data to be loaded is always the same, putting it in a lifecycle hook might be a way to avoid paying unnecessary overhead of loading the same thing repeatedly

  • in any case putting code they may raise exceptions in a lifecycle hook seems like it might be an immediate workaround for this issue (but I have not confirmed)

Otherwise, the only other workaround I can think if is to employ try/except blocks in the app code to prevent any exceptions from escaping.

Thanks,

Bryan

On Jun 27, 2018, at 13:53, ma…@kerkhofftech.ca wrote:

Thanks Bryan.

Now for my own information, could you describe the best practice for loading data in Bokeh? I have an app that loads different datasets depending on an argument in the URL. It could be that one dataset loads fine and one dataset triggers an exception, and this impacts all new sessions because currently the user data is fetched and handled in main.py (supposing a directory-structured app). Is it preferable to set up the plots and widgets in main.py and then load all user data and push to the document in an on_session_created hook? This would put the code most likely to encounter errors in a place I could handle them and keep the errors isolated. Or is there a different accepted practice for this?

Regards,

Matt

On Tuesday, June 26, 2018 at 5:35:17 PM UTC-7, ma…@kerkhofftech.ca wrote:

Hi,

I have a question about handling exceptions in a Bokeh application. We have a “bokeh serve” application which, if it has an unhandled exception, prevents all later sessions from working. This is different from other frameworks, in which a request exception affects only that request, but the application can continue to serve traffic after an exception.

This is the same issue raised in https://groups.google.com/a/continuum.io/d/topic/bokeh/nqiMn756zq0/discussion, in which Bryan said the described behaviour was unexpected. But no example code was produced. So I will produce some, copying from https://bokeh.pydata.org/en/latest/docs/user_guide/server.html#single-module-format:

myapp.py

from random import random

from bokeh.layouts import column

from bokeh.models import Button

from bokeh.palettes import RdYlBu3

from bokeh.plotting import figure, curdoc

create a plot and style its properties

p = figure(x_range=(0, 100), y_range=(0, 100), toolbar_location=None)

p.border_fill_color = ‘black’

p.background_fill_color = ‘black’

p.outline_line_color = None

p.grid.grid_line_color = None

add a text renderer to our plot (no data yet)

r = p.text(x=, y=, text=, text_color=, text_font_size=“20pt”,

       text_baseline="middle", text_align="center")

i = 0

ds = r.data_source

create a callback that will add a number in a random location

def callback():

global i
# BEST PRACTICE --- update .data in one step with a new dict
new_data = dict()
new_data['x'] = ds.data['x'] + [random()*70 + 15]
new_data['y'] = ds.data['y'] + [random()*70 + 15]
new_data['text_color'] = ds.data['text_color'] + [RdYlBu3[i%3]]
new_data['text'] = ds.data['text'] + [str(i)]
ds.data = new_data
i = i + 1

add a button widget and configure with the call back

button = Button(label=“Press Me”)

button.on_click(callback)

boom = random()

print(boom)

if boom < 0.3333:

raise Exception(‘boom!’)

put the button and plot in a layout and add to the document

curdoc().add_root(column(button, p))

This is exactly the same code as from the user guide, except that 33% of the time it will raise an exception.

To duplicate the issue, do:

  1. Run “bokeh serve myapp.py”
  1. Open a browser to http://localhost:5006/boom. If you don’t see “Exception: boom!” in the console, you can click the “Press Me” button to see numbers appear.
  1. Refresh the page until you see “Exception: boom!” in the console. The Bokeh app won’t appear in the browser.
  1. Refresh the page again. The app still doesn’t appear.
  1. Restart the server and refresh the page. If the exception isn’t raised, the app will load. It will work until a new session triggers the exception.

This makes it difficult to run a reliable Bokeh service, because crashes aren’t isolated to the request that caused the crash.

I’m not familiar with Bokeh internals, but if I had to guess, could this be due to how Applications seem to use a single ScriptHandler/DirectoryHandler (not one per session), which has a failed attribute that prevents the Bokeh user code from running when the attribute is set? See https://github.com/bokeh/bokeh/blob/master/bokeh/application/handlers/code.py#L138

If this is unexpected behaviour, I’ll make a GitHub issue, but some direction on a workaround would be appreciated.

I’d also like to capture exceptions and report to Sentry.io, but that seems to be closely related, so I’ll wait for the resolution of this.

Thanks!

Matt Fox


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/32a99cec-b3dc-4dc5-9f00-3af424fb1a0e%40continuum.io.

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