@@ -39,13 +39,34 @@ def _api_process(
39
39
started_event : multiprocessing .Event ,
40
40
) -> None :
41
41
42
+ import signal
43
+
42
44
qh = logging .handlers .QueueHandler (logging_queue )
43
45
logger = logging .getLogger ()
44
46
logger .handlers .clear ()
45
47
logger .addHandler (qh )
46
48
49
+ early_stop = False
50
+
51
+ def signal_handler (signum , frame ):
52
+ nonlocal early_stop
53
+ early_stop = True
54
+
55
+ signal .signal (signal .SIGINT , signal_handler )
56
+ signal .signal (signal .SIGTERM , signal_handler )
57
+
47
58
api = FractalGunicornApp (qcf_config , finished_queue , started_event )
48
- api .run ()
59
+
60
+ if early_stop :
61
+ logging_queue .close ()
62
+ logging_queue .join_thread ()
63
+ return
64
+
65
+ try :
66
+ api .run ()
67
+ finally :
68
+ logging_queue .close ()
69
+ logging_queue .join_thread ()
49
70
50
71
51
72
def _compute_process (compute_config : FractalComputeConfig , logging_queue : multiprocessing .Queue ) -> None :
@@ -57,15 +78,32 @@ def _compute_process(compute_config: FractalComputeConfig, logging_queue: multip
57
78
logger .handlers .clear ()
58
79
logger .addHandler (qh )
59
80
81
+ early_stop = False
82
+
83
+ def signal_handler (signum , frame ):
84
+ nonlocal early_stop
85
+ early_stop = True
86
+
87
+ signal .signal (signal .SIGINT , signal_handler )
88
+ signal .signal (signal .SIGTERM , signal_handler )
89
+
60
90
compute = ComputeManager (compute_config )
91
+ if early_stop :
92
+ logging_queue .close ()
93
+ logging_queue .join_thread ()
94
+ return
61
95
62
96
def signal_handler (signum , frame ):
63
97
compute .stop ()
64
98
65
99
signal .signal (signal .SIGINT , signal_handler )
66
100
signal .signal (signal .SIGTERM , signal_handler )
67
101
68
- compute .start ()
102
+ try :
103
+ compute .start ()
104
+ finally :
105
+ logging_queue .close ()
106
+ logging_queue .join_thread ()
69
107
70
108
71
109
def _job_runner_process (
@@ -79,25 +117,46 @@ def _job_runner_process(
79
117
logger .handlers .clear ()
80
118
logger .addHandler (qh )
81
119
120
+ early_stop = False
121
+
122
+ def signal_handler (signum , frame ):
123
+ nonlocal early_stop
124
+ early_stop = True
125
+
126
+ signal .signal (signal .SIGINT , signal_handler )
127
+ signal .signal (signal .SIGTERM , signal_handler )
128
+
82
129
job_runner = FractalJobRunner (qcf_config , finished_queue )
83
130
131
+ if early_stop :
132
+ logging_queue .close ()
133
+ logging_queue .join_thread ()
134
+ return
135
+
84
136
def signal_handler (signum , frame ):
85
137
job_runner .stop ()
86
138
87
139
signal .signal (signal .SIGINT , signal_handler )
88
140
signal .signal (signal .SIGTERM , signal_handler )
89
141
90
- job_runner .start ()
142
+ try :
143
+ job_runner .start ()
144
+ finally :
145
+ logging_queue .close ()
146
+ logging_queue .join_thread ()
91
147
92
148
93
- def _logging_thread (logging_queue ):
149
+ def _logging_thread (logging_queue , logging_thread_stop ):
94
150
while True :
95
- record = logging_queue .get ()
96
- if record is None :
97
- break
98
- logger = logging .getLogger (record .name )
99
-
100
- logger .handle (record )
151
+ try :
152
+ record = logging_queue .get (timeout = 0.5 )
153
+ logger = logging .getLogger (record .name )
154
+ logger .handle (record )
155
+ except Empty :
156
+ if logging_thread_stop .is_set ():
157
+ break
158
+ else :
159
+ continue
101
160
102
161
103
162
class FractalSnowflake :
@@ -128,7 +187,10 @@ def __init__(
128
187
# See https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes
129
188
130
189
self ._logging_queue = self ._mp_context .Queue ()
131
- self ._logging_thread = threading .Thread (target = _logging_thread , args = (self ._logging_queue ,), daemon = True )
190
+ self ._logging_thread_stop = threading .Event ()
191
+ self ._logging_thread = threading .Thread (
192
+ target = _logging_thread , args = (self ._logging_queue , self ._logging_thread_stop ), daemon = True
193
+ )
132
194
self ._logging_thread .start ()
133
195
134
196
# Create a temporary directory for everything
@@ -235,8 +297,9 @@ def _update_finalizer(self):
235
297
self ._compute_proc ,
236
298
self ._api_proc ,
237
299
self ._job_runner_proc ,
238
- self ._logging_thread ,
239
300
self ._logging_queue ,
301
+ self ._logging_thread ,
302
+ self ._logging_thread_stop ,
240
303
)
241
304
242
305
def _start_api (self ):
@@ -292,7 +355,7 @@ def _stop_job_runner(self):
292
355
self ._update_finalizer ()
293
356
294
357
@classmethod
295
- def _stop (cls , compute_proc , api_proc , job_runner_proc , logging_thread , logging_queue ):
358
+ def _stop (cls , compute_proc , api_proc , job_runner_proc , logging_queue , logging_thread , logging_thread_stop ):
296
359
####################################################################################
297
360
# This is written as a class method so that it can be called by a weakref finalizer
298
361
####################################################################################
@@ -301,7 +364,6 @@ def _stop(cls, compute_proc, api_proc, job_runner_proc, logging_thread, logging_
301
364
# First the compute, since it will communicate its demise to the api server
302
365
# Flask must be last. It was started first and owns the db
303
366
304
- # First, stop all, then join all for better performance
305
367
if compute_proc is not None :
306
368
compute_proc .terminate ()
307
369
compute_proc .join ()
@@ -314,8 +376,10 @@ def _stop(cls, compute_proc, api_proc, job_runner_proc, logging_thread, logging_
314
376
api_proc .terminate ()
315
377
api_proc .join ()
316
378
317
- logging_queue . put ( None )
379
+ logging_thread_stop . set ( )
318
380
logging_thread .join ()
381
+ logging_queue .close ()
382
+ logging_queue .join_thread ()
319
383
320
384
def wait_for_api (self ):
321
385
"""
@@ -363,12 +427,22 @@ def stop(self):
363
427
Stops all components of the snowflake
364
428
"""
365
429
366
- if self ._finalizer is not None :
367
- self ._finalizer ()
430
+ if self ._compute_proc is not None :
431
+ self ._compute_proc .terminate ()
432
+ self ._compute_proc .join ()
433
+ self ._compute_proc = None
368
434
369
- self ._api_proc = None
370
- self ._compute_proc = None
371
- self ._job_runner_proc = None
435
+ if self ._job_runner_proc is not None :
436
+ self ._job_runner_proc .terminate ()
437
+ self ._job_runner_proc .join ()
438
+ self ._job_runner_proc = None
439
+
440
+ if self ._api_proc is not None :
441
+ self ._api_proc .terminate ()
442
+ self ._api_proc .join ()
443
+ self ._api_proc = None
444
+
445
+ self ._update_finalizer ()
372
446
373
447
def get_uri (self ) -> str :
374
448
"""
0 commit comments