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

aio-libs / multidict / 35528954494
87%

Build:
DEFAULT BRANCH: master
Ran 20 Sep 2026 06:25PM UTC
Jobs 1
Files 39
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

20 Sep 2026 06:24PM UTC coverage: 87.382% (+0.08%) from 87.303%
35528954494

push

github

web-flow
Stop concurrent update()/merge()/__setitem__() from losing a key on free-threaded builds (#1487)

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

## What do these changes do?

`_md_replace()`/`_md_update()` mark the entry they are about to
overwrite before decref'ing its old key/value. Under the free-threaded
build, that decref can transiently suspend the writer's held critical
section, letting a second writer for the same key see the marked entry
as absent (a raw hash comparison can't tell a marked entry from a
missing one) and insert a duplicate; whichever writer's post-update
unmark sweep ran first could then unmark the other's still in-flight
entry, making it mistake its own live entry for a stale duplicate and
delete it, losing the key entirely.

Every decref that used to run inline during the scan is now deferred
into a small accumulator and released only once the caller has left its
critical section, so nothing can suspend mid-scan any more.
`setdefault()` had an unrelated instance of the same raw-comparison
blind spot (it could insert a duplicate instead of recognizing an
in-flight key); masking its comparison fixes that independently.

## Are there changes in behavior for the user?

No visible behavior change on the happy path. On the free-threaded
build, two threads racing
`update()`/`merge()`/`__setitem__()`/`setdefault()` against the same key
no longer lose the key or leave a spurious duplicate.

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

No. The change is contained to `multidict/_multilib/hashtable.h` and the
small set of `_multidict.c` call sites that own the new accumulator's
lifetime, and only affects the free-threaded build's writer paths
(`d[key]=value`, `.update()`, `.merge()`, `.setdefault()`). The
non-free-threaded build is untouched.

## Related issue number

Fixes #1483

## Checklist

- [x] I think the code is well written
- [x] Unit tests for the changes exist
- [x] Documentation reflects the changes
- [ ] I... (continued)

762 of 1524 branches covered (50.0%)

Branch coverage included in aggregate %.

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

6371 of 6639 relevant lines covered (95.96%)

1.92 hits per line

Jobs
ID Job ID Ran Files Coverage
1 MyPy - 35528954494.1 20 Sep 2026 06:25PM UTC 78
87.37
GitHub Action Run
Source Files on build 35528954494
  • Tree
  • List 39
  • 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 #35528954494
  • 6f91b5a4 on github
  • Prev Build on master (#35515167561)
  • Next Build on master (#35529635900)
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