7
7
from logging import basicConfig , getLevelName , getLogger
8
8
from multiprocessing import Process
9
9
from pathlib import Path
10
+ from queue import Queue
10
11
from time import sleep
11
12
from typing import Any , Generator , List
12
13
14
+ from watchdog .observers import Observer
15
+
13
16
from taskiq .abc .broker import AsyncBroker
14
17
from taskiq .cli .args import TaskiqArgs
15
18
from taskiq .cli .async_task_runner import async_listen_messages
19
+ from taskiq .cli .watcher import FileWatcher
16
20
17
21
try :
18
22
import uvloop # noqa: WPS433
25
29
26
30
restart_workers = True
27
31
worker_processes : List [Process ] = []
32
+ observer = Observer ()
33
+ reload_queue : "Queue[bool]" = Queue (- 1 )
28
34
29
35
30
36
def signal_handler (_signal : int , _frame : Any ) -> None :
@@ -45,9 +51,31 @@ def signal_handler(_signal: int, _frame: Any) -> None:
45
51
# This is how we kill children,
46
52
# by sending SIGINT to child processes.
47
53
if process .pid is None :
48
- process . kill ()
49
- else :
54
+ continue
55
+ try :
50
56
os .kill (process .pid , signal .SIGINT )
57
+ except ProcessLookupError :
58
+ continue
59
+ process .join ()
60
+ if observer .is_alive ():
61
+ observer .stop ()
62
+ observer .join ()
63
+
64
+
65
+ def schedule_workers_reload () -> None :
66
+ """
67
+ Function to schedule workers to restart.
68
+
69
+ This function adds worker ids to the queue.
70
+
71
+ This queue is later read in watcher loop.
72
+ """
73
+ global worker_processes # noqa: WPS420
74
+ global reload_queue # noqa: WPS420
75
+
76
+ reload_queue .put (True )
77
+ logger .info ("Scheduled workers reload." )
78
+ reload_queue .join ()
51
79
52
80
53
81
@contextmanager
@@ -212,13 +240,16 @@ def interrupt_handler(_signum: int, _frame: Any) -> None:
212
240
loop .run_until_complete (shutdown_broker (broker , args .shutdown_timeout ))
213
241
214
242
215
- def watch_workers_restarts (args : TaskiqArgs ) -> None :
243
+ def watcher_loop (args : TaskiqArgs ) -> None : # noqa: C901, WPS213
216
244
"""
217
245
Infinate loop for main process.
218
246
219
247
This loop restarts worker processes
220
248
if they exit with error returncodes.
221
249
250
+ Also it reads process ids from reload_queue
251
+ and reloads workers if they were scheduled to reload.
252
+
222
253
:param args: cli arguements.
223
254
"""
224
255
global worker_processes # noqa: WPS420
@@ -228,6 +259,21 @@ def watch_workers_restarts(args: TaskiqArgs) -> None:
228
259
# List of processes to remove.
229
260
sleep (1 )
230
261
process_to_remove = []
262
+ if not reload_queue .empty ():
263
+ while not reload_queue .empty ():
264
+ reload_queue .get ()
265
+ reload_queue .task_done ()
266
+
267
+ for worker_id , worker in enumerate (worker_processes ):
268
+ worker .terminate ()
269
+ worker .join ()
270
+ worker_processes [worker_id ] = Process (
271
+ target = start_listen ,
272
+ kwargs = {"args" : args },
273
+ name = f"worker-{ worker_id } " ,
274
+ )
275
+ worker_processes [worker_id ].start ()
276
+
231
277
for worker_id , worker in enumerate (worker_processes ):
232
278
if worker .is_alive ():
233
279
continue
@@ -241,14 +287,13 @@ def watch_workers_restarts(args: TaskiqArgs) -> None:
241
287
worker_processes [worker_id ].start ()
242
288
else :
243
289
logger .info ("Worker-%s terminated." , worker_id )
244
- worker .join ()
245
290
process_to_remove .append (worker )
246
291
247
292
for dead_process in process_to_remove :
248
293
worker_processes .remove (dead_process )
249
294
250
295
251
- def run_worker (args : TaskiqArgs ) -> None :
296
+ def run_worker (args : TaskiqArgs ) -> None : # noqa: WPS213
252
297
"""
253
298
This function starts worker processes.
254
299
@@ -279,7 +324,17 @@ def run_worker(args: TaskiqArgs) -> None:
279
324
)
280
325
worker_processes .append (work_proc )
281
326
327
+ if args .reload :
328
+ observer .schedule (
329
+ FileWatcher (
330
+ callback = schedule_workers_reload ,
331
+ use_gitignore = not args .no_gitignore ,
332
+ ),
333
+ path = "." ,
334
+ recursive = True ,
335
+ )
336
+ observer .start ()
282
337
signal .signal (signal .SIGINT , signal_handler )
283
338
signal .signal (signal .SIGTERM , signal_handler )
284
339
285
- watch_workers_restarts (args = args )
340
+ watcher_loop (args = args )
0 commit comments