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[int]" = Queue (- 1 )
28
34
29
35
30
36
def signal_handler (_signal : int , _frame : Any ) -> None :
@@ -45,9 +51,33 @@ 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
+ logger .info ("Reloading workers" )
77
+ for worker_id , _ in enumerate (worker_processes ):
78
+ reload_queue .put (worker_id )
79
+ logger .info ("Worker %s scheduled to reload" , worker_id )
80
+ reload_queue .join ()
51
81
52
82
53
83
@contextmanager
@@ -212,13 +242,16 @@ def interrupt_handler(_signum: int, _frame: Any) -> None:
212
242
loop .run_until_complete (shutdown_broker (broker , args .shutdown_timeout ))
213
243
214
244
215
- def watch_workers_restarts (args : TaskiqArgs ) -> None :
245
+ def watcher_loop (args : TaskiqArgs ) -> None : # noqa: C901, WPS213
216
246
"""
217
247
Infinate loop for main process.
218
248
219
249
This loop restarts worker processes
220
250
if they exit with error returncodes.
221
251
252
+ Also it reads process ids from reload_queue
253
+ and reloads workers if they were scheduled to reload.
254
+
222
255
:param args: cli arguements.
223
256
"""
224
257
global worker_processes # noqa: WPS420
@@ -228,6 +261,18 @@ def watch_workers_restarts(args: TaskiqArgs) -> None:
228
261
# List of processes to remove.
229
262
sleep (1 )
230
263
process_to_remove = []
264
+ while not reload_queue .empty ():
265
+ process_id = reload_queue .get ()
266
+ worker_processes [process_id ].terminate ()
267
+ worker_processes [process_id ].join ()
268
+ worker_processes [process_id ] = Process (
269
+ target = start_listen ,
270
+ kwargs = {"args" : args },
271
+ name = f"worker-{ process_id } " ,
272
+ )
273
+ worker_processes [process_id ].start ()
274
+ reload_queue .task_done ()
275
+
231
276
for worker_id , worker in enumerate (worker_processes ):
232
277
if worker .is_alive ():
233
278
continue
@@ -241,14 +286,13 @@ def watch_workers_restarts(args: TaskiqArgs) -> None:
241
286
worker_processes [worker_id ].start ()
242
287
else :
243
288
logger .info ("Worker-%s terminated." , worker_id )
244
- worker .join ()
245
289
process_to_remove .append (worker )
246
290
247
291
for dead_process in process_to_remove :
248
292
worker_processes .remove (dead_process )
249
293
250
294
251
- def run_worker (args : TaskiqArgs ) -> None :
295
+ def run_worker (args : TaskiqArgs ) -> None : # noqa: WPS213
252
296
"""
253
297
This function starts worker processes.
254
298
@@ -279,7 +323,17 @@ def run_worker(args: TaskiqArgs) -> None:
279
323
)
280
324
worker_processes .append (work_proc )
281
325
326
+ if args .reload :
327
+ observer .schedule (
328
+ FileWatcher (
329
+ callback = schedule_workers_reload ,
330
+ use_gitignore = not args .no_gitignore ,
331
+ ),
332
+ path = "." ,
333
+ recursive = True ,
334
+ )
335
+ observer .start ()
282
336
signal .signal (signal .SIGINT , signal_handler )
283
337
signal .signal (signal .SIGTERM , signal_handler )
284
338
285
- watch_workers_restarts (args = args )
339
+ watcher_loop (args = args )
0 commit comments