Skip to content

Commit 1fe07e6

Browse files
committed
docs(release): notes for 3.1.0
1 parent f95472a commit 1fe07e6

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

‎planning/releases/3.1.0.md‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# modern-di-aiohttp 3.1.0 — the request container stops leaking into the GC
2+
3+
Every request used to leave a reference cycle behind. `di_middleware` binds the
4+
`Request` into the container's context and then stored the container back into
5+
that same request, so the container — and the request and every dependency
6+
resolved during it — could only be reclaimed by the garbage collector, never by
7+
reference counting.
8+
9+
**No public API change.** `FromDI`, `fetch_di_container`,
10+
`fetch_request_container`, `inject`, `setup_di`, `aiohttp_request_provider` and
11+
`aiohttp_websocket_provider` keep their signatures and behavior.
12+
13+
## Fix
14+
15+
- **The request container is removed from the request when the handler
16+
returns**, in a `finally` so it also runs when the handler raises. The
17+
container was already bounded by the `async with` around it, so nothing may
18+
read the entry after that block — the entry outliving the request was the bug.
19+
20+
Measured over 100 requests against a real server, GC disabled during the run:
21+
22+
| modern-di | this integration | cyclic objects per request |
23+
|---|---|---|
24+
| 3.1.1 | 3.0.0 | 21.9 |
25+
| **3.1.1** | **3.1.0** | **0** |
26+
27+
Bare aiohttp with no DI produces 0, so all of them were ours.
28+
29+
There were two independent cycles and both had to go. The other one — every
30+
`Container` storing itself in its own `_scope_map` — was fixed upstream in
31+
[modern-di 3.1.1](https://github.com/modern-python/modern-di/releases/tag/3.1.1).
32+
Neither fix alone moves the number, which is why this release requires it.
33+
34+
## Packaging
35+
36+
- **`modern-di` floor raised to `>=3.1.1,<4`** (from `>=3,<4`). Required, not
37+
cosmetic: this release's regression test fails against modern-di 3.0.0, which
38+
the old range permitted, so leaving the floor alone would claim support for a
39+
version the suite does not pass on.
40+
41+
## Feature
42+
43+
- **A canonical `examples/app.py`** — a minimal runnable aiohttp app wiring
44+
modern-di end to end, covered by a test so it cannot rot.
45+
46+
## Downstream
47+
48+
Upgrading pulls modern-di 3.1.1 or newer. No code change needed on your side;
49+
no API of this package moved.
50+
51+
If you read `request[_CONTAINER_REQUEST_KEY]` directly rather than through
52+
`FromDI` / `fetch_request_container`, note that the entry is now gone once the
53+
handler returns. Reading it during the request is unchanged.
54+
55+
## Why this was found
56+
57+
An audit of all 12 modern-di integrations that build per-request containers,
58+
prompted by the upstream 3.1.1 fix. Two were affected: this one and
59+
`modern-di-starlette` (fixed in its 3.1.0). Both shared the same signature —
60+
the container stashed inside the very request object its own context points at.
61+
The other ten are clean: `fastapi`, `litestar` and `taskiq` expose the container
62+
through a yield dependency and never store it back; `flask`, `aiogram` and
63+
`faststream` store it somewhere the context cannot reach; `celery`, `arq` and
64+
`typer` pass no context at all; `grpc` keeps the child in a contextvar the
65+
`ServicerContext` cannot reach.
66+
67+
## Internals
68+
69+
- 12 tests, 100% line coverage, Python 3.10–3.14; `ruff`, `ty` clean.

0 commit comments

Comments
 (0)