• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aio-libs / multidict / 35994495384
87%

Build:
DEFAULT BRANCH: master
Ran 24 Sep 2026 11:42AM UTC
Jobs 1
Files 43
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

24 Sep 2026 11:41AM UTC coverage: 87.545% (+0.04%) from 87.504%
35994495384

push

github

web-flow
Drain the retired hash tables when a cleared multidict is deallocated (#1562)

<!-- Thank you for your contribution! -->

## What do these changes do?

On the free-threaded build, `md_clear()` returned early once `md->keys`
was
`&empty_htkeys`, so nothing on that path drained `md->retired`. The
drain
leaves a table on that list whenever it finds a lock-free reader in
flight,
for the next drain to free, and the only drain sites are reader exits
and
retirements; a multidict dropped right after a `clear()` that left its
table
there gets neither, so the table, and the references its entries still
owned,
were never freed. Draining in that early return makes the object's own
teardown the last drain, and that drain does free the table: a lock-free
reader reaches a multidict through a live reference, so its count is
zero by
the time the refcount is.

Getting a table onto that list and leaving it there needs the coarse
active-readers gate to flip between the two times
`_md_drain_retired_slow()`
reads it. The first read sees zero and takes the list, a reader then
enters,
the second read sees it, so the table goes to the push-back at the end
of the
function, and that reader's own exit drain runs before the push-back
lands and
finds the list empty. Nothing is left to drain, and since #1542 the
shell the
object is recycled into is memset, so the next owner cannot pick the
table up
either. Forcing that ordering takes an artificially widened window; the
recipe
and the resulting trace are in the footer.

## Are there changes in behavior for the user?

No, beyond the leak going away. The fix is C-extension only, and only
the
free-threaded build has a retirement scheme; builds with the GIL enabled
emit
byte-identical code.

## Is it a substantial burden for the maintainers to support this?

No. It is one load on a path that is not hot, and no benchmarked
operation
moves.

## Related issue number

Fixes #1555

## Checklist

- [x] I think the code is well written
- [x] U... (continued)

775 of 1550 branches covered (50.0%)

Branch coverage included in aggregate %.

31 of 31 new or added lines in 1 file covered. (100.0%)

7540 of 7948 relevant lines covered (94.87%)

1.9 hits per line

Jobs
ID Job ID Ran Files Coverage
1 MyPy - 35994495384.1 24 Sep 2026 11:42AM UTC 86
87.54
GitHub Action Run
Source Files on build 35994495384
  • Tree
  • List 43
  • Changed 3
  • Source Changed 1
  • Coverage Changed 3
Coverage ∆ File Lines Relevant Covered Missed Hits/Line Branch Hits Branch Misses
  • Back to Repo
  • Github Actions Build #35994495384
  • ae427c67 on github
  • Prev Build on master (#35994271425)
  • Next Build on master (#35996458823)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc