Skip to content

fix: close the root container after all worker shutdown code - #42

Merged
lesnik512 merged 2 commits into
mainfrom
fix/close-root-after-shutdown-hooks
Oct 17, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
fix/close-root-after-shutdown-hooks

Conversation

@lesnik512

@lesnik512 lesnik512 commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

setup_di closed the root container in a WORKER_SHUTDOWN handler. taskiq runs those handlers in list order and then calls middleware shutdown(), so handlers added after setup_di and every middleware shutdown() got ContainerClosedError, and APP finalizers had already run.

How taskiq 0.12.6 runs startup and shutdown

AsyncBroker.startup() runs the event handlers for the event in list order, then startup() on each middleware that overrides it, then result_backend.startup(). shutdown() does the same with the shutdown event, middleware shutdown() and result_backend.shutdown(). add_middlewares() calls set_broker() and appends. event_handlers and middlewares are public attributes, and nothing else runs after the middleware loop that an integration can hook into.

InMemoryBroker overrides both methods. It runs the client and worker event handlers and never calls middleware startup() or shutdown(). So a closing middleware alone, as the issue suggests, would leave the root open forever on InMemoryBroker, and on any other broker that does the same.

Approach

setup_di makes one _RootCloser middleware per call.

  • On WORKER_STARTUP, _open_root opens the root, appends the closer to broker.middlewares if it is not there yet, and moves _close_root to the end of the WORKER_SHUTDOWN handlers. Because the closer is added during the handler loop, the middleware loop that follows calls its startup(), which records that this broker runs middleware hooks.
  • On WORKER_SHUTDOWN, _close_root checks that flag. If middleware hooks run, it moves the closer to the end of broker.middlewares (the list is not being iterated yet) and the closer's shutdown() closes the root after every other middleware. If they do not run, or startup never happened, _close_root closes the root itself. It is the last shutdown handler by then, so it still runs after handlers added after setup_di.
  • The closer's shutdown() only closes when its startup() ran in the same cycle, so the root closes exactly once.

Restarts: the closer is never added twice, the flag resets on each startup, and the handler move is remove plus append, so neither list grows. manage_lifespan=False registers nothing, as before.

Raised taskiq floor to 0.11.8

Before 0.11.8, AsyncBroker.shutdown() passed the bound method to maybe_awaitable(middleware.shutdown) without calling it, so no middleware shutdown() ever ran. The closer's startup() did run, so on those versions the root would never close. The floors job caught this on 0.11.0. Since middleware shutdown does not work there at all, the issue's middleware criterion cannot be met on those versions, so the floor is now taskiq>=0.11.8.

Limits

  • A WORKER_SHUTDOWN handler or middleware shutdown() that raises stops broker.shutdown(), and the root is not closed. That was already true before.
  • On a broker that skips middleware hooks, middleware shutdown() never runs, so only the handler ordering applies.
  • A broker that calls middleware startup() but not shutdown() would leave the root open. No taskiq broker in 0.12.6 does this.

Changes

  • modern_di_taskiq/main.py: _RootCloser and the new open and close handlers.
  • tests/test_shutdown_order.py: a stub AsyncBroker with WORKER_SHUTDOWN handlers and middlewares registered before and after setup_di, a middleware added after startup, two cycles with no duplicate middleware, InMemoryBroker handlers added after setup_di over two cycles, shutdown without startup, and manage_lifespan=False on both brokers. The first four failed on main with ContainerClosedError. The issue's repro now prints ['user:live', 'finalized'] for all four cases.
  • README.md and CONTEXT.md: state when the root closes.
  • pyproject.toml: taskiq>=0.11.8,<0.13.

Closes #41

@lesnik512
lesnik512 merged commit ac47ec7 into main Oct 17, 2026
12 checks passed
@lesnik512
lesnik512 deleted the fix/close-root-after-shutdown-hooks branch October 17, 2026 13:35
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.

The root container closes before later WORKER_SHUTDOWN handlers and middleware shutdown

1 participant