|
8 | 8 | import sys
|
9 | 9 | import typing as t
|
10 | 10 | import uuid
|
| 11 | +from asyncio.futures import Future |
| 12 | +from concurrent.futures import Future as CFuture |
11 | 13 | from contextlib import contextmanager
|
12 | 14 | from enum import Enum
|
13 | 15 |
|
@@ -58,6 +60,11 @@ class KernelManager(ConnectionFileMixin):
|
58 | 60 | def __init__(self, *args, **kwargs):
|
59 | 61 | super().__init__(**kwargs)
|
60 | 62 | self._shutdown_status = _ShutdownStatus.Unset
|
| 63 | + try: |
| 64 | + self._ready = Future() |
| 65 | + except RuntimeError: |
| 66 | + # No event loop running, use concurrent future |
| 67 | + self._ready = CFuture() |
61 | 68 |
|
62 | 69 | _created_context: Bool = Bool(False)
|
63 | 70 |
|
@@ -139,6 +146,11 @@ def kernel_spec(self) -> t.Optional[kernelspec.KernelSpec]:
|
139 | 146 | def _default_cache_ports(self) -> bool:
|
140 | 147 | return self.transport == "tcp"
|
141 | 148 |
|
| 149 | + @property |
| 150 | + def ready(self) -> Future: |
| 151 | + """A future that resolves when the kernel process has started for the first time""" |
| 152 | + return self._ready |
| 153 | + |
142 | 154 | @property
|
143 | 155 | def ipykernel(self) -> bool:
|
144 | 156 | return self.kernel_name in {"python", "python2", "python3"}
|
@@ -329,12 +341,22 @@ async def _async_start_kernel(self, **kw):
|
329 | 341 | keyword arguments that are passed down to build the kernel_cmd
|
330 | 342 | and launching the kernel (e.g. Popen kwargs).
|
331 | 343 | """
|
332 |
| - kernel_cmd, kw = await ensure_async(self.pre_start_kernel(**kw)) |
| 344 | + done = self._ready.done() |
| 345 | + |
| 346 | + try: |
| 347 | + kernel_cmd, kw = await ensure_async(self.pre_start_kernel(**kw)) |
| 348 | + |
| 349 | + # launch the kernel subprocess |
| 350 | + self.log.debug("Starting kernel: %s", kernel_cmd) |
| 351 | + await ensure_async(self._launch_kernel(kernel_cmd, **kw)) |
| 352 | + await ensure_async(self.post_start_kernel(**kw)) |
| 353 | + if not done: |
| 354 | + self._ready.set_result(None) |
333 | 355 |
|
334 |
| - # launch the kernel subprocess |
335 |
| - self.log.debug("Starting kernel: %s", kernel_cmd) |
336 |
| - await ensure_async(self._launch_kernel(kernel_cmd, **kw)) |
337 |
| - await ensure_async(self.post_start_kernel(**kw)) |
| 356 | + except Exception as e: |
| 357 | + if not done: |
| 358 | + self._ready.set_exception(e) |
| 359 | + raise e |
338 | 360 |
|
339 | 361 | start_kernel = run_sync(_async_start_kernel)
|
340 | 362 |
|
@@ -471,8 +493,8 @@ async def _async_restart_kernel(self, now: bool = False, newports: bool = False,
|
471 | 493 | Any options specified here will overwrite those used to launch the
|
472 | 494 | kernel.
|
473 | 495 | """
|
474 |
| - if self._launch_args is None: |
475 |
| - raise RuntimeError("Cannot restart the kernel. " "No previous call to 'start_kernel'.") |
| 496 | + if not self._ready.done(): |
| 497 | + raise RuntimeError("Cannot restart the kernel. " "Kernel has been not fully started.") |
476 | 498 | else:
|
477 | 499 | # Stop currently running kernel.
|
478 | 500 | await ensure_async(self.shutdown_kernel(now=now, restart=True))
|
|
0 commit comments