Repository: dask/distributed

Issue:
RuntimeError: maximum recursion depth exceeded is raised when trying to view a function in the Profile web UI
I run the following code inside a jupyter notebook:

```
from dask.distributed import Client, progress
client = Client(n_workers=2, threads_per_worker=2, memory_limit='1GB')
client

import dask.dataframe as dd
df = dd.demo.make_timeseries('2000-01-01', '2000-12-31', freq='10s', partition_freq='1M',
                             dtypes={'name': str, 'id': int, 'x': float, 'y': float})
df

df.head(3)
```

Trying to access a method from the profile secion in the web UI I get the following error:
 ```
 if not (value is None or any(param.is_valid(value) for param in self.type_params)):
  File "/home/avlach/virtualenvs/dask/local/lib/python2.7/site-packages/bokeh/core/property/bases.py", line 256, in is_valid
    self.validate(value)
  File "/home/avlach/virtualenvs/dask/local/lib/python2.7/site-packages/bokeh/core/properties.py", line 1305, in validate
    raise ValueError("expected an element of %s, got %r" % (self, value))
  File "/home/avlach/virtualenvs/dask/local/lib/python2.7/site-packages/bokeh/core/properties.py", line 1285, in __str__
    return "%s(%s, %s)" % (self.__class__.__name__, self.keys_type, self.values_type)
  File "/home/avlach/virtualenvs/dask/local/lib/python2.7/site-packages/bokeh/core/properties.py", line 669, in __str__
    return "%s(%s)" % (self.__class__.__name__, ", ".join(map(str, self.type_params)))
  File "/home/avlach/virtualenvs/dask/local/lib/python2.7/site-packages/bokeh/core/properties.py", line 435, in __str__
    return "%s(%s)" % (self.__class__.__name__, self.instance_type.__name__)
RuntimeError: maximum recursion depth exceeded
```

The exception trace is quite large. If you like I can print it all in a file and attach it. The `ValueError` exception is raised some times before `RuntimeError` is raised.
dask version: 0.17.5
dask.distributed version: 1.21.6
bokeh: version:  0.12.16


Discussion:
I can reproduce this.  Thank you for the minimal example @avlahop .  This is very helpful.
cc @martindurant I'm not sure what kind of time you have, but diving into this might also be interesting
I'll let you know if I get around to looking into this.
FYI @martindurant I'm handling this

Resolving patch:
diff --git a/distributed/bokeh/components.py b/distributed/bokeh/components.py
--- a/distributed/bokeh/components.py
+++ b/distributed/bokeh/components.py
@@ -373,7 +373,11 @@ def __init__(self, server, doc=None, **kwargs):
         self.states = data.pop('states')
         self.source = ColumnDataSource(data=data)
 
+        changing = [False]  # avoid repeated changes from within callback
+
         def cb(attr, old, new):
+            if changing[0]:
+                return
             with log_errors():
                 try:
                     ind = new['1d']['indices'][0]
@@ -382,14 +386,18 @@ def cb(attr, old, new):
                 data = profile.plot_data(self.states[ind], profile_interval)
                 del self.states[:]
                 self.states.extend(data.pop('states'))
+                changing[0] = True  # don't recursively trigger callback
                 self.source.data.update(data)
                 self.source.selected = old
+                changing[0] = False
 
         self.source.on_change('selected', cb)
 
         self.profile_plot = figure(tools='tap', height=400, **kwargs)
-        self.profile_plot.quad('left', 'right', 'top', 'bottom', color='color',
-                               line_color='black', source=self.source)
+        r = self.profile_plot.quad('left', 'right', 'top', 'bottom', color='color',
+                                   line_color='black', source=self.source)
+        r.selection_glyph = None
+        r.nonselection_glyph = None
 
         hover = HoverTool(
             point_policy="follow_mouse",


Test patch: