Repository: dask/distributed

Issue:
RuntimeError: Cluster failed to start: No module named 'bokeh'
<!-- Please include a self-contained copy-pastable example that generates the issue if possible.

Please be concise with code posted. See guidelines below on how to provide a good bug report:

- Craft Minimal Bug Reports http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports
- Minimal Complete Verifiable Examples https://stackoverflow.com/help/mcve

Bug reports that follow these guidelines are easier to diagnose, and so are often handled much more quickly.
-->

**Describe the issue**:

I was attempting to re-run a previously passing Github Actions workflow ([first passing attempt](https://github.com/TDAmeritrade/stumpy/actions/runs/3252262194/attempts/2)) as a sanity check and was surprised to find the following error ([second failing attempt](https://github.com/TDAmeritrade/stumpy/actions/runs/3252262194/jobs/5553539623)):

```
self = LocalCluster(a0a6f468, '<Not Connected>', workers=0, threads=0, memory=0 B)

    async def _start(self):
        while self.status == Status.starting:
            await asyncio.sleep(0.01)
        if self.status == Status.running:
            return
        if self.status == Status.closed:
            raise ValueError("Cluster is closed")
    
        self._lock = asyncio.Lock()
        self.status = Status.starting
    
        if self.scheduler_spec is None:
            try:
                import distributed.dashboard  # noqa: F401
            except ImportError:
                pass
            else:
                options = {"dashboard": True}
            self.scheduler_spec = {"cls": Scheduler, "options": options}
    
        try:
            # Check if scheduler has already been created by a subclass
            if self.scheduler is None:
                cls = self.scheduler_spec["cls"]
                if isinstance(cls, str):
                    cls = import_term(cls)
                self.scheduler = cls(**self.scheduler_spec.get("options", {}))
                self.scheduler = await self.scheduler
            self.scheduler_comm = rpc(
                getattr(self.scheduler, "external_address", None)
                or self.scheduler.address,
                connection_args=self.security.get_connection_args("client"),
            )
            await super()._start()
        except Exception as e:  # pragma: no cover
            self.status = Status.failed
            await self._close()
>           raise RuntimeError(f"Cluster failed to start: {e}") from e
E           RuntimeError: Cluster failed to start: No module named 'bokeh'

/opt/hostedtoolcache/Python/3.10.8/x64/lib/python3.10/site-packages/distributed/deploy/spec.py:319: RuntimeError
---------------------------- Captured stderr setup -----------------------------
2022-10-29 14:43:07,628 - distributed.deploy.spec - WARNING - Cluster closed without starting up
=========================== short test summary info ============================
ERROR tests/test_stumped.py::test_stumped_int_input - RuntimeError: Cluster failed to start: No module named 'bokeh'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 1.73s ===============================
Error: pytest encountered exit code 1
Error: Process completed with exit code 1.
```

Specifically, ` RuntimeError: Cluster failed to start: No module named 'bokeh'`. A quick Google search revealed that [other folks](https://github.com/modin-project/modin/issues/5166) are also running into this issue. It's not entirely clear if this is a `distributed` issue or a problem with Github Actions. However, I did notice that the passing attempts were performed on `distributed` version `distributed-2022.9.2-py3-none-any.whl` whereas the failed attempt was `distributed-2022.10.1-py3-none-any.whl`.




Discussion:
Limiting the dask+distributed versions to be `<= 2022.9.2` appears to resolve this error

Resolving patch:
diff --git a/distributed/dashboard/core.py b/distributed/dashboard/core.py
--- a/distributed/dashboard/core.py
+++ b/distributed/dashboard/core.py
@@ -12,15 +12,14 @@
 import dask
 
 from distributed.dashboard.utils import BOKEH_VERSION
+from distributed.versions import MIN_BOKEH_VERSION
 
-_min_bokeh_version = "2.1.1"
-
-if BOKEH_VERSION < parse_version(_min_bokeh_version):
+if BOKEH_VERSION < parse_version(MIN_BOKEH_VERSION):
     warnings.warn(
-        f"\nDask needs bokeh >= {_min_bokeh_version}, < 3 for the dashboard."
+        f"\nDask needs bokeh >= {MIN_BOKEH_VERSION}, < 3 for the dashboard."
         "\nContinuing without the dashboard."
     )
-    raise ImportError(f"Dask needs bokeh >= {_min_bokeh_version}, < 3")
+    raise ImportError(f"Dask needs bokeh >= {MIN_BOKEH_VERSION}, < 3")
 
 
 def BokehApplication(applications, server, prefix="/", template_variables=None):
diff --git a/distributed/http/scheduler/missing_bokeh.py b/distributed/http/scheduler/missing_bokeh.py
--- a/distributed/http/scheduler/missing_bokeh.py
+++ b/distributed/http/scheduler/missing_bokeh.py
@@ -1,17 +1,17 @@
 from __future__ import annotations
 
-from distributed.dashboard.core import _min_bokeh_version
 from distributed.http.utils import RequestHandler, redirect
 from distributed.utils import log_errors
+from distributed.versions import MIN_BOKEH_VERSION
 
 
 class MissingBokeh(RequestHandler):
     @log_errors
     def get(self):
         self.write(
-            f"<p>Dask needs bokeh >= {_min_bokeh_version}, < 3 for the dashboard.</p>"
-            f"<p>Install with conda: conda install bokeh>={_min_bokeh_version},<3</p>"
-            f"<p>Install with pip: pip install bokeh>={_min_bokeh_version},<3</p>"
+            f"<p>Dask needs bokeh >= {MIN_BOKEH_VERSION}, < 3 for the dashboard.</p>"
+            f"<p>Install with conda: conda install bokeh>={MIN_BOKEH_VERSION},<3</p>"
+            f"<p>Install with pip: pip install bokeh>={MIN_BOKEH_VERSION},<3</p>"
         )
 
 
diff --git a/distributed/versions.py b/distributed/versions.py
--- a/distributed/versions.py
+++ b/distributed/versions.py
@@ -12,6 +12,8 @@
 from types import ModuleType
 from typing import Any
 
+MIN_BOKEH_VERSION = "2.1.1"
+
 required_packages = [
     ("dask", lambda p: p.__version__),
     ("distributed", lambda p: p.__version__),


Test patch: