1
1
import logging
2
2
import signal
3
+ from contextlib import suppress
3
4
from dataclasses import dataclass
4
- from multiprocessing import Process , Queue
5
+ from multiprocessing import Event , Process , Queue , current_process
6
+ from multiprocessing .synchronize import Event as EventType
5
7
from time import sleep
6
8
from typing import Any , Callable , List , Optional
7
9
@@ -54,7 +56,7 @@ def handle(
54
56
self ,
55
57
workers : List [Process ],
56
58
args : WorkerArgs ,
57
- worker_func : Callable [[WorkerArgs ], None ],
59
+ worker_func : Callable [[WorkerArgs , EventType ], None ],
58
60
) -> None :
59
61
"""
60
62
This action reloads a single process.
@@ -73,22 +75,31 @@ def handle(
73
75
logger .debug (f"Process { worker .name } is already terminated." )
74
76
# Waiting worker shutdown.
75
77
worker .join ()
78
+ event : EventType = Event ()
76
79
new_process = Process (
77
80
target = worker_func ,
78
- kwargs = {"args" : args },
81
+ kwargs = {"args" : args , "event" : event },
79
82
name = f"worker-{ self .worker_num } " ,
80
83
daemon = True ,
81
84
)
82
85
new_process .start ()
83
86
logger .info (f"Process { new_process .name } restarted with pid { new_process .pid } " )
84
87
workers [self .worker_num ] = new_process
88
+ _wait_for_worker_startup (new_process , event )
85
89
86
90
87
91
@dataclass
88
92
class ShutdownAction (ProcessActionBase ):
89
93
"""This action shuts down process manager loop."""
90
94
91
95
96
+ def _wait_for_worker_startup (process : Process , event : EventType ) -> None :
97
+ while process .is_alive ():
98
+ with suppress (TimeoutError ):
99
+ event .wait (0.1 )
100
+ return
101
+
102
+
92
103
def schedule_workers_reload (
93
104
action_queue : "Queue[ProcessActionBase]" ,
94
105
) -> None :
@@ -118,6 +129,9 @@ def get_signal_handler(
118
129
"""
119
130
120
131
def _signal_handler (signum : int , _frame : Any ) -> None :
132
+ if current_process ().name .startswith ("worker" ):
133
+ raise KeyboardInterrupt
134
+
121
135
logger .debug (f"Got signal { signum } ." )
122
136
action_queue .put (ShutdownAction ())
123
137
logger .warn ("Workers are scheduled for shutdown." )
@@ -137,8 +151,8 @@ class ProcessManager:
137
151
def __init__ (
138
152
self ,
139
153
args : WorkerArgs ,
140
- worker_function : Callable [[WorkerArgs ], None ],
141
- observer : Optional [Observer ] = None ,
154
+ worker_function : Callable [[WorkerArgs , EventType ], None ],
155
+ observer : Optional [Observer ] = None , # type: ignore[valid-type]
142
156
) -> None :
143
157
self .worker_function = worker_function
144
158
self .action_queue : "Queue[ProcessActionBase]" = Queue (- 1 )
@@ -162,10 +176,12 @@ def __init__(
162
176
163
177
def prepare_workers (self ) -> None :
164
178
"""Spawn multiple processes."""
179
+ events : List [EventType ] = []
165
180
for process in range (self .args .workers ):
181
+ event = Event ()
166
182
work_proc = Process (
167
183
target = self .worker_function ,
168
- kwargs = {"args" : self .args },
184
+ kwargs = {"args" : self .args , "event" : event },
169
185
name = f"worker-{ process } " ,
170
186
daemon = True ,
171
187
)
@@ -176,6 +192,11 @@ def prepare_workers(self) -> None:
176
192
work_proc .pid ,
177
193
)
178
194
self .workers .append (work_proc )
195
+ events .append (event )
196
+
197
+ # Wait for workers startup
198
+ for worker , event in zip (self .workers , events ):
199
+ _wait_for_worker_startup (worker , event )
179
200
180
201
def start (self ) -> None : # noqa: C901, WPS213
181
202
"""
0 commit comments