5
5
starts/stops/polls controllers, engines, etc.
6
6
"""
7
7
import asyncio
8
+ import atexit
8
9
import inspect
9
10
import logging
10
11
import os
15
16
import time
16
17
from functools import partial
17
18
from multiprocessing import cpu_count
19
+ from weakref import WeakSet
18
20
19
21
import IPython
20
22
import traitlets .log
21
23
from IPython .core .profiledir import ProfileDir
22
24
from IPython .core .profiledir import ProfileDirError
23
25
from traitlets import Any
26
+ from traitlets import Bool
24
27
from traitlets import default
25
28
from traitlets import Dict
26
29
from traitlets import Float
35
38
36
39
_suffix_chars = string .ascii_lowercase + string .digits
37
40
41
+ # weak set of clusters to be cleaned up at exit
42
+ _atexit_clusters = WeakSet ()
43
+
44
+
45
+ def _atexit_cleanup_clusters (* args ):
46
+ """Cleanup clusters during process shutdown"""
47
+ for cluster in _atexit_clusters :
48
+ if not cluster .shutdown_atexit :
49
+ # overridden after register
50
+ continue
51
+ if cluster ._controller or cluster ._engine_sets :
52
+ print (f"Stopping cluster { cluster } " , file = sys .stderr )
53
+ cluster .stop_cluster_sync ()
54
+
55
+
56
+ _atexit_cleanup_clusters .registered = False
57
+
38
58
39
59
class Cluster (AsyncFirst , LoggingConfigurable ):
40
60
"""Class representing an IPP cluster
@@ -48,6 +68,17 @@ class Cluster(AsyncFirst, LoggingConfigurable):
48
68
"""
49
69
50
70
# general configuration
71
+
72
+ shutdown_atexit = Bool (
73
+ True ,
74
+ help = """
75
+ Shutdown the cluster at process exit.
76
+
77
+ Set to False if you want to launch a cluster and leave it running
78
+ after the launching process exits.
79
+ """ ,
80
+ )
81
+
51
82
cluster_id = Unicode (help = "The id of the cluster (default: random string)" )
52
83
53
84
@default ("cluster_id" )
@@ -224,6 +255,12 @@ def _default_log(self):
224
255
_controller = Any ()
225
256
_engine_sets = Dict ()
226
257
258
+ def __del__ (self ):
259
+ if not self .shutdown_atexit :
260
+ return
261
+ if self ._controller or self ._engine_sets :
262
+ self .stop_cluster_sync ()
263
+
227
264
def __repr__ (self ):
228
265
229
266
fields = {
@@ -272,6 +309,11 @@ async def start_controller(self, **kwargs):
272
309
"controller is already running. Call stop_controller() first."
273
310
)
274
311
312
+ if self .shutdown_atexit :
313
+ _atexit_clusters .add (self )
314
+ if not _atexit_cleanup_clusters .registered :
315
+ atexit .register (_atexit_cleanup_clusters )
316
+
275
317
self ._controller = controller = self .controller_launcher_class (
276
318
work_dir = u'.' ,
277
319
parent = self ,
0 commit comments