77from logging import basicConfig , getLevelName , getLogger
88from multiprocessing import Process
99from pathlib import Path
10+ from queue import Queue
1011from time import sleep
1112from typing import Any , Generator , List
1213
14+ from watchdog .observers import Observer
15+
1316from taskiq .abc .broker import AsyncBroker
1417from taskiq .cli .args import TaskiqArgs
1518from taskiq .cli .async_task_runner import async_listen_messages
19+ from taskiq .cli .watcher import FileWatcher
1620
1721try :
1822 import uvloop # noqa: WPS433
2529
2630restart_workers = True
2731worker_processes : List [Process ] = []
32+ observer = Observer ()
33+ reload_queue : "Queue[int]" = Queue (- 1 )
2834
2935
3036def signal_handler (_signal : int , _frame : Any ) -> None :
@@ -45,9 +51,33 @@ def signal_handler(_signal: int, _frame: Any) -> None:
4551 # This is how we kill children,
4652 # by sending SIGINT to child processes.
4753 if process .pid is None :
48- process . kill ()
49- else :
54+ continue
55+ try :
5056 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 ()
5181
5282
5383@contextmanager
@@ -212,13 +242,16 @@ def interrupt_handler(_signum: int, _frame: Any) -> None:
212242 loop .run_until_complete (shutdown_broker (broker , args .shutdown_timeout ))
213243
214244
215- def watch_workers_restarts (args : TaskiqArgs ) -> None :
245+ def watcher_loop (args : TaskiqArgs ) -> None : # noqa: C901, WPS213
216246 """
217247 Infinate loop for main process.
218248
219249 This loop restarts worker processes
220250 if they exit with error returncodes.
221251
252+ Also it reads process ids from reload_queue
253+ and reloads workers if they were scheduled to reload.
254+
222255 :param args: cli arguements.
223256 """
224257 global worker_processes # noqa: WPS420
@@ -228,6 +261,18 @@ def watch_workers_restarts(args: TaskiqArgs) -> None:
228261 # List of processes to remove.
229262 sleep (1 )
230263 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+
231276 for worker_id , worker in enumerate (worker_processes ):
232277 if worker .is_alive ():
233278 continue
@@ -241,14 +286,13 @@ def watch_workers_restarts(args: TaskiqArgs) -> None:
241286 worker_processes [worker_id ].start ()
242287 else :
243288 logger .info ("Worker-%s terminated." , worker_id )
244- worker .join ()
245289 process_to_remove .append (worker )
246290
247291 for dead_process in process_to_remove :
248292 worker_processes .remove (dead_process )
249293
250294
251- def run_worker (args : TaskiqArgs ) -> None :
295+ def run_worker (args : TaskiqArgs ) -> None : # noqa: WPS213
252296 """
253297 This function starts worker processes.
254298
@@ -279,7 +323,17 @@ def run_worker(args: TaskiqArgs) -> None:
279323 )
280324 worker_processes .append (work_proc )
281325
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 ()
282336 signal .signal (signal .SIGINT , signal_handler )
283337 signal .signal (signal .SIGTERM , signal_handler )
284338
285- watch_workers_restarts (args = args )
339+ watcher_loop (args = args )
0 commit comments