Repository: holoviz/holoviews

Issue:
Using Panel notifications in callbacks triggered by hv.streams causes crash
Panel 0.14.0, HoloViews 1.15.1 on Linux (tried RHEL7 and Ubuntu 18.04)

The behavior is very similar to what was observed with notifications + threading (#3819). The subscriber is called, but crashes at the first notifications-related instruction.

#### Minimal example

```python
import panel as pn
import holoviews as hv

pn.extension(notifications=True)
pn.state.notifications.position = 'center-center'


def callback(index):
    pn.state.notifications.info(f'Point {index} clicked')


plot = hv.Scatter([1, 2, 3, 4]).opts(size=8, tools=['tap'])
hv.streams.Selection1D(source=plot).add_subscriber(callback)

pn.template.FastListTemplate(main=[content], sidebar=[]).servable()
```

#### Stack traceback
```
  File "/home/mathurin/miniconda3/envs/dataviz_v4/lib/python3.10/site-packages/holoviews/plotting/bokeh/callbacks.py", line 344, in process_on_change
    self.on_msg(msg)
  File "/home/mathurin/miniconda3/envs/dataviz_v4/lib/python3.10/site-packages/holoviews/plotting/bokeh/callbacks.py", line 186, in on_msg
    raise e
  File "/home/mathurin/miniconda3/envs/dataviz_v4/lib/python3.10/site-packages/holoviews/plotting/bokeh/callbacks.py", line 178, in on_msg
    Stream.trigger(streams)
  File "/home/mathurin/miniconda3/envs/dataviz_v4/lib/python3.10/site-packages/holoviews/streams.py", line 184, in trigger
    subscriber(**dict(union))
  File "/home/mathurin/PycharmProjects/volcplume/toast.py", line 10, in callback
    pn.state.notifications.info(f'Point {index} clicked')
AttributeError: 'NoneType' object has no attribute 'info'
```


Discussion:
Probably going to have to move this to HoloViews. The `state.curdoc` is probably not being managed correctly by HoloViews so session state lookups fail.
Hi @philippjfr 

Do you know if solving this entails only limited changes? If so I'm willing to look into it but don't know where to start.
Yes, should be relatively simple. The problem is that process_on_change is being schedule as a async task but in an async context it is possible for `pn.state.curdoc` not to correctly reflect the current context. What I would suggest is that in `holoviews.plotting.bokeh.callbacks` inside the `on_msg` method we make the following change:

```python
from panel.io.state import set_curdoc
...
def on_msg(self, msg):
        ...
        try:
            with set_curdoc(self.plot.document):
                Stream.trigger(streams)
        except CallbackError as e:
            if self.plot.root and self.plot.root.ref['id'] in state._handles:
                handle, _ = state._handles[self.plot.root.ref['id']]
                handle.update({'text/html': str(e)}, raw=True)
            else:
                raise e
```
Thanks! It turns out `set_curdoc` cannot be used as a context manager, but the following works:

```python
set_curdoc(self.plot.document)
Stream.trigger(streams)
```

If it's just this I can make a PR right away.
It definitely should be possible to use it as `set_curdoc` as a context manager. Are you using the latest version of Panel?

https://github.com/holoviz/panel/blob/main/panel/reactive.py/#L415-L416
My mistake I was using Bokeh's `set_curdoc`. Everythin fine with `panel.io.set_curdoc`.

Resolving patch:
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -16,7 +16,7 @@
     PolyEditTool,
     Range1d,
 )
-from panel.io.state import state
+from panel.io.state import set_curdoc, state
 
 from ...core.options import CallbackError
 from ...core.util import datetime_types, dimension_sanitizer, dt64_to_dt, isequal
@@ -209,7 +209,8 @@ def on_msg(self, msg):
             streams.append(stream)
 
         try:
-            Stream.trigger(streams)
+            with set_curdoc(self.plot.document):
+                Stream.trigger(streams)
         except CallbackError as e:
             if self.plot.root and self.plot.root.ref['id'] in state._handles:
                 handle, _ = state._handles[self.plot.root.ref['id']]


Test patch: