Repository: holoviz/holoviews

Issue:
rasterize leads to Bokeh "inner_height doesn't have a value set" error
#### Description of expected behavior and the observed behavior

Using `rasterize` leads to the following being printed to the output:
```
bokeh.core.property.descriptors.UnsetValueError: figure(id='30ff47ca-b677-4099-9127-3ba111a7364a', ...).inner_height doesn't have a value set
```

For some reason the result is non-deterministic, I can not always replicate this (only exactly the same setting). Testing this on VSCode notebooks.

#### Complete, minimal, self-contained example code that reproduces the issue

```python
import numpy as np

import xarray as xr

import matplotlib.pyplot as plt
import bokeh.models
import holoviews as hv
from holoviews.operation.datashader import rasterize
import panel as pn

hv.extension('bokeh')
pn.extension(comms='vscode')


x_size = 100
y_size = 100
data = xr.DataArray(
    data=np.random.rand(x_size, y_size),
    name='z',
    dims=['x', 'y'],
    coords={
        'x': np.sort(np.random.rand(x_size)),
        'y': np.sort(np.random.rand(y_size)),
    },
)

lcms_fitted_mesh = hv.QuadMesh(data, kdims=['x', 'y'], vdims=['z'])
rasterize(lcms_fitted_mesh)
```

#### Stack traceback and/or browser JavaScript console output
```
Task exception was never retrieved
future: <Task finished name='Task-15' coro=<Callback.process_on_change() done, defined at [/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/callbacks.py:322](https://vscode-remote+ssh-002dremote-002bspicy.vscode-resource.vscode-cdn.net/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/callbacks.py:322)> exception=UnsetValueError("figure(id='30ff47ca-b677-4099-9127-3ba111a7364a', ...).inner_height doesn't have a value set")>
Traceback (most recent call last):
  File "[/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/callbacks.py](https://vscode-remote+ssh-002dremote-002bspicy.vscode-resource.vscode-cdn.net/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/callbacks.py)", line 340, in process_on_change
    msg[attr] = self.resolve_attr_spec(path, cb_obj)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/callbacks.py](https://vscode-remote+ssh-002dremote-002bspicy.vscode-resource.vscode-cdn.net/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/holoviews/plotting/bokeh/callbacks.py)", line 248, in resolve_attr_spec
    resolved = getattr(resolved, p, None)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "[/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/bokeh/core/property/descriptors.py](https://vscode-remote+ssh-002dremote-002bspicy.vscode-resource.vscode-cdn.net/home/peterroelants/mambaforge/envs/lcms_polymer_env/lib/python3.11/site-packages/bokeh/core/property/descriptors.py)", line 283, in __get__
    raise UnsetValueError(f"{obj}.{self.name} doesn't have a value set")
bokeh.core.property.descriptors.UnsetValueError: figure(id='30ff47ca-b677-4099-9127-3ba111a7364a', ...).inner_height doesn't have a value set
```


#### ALL software version info
```
Visual Studio Code Version 1.78.2

Python implementation: CPython
Python version       : 3.11.3
IPython version      : 8.14.0

holoviews : 1.16.1
numpy     : 1.24.3
xarray    : 2023.5.0
panel     : 1.1.0
matplotlib: 3.7.1
bokeh     : 3.1.1
```




Discussion:
I experience the same error. 
@mattpap, any idea what is causing this? 
`inner_height` is a readonly property set by bokehjs and returned back to bokeh. Seeing this code:

> `getattr(resolved, p, None)`

If I understand this code correctly, then this is expected to fail. Since the introduction of unset values, getting bokeh's properties like this will result in `UnsetValueError`, if the value is unset. `None` is a perfectly valid value in bokeh's type system (see `Nullable`), thus it shouldn't be expected to be viable missing value indicator. `try-expect UnsetValueError` should resolve this, at least on the surface. However, if someone could explain the intention behind this code, then I can point to the right API to deal with this.
The behavior only happens in VSCode, which uses `jupyter_bokeh`. 

As far as I can see, the example works even with the error. 
> As far as I can see, the example works even with the error.

That's expected. `inner_height` and similar properties are for informational purpose and have no real bearing on anything. If the failure doesn't interrupt anything important, then a bokeh process will continue fine, just with the relevant value missing.
> The behavior only happens in VSCode,

It may be a timing issue. If those properties are read before bokehjs has a chance to update them, then `UnsetValueError` will occur. If things happen in holoviews (or whichever layer of integration) before bokehjs signals the ready state, then errors like this one should be expected.

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
@@ -15,7 +15,7 @@
 
 from ...core.options import CallbackError
 from ...core.util import (
-    datetime_types, dimension_sanitizer, dt64_to_dt
+    datetime_types, dimension_sanitizer, dt64_to_dt, isequal
 )
 from ...element import Table
 from ...streams import (
@@ -234,7 +234,7 @@ def resolve_attr_spec(cls, spec, cb_obj, model=None):
         be the same as the model.
         """
         if not cb_obj:
-            raise Exception(f'Bokeh plot attribute {spec} could not be found')
+            raise AttributeError(f'Bokeh plot attribute {spec} could not be found')
         if model is None:
             model = cb_obj
         spec = spec.split('.')
@@ -337,15 +337,18 @@ async def process_on_change(self):
             else:
                 obj_handle = attr_path[0]
             cb_obj = self.plot_handles.get(obj_handle)
-            msg[attr] = self.resolve_attr_spec(path, cb_obj)
+            try:
+                msg[attr] = self.resolve_attr_spec(path, cb_obj)
+            except Exception:
+                # To give BokehJS a chance to update the model
+                # https://github.com/holoviz/holoviews/issues/5746
+                await asyncio.sleep(0.05)
+                msg[attr] = self.resolve_attr_spec(path, cb_obj)
 
         if self.skip_change(msg):
             equal = True
         else:
-            try:
-                equal = msg == self._prev_msg
-            except Exception:
-                equal = False
+            equal = isequal(msg, self._prev_msg)
 
         if not equal or any(s.transient for s in self.streams):
             self.on_msg(msg)


Test patch: