Skip to content

fix: close the root container after every after_shutdown hook - #83

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

lesnik512 merged 1 commit into
mainfrom
fix/close-root-after-shutdown-hooks

Conversation

@lesnik512

Copy link
Copy Markdown
Member

What changed

setup_di used to register container.close_async as an after_shutdown hook when it was called. FastStream runs after_shutdown hooks in registration order, so a hook registered after setup_di ran against a closed root and got ContainerClosedError, after the APP finalizers had already run.

With manage_lifespan=True, setup_di now registers one on_startup hook that opens the root and, on the first startup only, appends close_async to the after_shutdown hooks. By then the user has registered their hooks, so the close runs after all of them. A flag in the closure stops a restarted app from appending the close a second time. This follows the startup install of the middleware in ADR 0002.

manage_lifespan=False is unchanged: neither open nor close is registered.

FastStream details this relies on (0.7.7)

  • on_startup and after_shutdown append an apply_types wrapper to the lists _on_startup_calling and _after_shutdown_calling. Because of the wrapper, a membership check on the original callable does not work, so the guard is a flag.
  • start() iterates _on_startup_calling and stop() iterates _after_shutdown_calling only at shutdown, so appending to the shutdown list during startup takes effect in the same cycle. TestApp calls the same start() and stop().
  • AsgiFastStream inherits both methods. as_asgi() (AsgiFastStream.from_app) shares the four hook lists with the original app, so the close lands in the right list whether setup_di gets a FastStream, an AsgiFastStream, or a FastStream that is converted later.

Limits

  • A FastStream(lifespan=...) context manager exits after all hooks, so its teardown still runs after the close. FastStream has no public hook after it. The README now says so and points to manage_lifespan=False for that case.
  • An after_shutdown hook appended during startup by an on_startup hook registered after setup_di runs after the close. The same goes for a hook registered between two runs of the same app, since the close keeps its position from the first startup.

Tests

test_after_shutdown_hook_registered_after_setup_di_sees_the_open_root registers an after_shutdown hook after setup_di, runs two TestApp cycles, and checks that the hook resolves the APP dependency before it is finalized, that the finalizer runs once per cycle, and that close_async is called once per cycle. It runs on a FastStream, an AsgiFastStream, and a FastStream converted with as_asgi() after setup_di. All three failed on main with ContainerClosedError. With the guard removed the close count check fails. The existing manage_lifespan=False tests still pass. The repro from the issue now prints ['user:live', 'finalized'] for every hook case.

Closes #82

@lesnik512
lesnik512 merged commit 9ae0c39 into main Oct 17, 2026
12 checks passed
@lesnik512
lesnik512 deleted the fix/close-root-after-shutdown-hooks branch October 17, 2026 13:33
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 after_shutdown hooks registered after setup_di

1 participant