Skip to content

fix(worker): wake the event loop on shutdown signals - #724

Open
AlexeyShalaev wants to merge 1 commit into
taskiq-python:masterfrom
AlexeyShalaev:fix/worker-signal-wakeup
Open

AlexeyShalaev wants to merge 1 commit into
taskiq-python:masterfrom
AlexeyShalaev:fix/worker-signal-wakeup

Conversation

@AlexeyShalaev

Copy link
Copy Markdown

Closes #722

The worker's signal handler is registered with signal.signal and calls shutdown_event.set(). That only schedules the event's waiters and doesn't wake the loop, so without uvloop a worker that is idle waiting for a message stays blocked in the selector. The main process then waits for it until it gets killed, and broker.shutdown() doesn't run.

The handler now sets the event through loop.call_soon_threadsafe, which also wakes the loop. The loop is created before the handlers are installed, so it exists by the time a signal can arrive.

The test starts a real worker the way the #686 test does and sits next to it to reuse its idle broker and CLI runner. A stub uvloop.py in the worker's directory makes it run on the default loop: CI installs uvloop, and with uvloop the test passes even without the fix. It sends SIGTERM to the main process and expects it to exit within 10 s. Without the fix it times out; with it, it passes on Python 3.10, 3.12 and 3.14. The full suite and prek run -a pass.

The repro from the issue, Python 3.12 without uvloop, taskiq worker --workers 1, SIGTERM to the main process:

broker before after
no-timer broker from the issue still running after 30 s exits in 1.2 s, broker.shutdown() runs
taskiq-redis ListQueueBroker, socket_timeout=None still running after 30 s exits in 1.0 s

Most of that second is the main process's 1 s polling loop. The worker starts shutting down the broker 3 to 4 ms after it gets the signal.

The worker's signal handler is installed with signal.signal and calls
shutdown_event.set(). That only schedules the event's waiters, it
doesn't wake the loop up. Without uvloop, a worker that is idle
waiting for a message stays blocked in the selector, never shuts down,
and the main process waits for it until it gets killed.

Set the event with loop.call_soon_threadsafe, which also wakes the
loop up, and create the loop before the handlers are installed.

Closes taskiq-python#722

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Idle worker doesn't stop on SIGTERM without uvloop

1 participant