3
3
# Distributed under the terms of the Modified BSD License.
4
4
import asyncio
5
5
import os
6
+ import functools
6
7
import re
7
8
import signal
8
9
import sys
@@ -51,6 +52,32 @@ class _ShutdownStatus(Enum):
51
52
SigkillRequest = "SigkillRequest"
52
53
53
54
55
+ def in_pending_state (method ):
56
+ """Sets the kernel to a pending state by
57
+ creating a fresh Future for the KernelManager's `ready`
58
+ attribute. Once the method is finished, set the Future's results.
59
+ """
60
+ @functools .wraps (method )
61
+ async def wrapper (self , * args , ** kwargs ):
62
+ # Create a future for the decorated method
63
+ try :
64
+ self ._ready = Future ()
65
+ except RuntimeError :
66
+ # No event loop running, use concurrent future
67
+ self ._ready = CFuture ()
68
+ try :
69
+ # call wrapped method, await, and set the result or exception.
70
+ out = await method (self , * args , ** kwargs )
71
+ # Add a small sleep to ensure tests can capture the state before done
72
+ await asyncio .sleep (0.01 )
73
+ self ._ready .set_result (None )
74
+ return out
75
+ except Exception as e :
76
+ self ._ready .set_exception (e )
77
+ self .log .exception (self ._ready .exception ())
78
+ return wrapper
79
+
80
+
54
81
class KernelManager (ConnectionFileMixin ):
55
82
"""Manages a single kernel in a subprocess on this host.
56
83
@@ -329,6 +356,7 @@ async def _async_post_start_kernel(self, **kw) -> None:
329
356
330
357
post_start_kernel = run_sync (_async_post_start_kernel )
331
358
359
+ @in_pending_state
332
360
async def _async_start_kernel (self , ** kw ):
333
361
"""Starts a kernel on this host in a separate process.
334
362
@@ -341,25 +369,12 @@ async def _async_start_kernel(self, **kw):
341
369
keyword arguments that are passed down to build the kernel_cmd
342
370
and launching the kernel (e.g. Popen kwargs).
343
371
"""
344
- done = self ._ready .done ()
345
-
346
- try :
347
- kernel_cmd , kw = await ensure_async (self .pre_start_kernel (** kw ))
372
+ kernel_cmd , kw = await ensure_async (self .pre_start_kernel (** kw ))
348
373
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
- # Add a small sleep to ensure tests can capture the state before done
355
- await asyncio .sleep (0.01 )
356
- self ._ready .set_result (None )
357
-
358
- except Exception as e :
359
- if not done :
360
- self ._ready .set_exception (e )
361
- self .log .exception (self ._ready .exception ())
362
- raise e
374
+ # launch the kernel subprocess
375
+ self .log .debug ("Starting kernel: %s" , kernel_cmd )
376
+ await ensure_async (self ._launch_kernel (kernel_cmd , ** kw ))
377
+ await ensure_async (self .post_start_kernel (** kw ))
363
378
364
379
start_kernel = run_sync (_async_start_kernel )
365
380
@@ -434,6 +449,7 @@ async def _async_cleanup_resources(self, restart: bool = False) -> None:
434
449
435
450
cleanup_resources = run_sync (_async_cleanup_resources )
436
451
452
+ @in_pending_state
437
453
async def _async_shutdown_kernel (self , now : bool = False , restart : bool = False ):
438
454
"""Attempts to stop the kernel process cleanly.
439
455
@@ -452,10 +468,6 @@ async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False)
452
468
Will this kernel be restarted after it is shutdown. When this
453
469
is True, connection files will not be cleaned up.
454
470
"""
455
- # Shutdown is a no-op for a kernel that had a failed startup
456
- if self ._ready .exception ():
457
- return
458
-
459
471
self .shutting_down = True # Used by restarter to prevent race condition
460
472
# Stop monitoring for restarting while we shutdown.
461
473
self .stop_restarter ()
@@ -473,6 +485,7 @@ async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False)
473
485
474
486
await ensure_async (self .cleanup_resources (restart = restart ))
475
487
488
+
476
489
shutdown_kernel = run_sync (_async_shutdown_kernel )
477
490
478
491
async def _async_restart_kernel (self , now : bool = False , newports : bool = False , ** kw ) -> None :
@@ -503,9 +516,6 @@ async def _async_restart_kernel(self, now: bool = False, newports: bool = False,
503
516
if self ._launch_args is None :
504
517
raise RuntimeError ("Cannot restart the kernel. " "No previous call to 'start_kernel'." )
505
518
506
- if not self ._ready .done ():
507
- raise RuntimeError ("Cannot restart the kernel. " "Kernel has not fully started." )
508
-
509
519
# Stop currently running kernel.
510
520
await ensure_async (self .shutdown_kernel (now = now , restart = True ))
511
521
0 commit comments