Skip to content

docs: share a container with manage_lifespan=False and one owner - #702

Merged
lesnik512 merged 1 commit into
mainfrom
docs/manage-lifespan-701
Oct 17, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
docs/manage-lifespan-701

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #701

Documents the decision in #701: no counted lifespan. Every integration that opens and closes the root has a keyword-only manage_lifespan: bool = True, and with False the user owns the root.

Changes

  • providers/lifecycle.md: the docs: warn that apps sharing a root container close it for each other #700 caution now says a shared container needs one owner. Pass manage_lifespan=False to every integration that shares it and wrap the apps in async with container:. Flask, gRPC and Typer never touch the root. Links the recipe.
  • New recipe recipes/shared-container.md: a FastAPI app and a FastStream app on one container, both with the flag off. It covers the side-by-side case (asyncio.gather of stream.run() and a uvicorn server) and the case where FastStream starts inside the FastAPI lifespan. APP finalizers run once, at the owner's exit. Hidden blocks check both cases with TestClient, TestNatsBroker and TestApp.
  • troubleshooting/container-closed-error.md and providers/context.md: the shared-container sentences name the flag and link the recipe.
  • Integration pages (starlette 4.2.0, fastapi 4.1.0, litestar 4.1.0, aiohttp 4.2.0, aiogram 4.2.0, faststream 4.1.0, taskiq 4.1.0, arq 4.1.0, celery 4.1.0): the flag is in the lifecycle section and the API table, with the release it arrived in. Starlette and FastAPI say the flag covers only that app's own container, and a mounted app's own flag decides for it. Celery says a prefork child inherits the open container, and the user closes it from their own worker_process_shutdown handler.
  • conftest.py: faststream and uvicorn join the packages that skip in just test-docs. uvicorn comes in through modern-di-fastmcp in just test-docs-integrations, so the extras list is unchanged.

The gather order differs from the issue

The issue shows asyncio.gather(server.serve(), stream.run()). With both flags off, that order hangs on SIGTERM: FastStream's loop.add_signal_handler replaces uvicorn's handler, so FastStream stops and uvicorn keeps serving. With gather(stream.run(), server.serve()), uvicorn stops first, restores the earlier handler and raises the signal again, so FastStream stops after it. The container then closes once. I checked both orders with modern-di-fastapi 4.1.0, modern-di-faststream 4.1.0, uvicorn 0.54.0 and faststream 0.7.7. The recipe uses the working order and explains why.

Rewrite the shared-container caution from #700 around a single owner, add a
recipe for a FastAPI app and a FastStream app sharing one container, and
document the manage_lifespan flag on each integration page that has it.

