Bokeh server external param update '_pending_writes' error

Not sure if this is the correct place, as it may be more ‘param’/‘panel’ related.

I’m using PyQt to provide some cusomisation to a plot running via a Bokeh server, using param to update. I’m able to update a slider widget in the browser from python server side, but when trying to update a Bokeh plot, I get the error:

RuntimeError: _pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes

The same occurs with tkinter, using the example from https://github.com/pyviz/panel/issues/344 as a basis.

Any pointers as to what I’m doing wrong would be much appreciated!

Simple example below with tk and the sine wave from https://panel.pyviz.org/user_guide/Django_Apps.html:

import asyncio
import tkinter as tk
from functools import partial
from threading import Thread

import numpy as np
import panel as pn
import param
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from panel.io import get_server

class SineWave(param.Parameterized):

  offset = param.Number(default=0.0, bounds=(-5.0,5.0))
  amplitude = param.Number(default=1.0, bounds=(-5.0,5.0))
  phase = param.Number(default=0.0,bounds=(0.0,2*np.pi))
  frequency = param.Number(default=1.0, bounds=(0.1, 5.1))
  N = param.Integer(default=200, bounds=(0,None))
  x_range = param.Range(default=(0, 4*np.pi),bounds=(0,4*np.pi))
  y_range = param.Range(default=(-2.5,2.5),bounds=(-10,10))

  def __init__(self, **params):
    super().__init__(**params)
    x, y = self.sine()
    self.cds = ColumnDataSource(data=dict(x=x, y=y))
    self.plot = figure(
      plot_height=400,
      plot_width=400,
      tools='pan,wheel_zoom,box_zoom,save,reset',
      x_range=self.x_range,
      y_range=self.y_range,
      sizing_mode='stretch_both',
    )
    self.plot.line('x', 'y', source=self.cds, line_width=3, line_alpha=0.6)

  @param.depends(
    'N',
    'frequency',
    'amplitude',
    'offset',
    'phase',
    'x_range',
    'y_range',
    watch=True
  )
  def update_plot(self):
    x, y = self.sine()
    self.cds.data = dict(x=x, y=y)
    self.plot.x_range.start, self.plot.x_range.end = self.x_range
    self.plot.y_range.start, self.plot.y_range.end = self.y_range

  def sine(self):
    x = np.linspace(0, 4*np.pi, self.N)
    y = self.amplitude*np.sin(self.frequency*x + self.phase) + self.offset
    return x, y

  def set_offset(self, offset):
    self.offset = offset

sinwave = SineWave()
row = pn.Row(sinwave.plot)

def get_server_in_thread(**kwargs):
  asyncio.set_event_loop(asyncio.new_event_loop())
  get_server(**kwargs)

class Display():

  def __init__(self):
    Thread(target=partial(get_server_in_thread,panel=row,start=True,show=True,port=5010)).start()
    self.root = tk.Tk()
    self.slider = tk.Scale(self.root, from_=0., to=1., resolution=0.1,
                 orient=tk.HORIZONTAL, command=self.on_slider_change)
    self.slider.pack()
    self.root.mainloop()

  def on_slider_change(self, event):
    sinwave.offset = float(event)

display = Display()

That message indicates that you are trying to update Bokeh models outside API callbacks e.g on_change, on_event, periodic, timeout, or “next tick” callbacks. These callbacks perform the necessary locking around the Document and all updates to Bokeh models should be performed in one of them. Please see the User’s Guide section Updating From Threads and possibly Updating From Unlocked Callbacks.

EDIT: This response concerns core Bokeh. It’s possible Panel wraps some of these concerns in their own abstractions, but those questions would be better to ask them about.