@@ -82,6 +82,12 @@ class UnknownStatus(LauncherError):
82
82
pass
83
83
84
84
85
+ class NotRunning (LauncherError ):
86
+ """Raised when a launcher is no longer running"""
87
+
88
+ pass
89
+
90
+
85
91
class BaseLauncher (LoggingConfigurable ):
86
92
"""An abstraction for starting, stopping and signaling a process."""
87
93
@@ -408,7 +414,10 @@ def from_dict(cls, d, **kwargs):
408
414
def _reconstruct_process (self , d ):
409
415
"""Reconstruct our process"""
410
416
if 'pid' in d and d ['pid' ] > 0 :
411
- self .process = psutil .Process (d ['pid' ])
417
+ try :
418
+ self .process = psutil .Process (d ['pid' ])
419
+ except psutil .NoSuchProcess as e :
420
+ raise NotRunning (f"Process { d ['pid' ]} " )
412
421
self ._start_waiting ()
413
422
414
423
def _wait (self ):
@@ -465,10 +474,18 @@ def start(self):
465
474
async def join (self , timeout = None ):
466
475
"""Wait for the process to exit"""
467
476
with ThreadPoolExecutor (1 ) as pool :
477
+ wait = partial (self .process .wait , timeout )
468
478
try :
469
- await asyncio .wrap_future (
470
- pool .submit (partial (self .process .wait , timeout ))
471
- )
479
+ try :
480
+ future = pool .submit (wait )
481
+ except RuntimeError :
482
+ # e.g. called during process shutdown,
483
+ # which raises
484
+ # RuntimeError: cannot schedule new futures after interpreter shutdown
485
+ # Instead, do the blocking call
486
+ wait ()
487
+ else :
488
+ await asyncio .wrap_future (future )
472
489
except psutil .TimeoutExpired :
473
490
raise TimeoutError (
474
491
f"Process { self .pid } did not complete in { timeout } seconds."
@@ -638,8 +655,20 @@ def to_dict(self):
638
655
@classmethod
639
656
def from_dict (cls , d , ** kwargs ):
640
657
self = super ().from_dict (d , ** kwargs )
658
+ n = 0
641
659
for i , engine_dict in d ['engines' ].items ():
642
- self .launchers [i ] = self .launcher_class .from_dict (engine_dict , parent = self )
660
+ try :
661
+ self .launchers [i ] = self .launcher_class .from_dict (
662
+ engine_dict , parent = self
663
+ )
664
+ except NotRunning as e :
665
+ self .log .error (f"Engine { i } not running: { e } " )
666
+ else :
667
+ n += 1
668
+ if n == 0 :
669
+ raise NotRunning ("No engines left" )
670
+ else :
671
+ self .n = n
643
672
return self
644
673
645
674
def start (self , n ):
@@ -1184,9 +1213,17 @@ def wait_one(self, timeout):
1184
1213
1185
1214
async def join (self , timeout = None ):
1186
1215
with ThreadPoolExecutor (1 ) as pool :
1187
- await asyncio .wrap_future (
1188
- pool .submit (partial (self .wait_one , timeout = timeout ))
1189
- )
1216
+ wait = partial (self .wait_one , timeout = timeout )
1217
+ try :
1218
+ future = pool .submit (wait )
1219
+ except RuntimeError :
1220
+ # e.g. called during process shutdown,
1221
+ # which raises
1222
+ # RuntimeError: cannot schedule new futures after interpreter shutdown
1223
+ # Instead, do the blocking call
1224
+ wait ()
1225
+ else :
1226
+ await asyncio .wrap_future (future )
1190
1227
1191
1228
def signal (self , sig ):
1192
1229
if self .state == 'running' :
0 commit comments