Closes #701

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Details
Benchmark suite Current: 533b813 Previous: d5d167e Ratio
benchmarks/test_guard_by_type.py::test_g16_resolve_by_type 3259300.1768232496 iter/sec (stddev: 1.1719772210567866e-8) 6614892.345319018 iter/sec (stddev: 1.0929152659504707e-8) 2.03
benchmarks/test_guard_by_type.py::test_g17_resolve_by_type_large_registry 3257169.632932509 iter/sec (stddev: 1.2026941281055951e-8) 6234973.324590048 iter/sec (stddev: 1.5000817187998557e-8) 1.91
benchmarks/test_guard_cold.py::test_g8_cold_first_resolve 16043.618361941204 iter/sec (stddev: 0.000053905631977389955) 22188.514415505066 iter/sec (stddev: 0.000035116159669307494) 1.38
benchmarks/test_guard_cold.py::test_g8b_cold_first_resolve_cached 12719.789967340052 iter/sec (stddev: 0.00013230822904484915) 17848.38733805733 iter/sec (stddev: 0.00012983775205901163) 1.40
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[1] 480.6872461432672 iter/sec (stddev: 0.00003921777607523051) 882.4458447040489 iter/sec (stddev: 0.00005007895117263565) 1.84
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[2] 467.6135557646355 iter/sec (stddev: 0.000051572333286011216) 815.1451369162148 iter/sec (stddev: 0.00006205129290350007) 1.74
benchmarks/test_guard_concurrency.py::test_g14_concurrent_cached_hit[4] 438.0726997417211 iter/sec (stddev: 0.00005835652527176934) 771.9224829291804 iter/sec (stddev: 0.000051560288745671776) 1.76
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[1] 13414.01265768367 iter/sec (stddev: 0.000008120819925014643) 10718.050429434346 iter/sec (stddev: 0.00001405952819628994) 0.80
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[2] 6308.629511387363 iter/sec (stddev: 0.000011799316533338899) 5194.4762113398365 iter/sec (stddev: 0.000055616411227824346) 0.82
benchmarks/test_guard_concurrency.py::test_g15_concurrent_first_resolve[4] 3403.9139621161685 iter/sec (stddev: 0.000038623879832138995) 2766.1713199048672 iter/sec (stddev: 0.00006770754223448173) 0.81
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[1] 8627.038461471422 iter/sec (stddev: 0.00003102300534651064) 10006.776255353865 iter/sec (stddev: 0.000012485493400864475) 1.16
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[2] 4391.261871833296 iter/sec (stddev: 0.00002488832729008276) 3966.2898415947802 iter/sec (stddev: 0.000021012149128388896) 0.90
benchmarks/test_guard_concurrency.py::test_g15b_concurrent_first_resolve_sibling_children[4] 2046.054469545547 iter/sec (stddev: 0.000045198401763239224) 1990.070113983001 iter/sec (stddev: 0.00007702623055312865) 0.97
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[1] 41467.03457175756 iter/sec (stddev: 0.000004899712713012755) 18289.568681821627 iter/sec (stddev: 0.000003670232355104127) 0.44
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[2] 14047.378764223864 iter/sec (stddev: 0.00001286433936985704) 12746.041079554532 iter/sec (stddev: 0.000013321275657479497) 0.91
benchmarks/test_guard_concurrency.py::test_g15c_worker_pool_floor_control[4] 5721.764332720986 iter/sec (stddev: 0.00003848276870980056) 5414.373265965316 iter/sec (stddev: 0.00004619088497537116) 0.95
benchmarks/test_guard_lifecycle.py::test_g6_build_child_container 1514014.1120381148 iter/sec (stddev: 3.079944351938926e-8) 2557016.6775028585 iter/sec (stddev: 2.249113177769194e-8) 1.69
benchmarks/test_guard_lifecycle.py::test_g6b_build_child_container_auto_scope 1654528.8363356777 iter/sec (stddev: 2.852479804880834e-8) 2864672.8543470814 iter/sec (stddev: 2.0401720220810464e-8) 1.73
benchmarks/test_guard_lifecycle.py::test_g7_request_lifecycle_batch 3092.0122701103733 iter/sec (stddev: 0.00001790983371216991) 4668.377147152932 iter/sec (stddev: 0.000006594970554881534) 1.51
benchmarks/test_guard_lifecycle.py::test_g7c_event_loop_floor_control 57543.04051602528 iter/sec (stddev: 0.0000027482937998829485) 90763.94329114971 iter/sec (stddev: 0.000001244606839229857) 1.58
benchmarks/test_guard_lifecycle.py::test_g7b_request_cycle_sync 410995.6713360705 iter/sec (stddev: 1.1715315346288843e-7) 627221.9140280918 iter/sec (stddev: 8.339147924090178e-8) 1.53
benchmarks/test_guard_lifecycle.py::test_g13_teardown_at_scale 65028.907015270845 iter/sec (stddev: 0.00000201470169427272) 98973.4349339857 iter/sec (stddev: 9.675758960576356e-7) 1.52
benchmarks/test_guard_lifecycle.py::test_g13b_teardown_at_scale_async_no_finalizers 582.8698334351706 iter/sec (stddev: 0.000977979433943244) 891.6048760363765 iter/sec (stddev: 0.0009891898261389358) 1.53
benchmarks/test_guard_resolve.py::test_g1_transient_resolve 2108443.8008126174 iter/sec (stddev: 3.643590676888671e-8) 3829164.872535829 iter/sec (stddev: 2.4671036532409833e-8) 1.82
benchmarks/test_guard_resolve.py::test_g2_cached_resolve 3613779.746849006 iter/sec (stddev: 1.2500530253088961e-8) 6472059.600973104 iter/sec (stddev: 1.4100226005685917e-8) 1.79
benchmarks/test_guard_resolve.py::test_g3_deep_chain 802555.9802833487 iter/sec (stddev: 4.1555770256427324e-8) 1393881.1128142371 iter/sec (stddev: 4.6755047461464293e-8) 1.74
benchmarks/test_guard_resolve.py::test_g4_wide_resolve 476283.60097498784 iter/sec (stddev: 4.0725578228055595e-7) 761653.8658615567 iter/sec (stddev: 3.247565662466623e-7) 1.60
benchmarks/test_guard_resolve.py::test_g5_cross_scope 1956331.2626113317 iter/sec (stddev: 4.0088335496065334e-8) 3182317.263195788 iter/sec (stddev: 2.2342043632675758e-8) 1.63
benchmarks/test_guard_resolve.py::test_g9_context_resolve 1393082.1144191835 iter/sec (stddev: 1.4438867520740004e-7) 2475931.316943801 iter/sec (stddev: 1.132751206586173e-7) 1.78
benchmarks/test_guard_resolve.py::test_g12_override_active_resolve 823986.1715401643 iter/sec (stddev: 6.172730512737653e-8) 1409484.491617221 iter/sec (stddev: 2.9863822030957385e-8) 1.71
benchmarks/test_guard_resolve.py::test_g18_alias_hop 3422156.8986208234 iter/sec (stddev: 1.8967016775463708e-8) 6193888.403586523 iter/sec (stddev: 1.247957913272508e-8) 1.81
benchmarks/test_guard_validate.py::test_g10_validate_deep_chain 35400.750120318604 iter/sec (stddev: 0.00001891294545171517) 40436.505063159784 iter/sec (stddev: 0.00025723935944531166) 1.14
benchmarks/test_guard_validate.py::test_g11_validate_wide 21474.10404489243 iter/sec (stddev: 0.00002358850422808318) 30723.64444381395 iter/sec (stddev: 0.000017359402832119415) 1.43

This comment was automatically generated by workflow using github-action-benchmark.

@lesnik512
lesnik512 merged commit 7456d6c into main Oct 17, 2026
12 checks passed
@lesnik512
lesnik512 deleted the docs/manage-lifespan-701 branch October 17, 2026 13:13
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.

Let one owner open and close a root container shared by several apps

1 participant