Repository navigation
chore(deps): update dependency multidict to v6.9.1 [security] - #873
Merged
Merged
Conversation
Generated by renovateBot
renovate
Bot
requested review from
changt,
cloudwebrtc,
lukasIO and
xianshijing-lk
as code owners
October 17, 2026 06:07
Contributor
There was a problem hiding this comment.
🔍 Devin Review: 1 flag
Not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
6.7.1→6.9.1Warning
Some dependencies could not be looked up. Check the Dependency Dashboard for more information.
Multidict: Reference leak in CIMultiDict/MultiDict items-view union and subtraction
CVE-2026-104874 / GHSA-54p9-h82j-f925
More information
Details
Description
A reference leak in the items-view union and subtraction operators of aio-libs/multidict 6.7.0 through 6.9.0 (C extension) lets a remote client drive unbounded, unreclaimable memory growth by having each operand element leak one key-identity object and one value object. The reflected-union path (
operand | d.items(),multidict_itemsview_or2_impl) and the subtraction path (d.items() - operand,multidict_itemsview_sub1_impl) parse each element into new strong references but release only the tuple wrapper, never the identity and value. Servers in the aio-libs stack build these views over attacker-supplied HTTP headers and query strings, so the operand size is under remote control. Forced garbage collection does not recover the leaked objects, so resident memory rises monotonically until the process is killed.Root Cause
_multidict_itemsview_parse_item()returns new references: a fresh identity viamd_calc_identity()and a fresh value viaPy_NewRef(). Theor2_implfirst parse loop requests both but clears onlyarg:_set_add()builds its own tuple withPyTuple_PackandPy_DECREFs it, so it never borrows the loop'sidentity/value. On the next iteration those variables are overwritten, so the previous references are lost permanently. Thesub1_implfirst loop (:686-696) has the identical omission.This is an editing slip, not an ownership contract: every sibling path clears all three references per iteration,
and1_impl(:304-306),and2_impl(:389-391),or1_impl(:506-508), and the second (md_next) loops of both functions (:605-607,:726-728). PR #1413 added aPy_DECREF(tpl)to the second loop ofor2/sub1, fixing a different temporary-tuple leak; it never touched the first parse loop, so this leak remains onmaster.views.his byte-identical betweenv6.6.0andv6.7.0, yet 6.6.4 does not leak and 6.7.0 does. The regression is behavioural, introduced by a key-identity ownership change inhashtable.hacross that boundary that mademd_calc_identity()return a fresh strong reference the unchanged parse loop never releases.Reproduction Environment
Proof of Concept
POC Source Code
poc_refcount.py — leak proof with intersection control
poc_dispatch.py — maps which paths leak
poc_rss.py — availability consequence
Execution Steps
python3 -m venv venv(CPython 3.10+)../venv/bin/pip install "multidict==6.9.0"(installs the C-extension wheel)../venv/bin/python.Actual Execution Evidence
Version-boundary evidence
Same
poc_dispatch.py, PyPI wheels, identical environment. Last-clean is6.6.4; first-affected is6.7.0:The clean 6.6.x releases return correct set-operation results, so the zero leak reflects correct memory management, not a broken path.
Analysis of Results
The operand holds exactly
Nreferences to one sentinel value; after the union its refcount rises by anotherNand stays there post-GC, so each element leaked one strong reference. Subtraction shows the identical delta. The intersection control, which clearsidentityandvalue, leaks zero; the only code difference is the two missingPy_CLEARcalls, so the leak is caused by that omission and not the harness. RSS climbs from 15.6 MB to 1266.8 MB (~250 MB per 2M elements) despite per-iteration GC, confirming the objects are unreachable by the cyclic collector.Impact
A process that evaluates items-view unions or subtractions over remote-influenced operands leaks one identity plus one value object per element, permanently. In the aio-libs stack multidict backs HTTP headers and query strings, so an attacker who enlarges the operand (for example, many repeated header items compared against a fixed allow/deny set) forces steady, unrecoverable heap growth and can eventually exhaust memory in a long-lived server. This is an availability defect only; results stay correct and no data is exposed.
Reachability depends on the application evaluating
operand | view.items()(reflected union) orview.items() - operand(subtraction) over a sequence of 2-tuples whose count is remote-influenced. The forward unionview.items() | operandroutes toor1_impl, which clears correctly and does not leak; a non-tuple operand element takes theparse_itemearly-return and does not leak. Set algebra over items views is not the most common multidict usage, which bounds exposure and is why this is Medium, not High. This is distinct from PR #1413, which fixed a temporary-tuple leak in the second loop and did not release these per-element objects.Remediation
Recommended fix. Clear the two per-element references at the end of the first parse loop in both functions, exactly as every sibling path does. In
or2_impl(views.h:575) andsub1_impl(views.h:696), replace the lonePy_CLEAR(arg);with:This is an in-repo change to a static internal function; it alters no public API, type, or signature, and asks callers to change nothing. A leak test in the existing
tests/test_leaks.pystyle would lock it in.Workaround. Avoid
operand | view.items()andview.items() - operandon attacker-influenced operands, or install withMULTIDICT_NO_EXTENSIONS=1to use the unaffected pure-Python build.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
aio-libs/multidict (multidict)
v6.9.1Compare Source
=====
(2026-09-21)
Bug fixes
Fixed the C extension reading freed memory on free-threaded builds when a
list handed to :py:meth:
~multidict.MultiDict.update,:py:meth:
~multidict.MultiDict.extend, :py:meth:~multidict.MultiDict.mergeor the :py:class:
~multidict.MultiDictand :py:class:~multidict.CIMultiDictconstructors, a
[key, value]item inside any iterable handed to them, or alist tested with
inagainst :py:meth:~multidict.MultiDict.items, ischanged by another thread; a call that catches the list shrinking under it
now raises :py:exc:
RuntimeError-- by :user:rodrigobnogueira.Related issues and pull requests on GitHub:
:issue:
1437.Fixed a data race on the free-threaded build where a retired hash table's
reader count used relaxed atomics, letting a lock-free
get()/getone()/__getitem__()read race a concurrent free of that table. The reader-exitdecrement and the drain's free check now use release/acquire ordering
instead -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1481.Fixed a free-threaded build bug where two threads calling
update(),merge(), or__setitem__()on the same key at the same time could losethe key entirely instead of just racing on which value wins. A decref of the
replaced value could transiently suspend the writer's critical section,
letting a second writer for the same key observe the first writer's
in-progress entry as absent and, once both settled, mistake it for a stale
duplicate and delete it. Every such decref is now deferred until the writer
has released its critical section, so the window can no longer open.
setdefault()had an unrelated instance of the same blind spot (it couldinsert a duplicate rather than recognizing an in-flight key), fixed alongside
it -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1483.Fixed a free-threaded build bug where
getall()and theitems()/keys()/values()equality path could raiseKeyErroror report apresent, never-deleted key as missing. A concurrent
update()/extend()/__setitem__()call can have its critical section transiently suspended(a decref triggering a blocking allocator call) while an entry is marked as
part of its own bookkeeping; a reader landing in that window used to treat
the mark as "not found" instead of "still there, in flight" -- by
:user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1484.Fixed a reference leak in the C extension where
operand | md.items()and
md.items() - operandleaked one key and one value reference perelement of
operand, letting a large operand grow memory without bound(:gh:
GHSA-54p9-h82j-f925 <aio-libs/multidict/security/advisories/GHSA-54p9-h82j-f925>)-- by :user:
asvetlov.The issue was reported by :user:
waydeshi.Related commits on GitHub:
:commit:
350b4a0.Fixed a segmentation fault on the standard (non-free-threaded) C extension
build when a value type's
__del__released the GIL (for example bycalling
time.sleep()) whileupdate(),merge(),__setitem__(),__delitem__(),pop(),popone(), orpopall()was dropping areplaced or removed value.
Py_BEGIN_CRITICAL_SECTIONcompiles to a no-opon this build, so nothing else was stopping a second thread from mutating the
very same
MultiDictconcurrently once the GIL was released mid-mutation.Every such decref is now deferred until the mutation has fully finished, the
same technique already used to close the analogous free-threaded-build race
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1489.Fixed the C extension reading freed memory while iterating a
:py:class:
~multidict.CIMultiDictwhose keys are plain :py:class:str.Converting such a key to :py:class:
~multidict.istrcould run Python code(a :py:class:
strsubclass's__str__or__del__) or, on free-threadedbuilds, suspend the iterator's critical section, after which the iterator read
the entry again even though a concurrent mutation could already have freed it.
As part of the fix, :py:meth:
~multidict.MultiDict.copyand re-initializingfrom another multidict now assign a new version in the C extension instead of
reusing the source's, matching the pure Python implementation
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1496.Fixed a use-after-free on the free-threaded build where a lock-free
get(),[]orincould read a hash table that a concurrentresize had just retired and another reader was freeing
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1497.Contributor-facing changes
Removed a redundant
includeand a duplicatedexcludeline fromMANIFEST.in; sdist contents are unchanged -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1478.Added
.claudeignorefile -- by :user:asvetlovRelated issues and pull requests on GitHub:
:issue:
1479.Scaled up the pure-Python
pop(),popitem(),__delitem__(),add()and item-insertion benchmarks to do more work per measurement.Repeated CodSpeed runs on the same commit showed these particular
benchmarks flagged as dominated by syscalls, understating their real
cost and adding noise to the reported values; a larger working set
amortizes that overhead -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1485.Replaced deprecated instrumentation codspeed mode with simulation -- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1493.Reorganized the mutating benchmarks (item insertion,
update(),add()of the same key,pop(),popitem(),clear(),__delitem__()and__setitem__()) to copy a fresh multidict andapply the operation in a loop, like the
add()andextend()benchmarks already do. The insertion,
update()andclear()benchmarks previously mutated a single multidict shared across
rounds, so only the first round measured the intended operation; the
rest did a single copy per round, letting per-round overhead dominate
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1494.The
repr()and view inequality benchmarks were updated to repeat theiroperation in a loop, like the other benchmarks, and the CodSpeed benchmark
job was moved to Python 3.14 -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1498.Dropped
-Ifrom the AddressSanitizer test command inAGENTS.mdand in the CI job. It implies
-E, which made Python ignorePYTHONMALLOC=malloc, so small hash tables were still served frompymallocarenas where use-after-free went undetected-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1499.The CI/CD workflow was updated to stop superseded runs of the same pull request
when a new commit is pushed; runs on
master, release branches, tags,the merge queue, and the daily schedule are never interrupted
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1500.The release job was changed to upload distributions and their signatures
to the GitHub Release one file at a time, skipping assets that were
already attached and retrying after a pause, so that a parallel upload
burst no longer tripped the GitHub secondary rate limit
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1503.Miscellaneous internal changes
Corrected several comments in the free-threaded C extension that
attributed critical-section suspension to a blocking
PyMem_Malloc()call; allocation alone never suspends an acquired critical section, and
the real risk at those sites is a decref running
a finalizer or weakref callback. Also dropped a retry loop in
md_clone_from_ht()that guarded against the same, non-existentallocation-triggered suspension -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1486.Changed the free-threaded build's deferred decref buffer, used by
update()and__setitem__(), to a chain of fixed-size blockswith a large inline first block instead of a small inline array that
was reallocated on growth -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1501.v6.9.0Compare Source
==========
(2026-09-18)
Bug fixes
Protected
repr()ofMultiDict,MultiDictProxy, and their viewsin the C extension with a critical section, avoiding data races on the
free-threaded build of CPython -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1431.Guarded
repr()ofMultiDictProxyin the C extension and ofKeysViewin both the C extension and the pure-Pythonimplementation against infinite recursion on self-referential
containers, matching the existing guard on
MultiDict,ItemsView, andValuesView-- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1431.Protected
MultiDict.update(),.extend(),.merge(),.clear(),.copy(), and theMultiDict/CIMultiDictconstructors in the Cextension with a critical section, using the two-object form when a
second multidict, multidict proxy, or plain
dictinstance isinvolved, avoiding data races and a segmentation fault on the
free-threaded build of CPython.
.clear()now also publishes theempty table before releasing any entry's references, so a concurrent
caller can never observe a partially-cleared multidict even if releasing
a value runs arbitrary Python code that suspends the held critical
section -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1433.Protected
MultiDict.add(),__setitem__/__delitem__,get()/getone()/__getitem__,__contains__,getall(),setdefault(),pop()/popone()/popall()/popitem(),__eq__, iteration, and the&/|/-/^/in/isdisjoint()operations on.keys()and.items()views in theC extension with a critical section, avoiding data races and
use-after-free crashes on the free-threaded build of CPython
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1438.Fixed a data race on the free-threaded build where
MultiDictObject.usedwas written non-atomically while
len()read it with a relaxed atomicload -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1441.Fixed several pre-existing use-after-free races on the free-threaded build:
_md_resize()and
md_clone_from_ht()could allocate a new hash table, have theircritical section transiently suspended during that allocation, and then use
a keys-table pointer or size a concurrent operation had already invalidated;
_md_del_at()(plaindel/pop()) could likewise leaveselfinan inconsistent state across a suspending decref -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1441.Fixed a data race on the free-threaded build where
md->keyswaspublished with a plain store while lock-free readers loaded it
atomically, and a false-negative race in
get()/__contains__where a hash temporarily marked by a concurrent replace could make a
present key look absent -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1441.Fixed several more pre-existing use-after-free and data-corruption races on
the free-threaded build, this time in the
__setitem__()/update()/extend()/merge()replace path: a decref suspending the criticalsection mid-replace could leave a stale table pointer in use, misplace a
temporarily-marked entry during a concurrent resize, or let one thread's
duplicate-tracking mark get overwritten by an unrelated key's entry sharing
the same hash bucket -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1441.Fixed an unbounded memory leak on the free-threaded build where a retired
hash table could sit on
md->retiredfor the rest of the object'slifetime under sustained concurrent read traffic instead of being freed
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1443.Fixed a data race on the free-threaded build of the C extension where
state->global_version, the version counter shared by everymultidict instance and used to derive
getversion(), was bumped witha plain increment instead of an atomic op. Two threads mutating
different instances at the same time could step on each other's update
and hand out a duplicate (or non-monotonic) version number
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1444.Fixed a data race on the free-threaded build of the C extension where
getall()/popall()andupdate()/extend()/merge()temporarily marked and unmarked a matching entry's hash with a plain,
non-atomic store, while a lock-free
__contains__/get()on anotherthread could load that same field with an atomic operation and no lock at
all -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1448.Fixed a data race in the pure-Python
MultiDict/CIMultiDictfallback where concurrent mutating calls (
add(),__setitem__,__delitem__,setdefault(),pop()/popone(),popall(),popitem(),update(),extend(),merge(),clear(), and re-__init__) could corrupt the shared hash table,or silently drop a concurrent write, on a regular, GIL-enabled
interpreter. Pure-Python bytecode is not atomic under the GIL, so two
threads could interleave mid hash-table insert or deletion, leaving
the index table pointing at stale or out-of-range entries and causing
an infinite probe loop, an
AttributeError, or a lost mutation.Each
MultiDict/CIMultiDictinstance now holds its own lock forthe duration of these operations, with two-object operations locking
both instances in a fixed order to avoid deadlock. The C extension
already serializes these operations with a critical section and was
not affected
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1449.Fixed a data race on the free-threaded build of the C extension where
to_dict()cleared every entry's temporary "seen" mark with a plain,non-atomic store, while a lock-free
__contains__/get()on anotherthread could load that same field with an atomic operation and no lock at
all -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1450.Fixed a data race on the free-threaded build of the C extension where
update()/extend()/merge()readself's module state withoutholding
self's lock, while a concurrent__init__()on the same,already-published multidict could rewrite that same field under lock.
Rather than routing every read through a lock-free lookup, the field is
now written only once, at object construction, and never touched again by
__init__(), so it is safe to read unlocked anywhere -- by:user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1452.Fixed
items()set algebra (&,|,-,in,isdisjoint()) so it nolonger corrupts or hangs when a compared value's
__eq__()re-enters thesame :class:
~multidict.MultiDict, for example by callinggetall()onit. The comparison now runs only after the internal hash-chain walk has
been fully materialized and restored, so the callback can no longer
observe entries the walk still has marked -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1453.Fixed a data race on the free-threaded build of the C extension where
popall()/popone()/__delitem__/add()rewrote a hash tableindex slot with a plain, non-atomic store, while a lock-free
get()/getone()/__getitem__/__contains__on another threadwalked that same index array with a plain, non-atomic load and no lock at
all -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1454.Stopped folding the case of :class:
~multidict.istrkeys in thecase-sensitive :class:
~multidict.MultiDict, so both the C and the purePython implementations now keep such a key exactly as given
-- by :user:
youdie006.Related issues and pull requests on GitHub:
:issue:
1457.Fixed the C extension's
update()(andmerge(), which shares the samecode path) silently failing to invalidate an in-progress
keys()/items()/values()iterator when the call only overwrote analready-present key's value in place. Adding a new key already invalidated
iterators correctly; only the in-place overwrite branch was missing the
version bump -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1458.Fixed the pure-Python
MultiDict/CIMultiDictiterator guard so thatmutating the mapping before the first
next()call on anitems(),keys(), orvalues()iterator (or oniter(md)) now reliably raisesRuntimeError, matching the C extension. Previously a mutation thathappened before the iterator was ever advanced, such as
clear()or adel/popone()that removed the only remaining entry, could make theiterator silently raise
StopIterationinstead -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1459.Fixed a use-after-free race on the free-threaded build of the C extension
where a retired hash table could be freed while a lock-free
get()/getone()/__getitem__/__contains__reader on anotherthread was still walking it: the coarse "no reader in flight" gate
reaching zero did not reliably mean every such reader had also reached
its own per-table exit, so a table whose own reader count is still
nonzero is now deferred for a later retry instead of freed
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1461.Features
Added a :meth:
~multidict.MultiDict.to_dictmethod returning a plain:class:
dictthat maps every key to the list of all its values. Unlikedict(md), which keeps only the first value per key, and unlike a{k: md.getall(k) for k in md}comprehension, which emits one entry perspelling of a case-insensitive key, it groups by key identity
-- by :user:
rodrigobnogueira.Related issues and pull requests on GitHub:
:issue:
783.MultiDict,CIMultiDict,MultiDictProxyandCIMultiDictProxyin theC extension implemented the vectorcall calling convention for construction.
This sped up
MultiDict(...)-style calls by avoiding an intermediatepositional-arguments tuple and keyword-arguments dictionary in the common case; the gained burst is ~10.
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1436.Made
MultiDict.__contains__(),.get(),.getone()and__getitem__()lock-free on CPython 3.14+'s free-threaded build, insteadof taking a critical section like the rest of the C extension's API (on
3.13, which lacks the public API these need to safely read an entry
concurrently, they still take the critical section, exactly as before). A
resize/shrink/clear no longer frees the outgoing hash table immediately; it
is deferred until no lock-free reader could still be walking it -- by
:user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1441.Made the pure-Python
MultiDict/CIMultiDictimplementation safe touse from multiple threads under a free-threaded (no-GIL) build. Every
method that touches an instance's hash table now takes that instance's own
lock, two-object operations (
update(),extend(),merge(),__eq__(), copying) lock both objects in a fixed, deadlock-safe order,and the version counter shared by every instance is guarded separately. On
a regular (GIL-enabled) interpreter this adds no overhead: the locked
methods are the exact same function objects as before -- by
:user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1447.Sped up adding many values for the same key to a
:class:
~multidict.MultiDictor :class:~multidict.CIMultiDict, eachadded value no longer gets slower than the one before it
-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1466.Improved documentation
Added the plural form "fallbacks" to the docs spell checker's allowed
word list so :file:
CHANGES.rstbuilds cleanly -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1439.Contributor-facing changes
Added race tests that iterate and extend a :class:
~multidict.MultiDictfrom several threads on a free-threaded build, so a regression in the C
extension's locking fails a free-threaded CI leg instead of going unnoticed
-- by :user:
rodrigobnogueira.Related issues and pull requests on GitHub:
:issue:
1317.Added
AddressSanitizer/UndefinedBehaviorSanitizerandThreadSanitizerCI jobs. The former runs the suite under aregular CPython with
MULTIDICT_ASAN_BUILD=1; the latter builds afree-threaded CPython instrumented with
--with-thread-sanitizerand runs the suite against it via
MULTIDICT_TSAN_BUILD=1-- by:user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1455.Added Hypothesis-based property and stateful fuzz tests covering
:class:
~multidict.MultiDict/:class:~multidict.CIMultiDictsemantics,views, iterators, and threaded stress scenarios, run against both the
C-extension and pure-Python backends -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1460.Added core dump collection to the AddressSanitizer and ThreadSanitizer
CI jobs: a crash now uploads the core file alongside the crashing
interpreter binary and the compiled extension as a downloadable
artifact, for offline debugging with a matching symbol-carrying binary
instead of raw hex offsets -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1462.Added benchmarks for adding many values for the same key and for
creating a multidict with many items -- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1465.Skipped abstractmethods from coverage leaks report, they are empty placeholders that are never executed -- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1470.Add benchmarks for
__setitem__-- by :user:asvetlovRelated issues and pull requests on GitHub:
:issue:
1471.The AddressSanitizer and ThreadSanitizer CI jobs were changed to run pytest
with
--capture=no. Previously a sanitizer abort could exit the processbefore pytest flushed its per-test output buffer, silently dropping the
sanitizer report from the job log -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1472.Moved the
hypothesispin out ofrequirements/pytest.txtinto its ownrequirements/pytest-hypothesis.txt, installed only by the dedicatedHypothesis CI jobs.
hypothesisnow ships a Rust extension with noprebuilt wheel for some emulated architectures (e.g. musllinux i686),
which broke wheel-testing jobs that never run the Hypothesis-marked tests
in the first place -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1475.Miscellaneous internal changes
Replaced
PyIter_Next()calls with the newerPyIter_NextItem()API inthe C extension, so the iterator-exhausted and error cases are told apart by
the return code instead of an ambiguous
NULLresult -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1440.v6.8.0Compare Source
=====
(2026-09-09)
Bug fixes
A segmentation fault that could be triggered when getting an item is now fixed
-- by :user:
Vizonex.Related issues and pull requests on GitHub:
:issue:
1310.Fixed reference leak in iterators, views and
istr-- by :user:
Vizonex.Related issues and pull requests on GitHub:
:issue:
1311.Fixed the pure-Python :class:
~multidict.MultiDictconstructor and:py:meth:
~multidict.MultiDict.extend,:py:meth:
~multidict.MultiDict.update, and:py:meth:
~multidict.MultiDict.mergemethods over-allocating theirinternal hash table when called with both a positional argument and
keyword arguments, because keyword arguments were counted twice in the
size estimate -- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1338.Fixed
__repr__of :class:~multidict.MultiDict,:class:
~multidict.CIMultiDict, their proxies, and the keys/items viewsproducing invalid output when keys contained quote characters --
keys are now formatted with :func:
reprso the result is a valid Pythonstring literal -- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1342.Fixed a segfault when calling :py:meth:
~multidict.MultiDict.addwith only one of its two required arguments supplied by keyword, e.g.d.add(key="k"). Extra keyword arguments passed to the lookup and removal methods are also now rejected with :exc:TypeErrorinstead of being silently ignored.-- by :user:
devdanzinRelated issues and pull requests on GitHub:
:issue:
1376.Fixed a segfault when constructing a multidict iterator type directly, e.g.
type(iter(md.keys())).__new__(...). Such an iterator had a NULL internal pointer thatnext()dereferenced. The iterator types now forbid direct instantiation, the same way the view types were fixed in :issue:1163.-- by :user:
devdanzinRelated issues and pull requests on GitHub:
:issue:
1377.Fixed a segfault when using a :py:class:
~multidict.MultiDictor :py:class:~multidict.CIMultiDictcreated via__new__without calling__init__(for example a subclass that does not callsuper().__init__()). The internal state was left as NULL pointers that the first method call dereferenced.tp_newnow initializes the object to a valid empty mapping.-- by :user:
devdanzinRelated issues and pull requests on GitHub:
:issue:
1378.Fixed two crashes in the C extension caused by holding a raw pointer into a hash table across an operation that could reshape it. Updating a multidict from itself (e.g.
d.extend(d)) freed the very table being iterated -- a use-after-free;extend(self)now doubles the contents andupdate(self)/merge(self)are no-ops. Building a multidict from a list of pairs whose case-insensitive key.lower()shrinks that list read past the end of the list; the length is now re-checked on every iteration.-- by :user:
devdanzinRelated issues and pull requests on GitHub:
:issue:
1379.Fixed three reference/resource leaks on error paths in the C extension: the items-view
__contains__leaked the first element of a candidate pair when reading the second one raised;__repr__leaked itsPyUnicodeWriterwhen the multidict was mutated during iteration; and the internal iteration helper leaked the identity reference when key materialization failed under low memory.-- by :user:
devdanzinRelated issues and pull requests on GitHub:
:issue:
1381.Stopped several feature-detection fallbacks in the C extension from swallowing every exception. When probing an argument (
arg.items()/arg.keys()) or measuring it (len(other)) fails, the code now clears only the expected :exc:TypeError/:exc:AttributeErrorand lets everything else -- notably :exc:MemoryErrorand :exc:KeyboardInterrupt-- propagate, matching the already-correct sites elsewhere in the module.-- by :user:
devdanzinRelated issues and pull requests on GitHub:
:issue:
1382.Fixed a segmentation fault when extending a multidict with itself
-- by :user:
cananooand :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1398.Fixed a memory leak when
MultiDictandCIMultiDictinstances areinitialized more than once -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1412.Fixed a reference leak in items-view set operations
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1413.Fixed a memory leak when destroying C-extension multidict instances
and proxies -- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1415.Fixed a reference leak from repeated
__init__calls onMultiDictProxyandCIMultiDictProxyinstances -- by :user:asvetlov.Related issues and pull requests on GitHub:
:issue:
1419.Fixed missing decref for :data:
Noneon error in :meth:~multidict.MultiDict.setdefault-- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1421.Features
Added :py:func:
reversedsupport to the keys, values, and items views of:class:
~multidict.MultiDict, :class:~multidict.CIMultiDict, and theirproxies in both the C-extension and pure-Python implementations
-- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
448.Optimized key identity comparison for C Extension.
Now it uses fast path that is equal to :c:func:
PyUnicode_EqualfromPython 3.14+ but without redundant type checks. It gives ~15% speed-up on benchmarks with many key comparisons.
-- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1406.Changed both the pure-Python and C implementations to mark a temporarily
removed hash table entry by setting the high bit of its hash instead of
overwriting it with a sentinel value, so restoring the entry no longer
recomputes the hash; :meth:
~multidict.MultiDict.getallis faster ~10% now in C version-- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1426.Removals and backward incompatible breaking changes
Dropped support for Python 3.9 as it has reached end of life.
Related issues and pull requests on GitHub:
:issue:
1316.Dropped support for free-threaded Python 3.13 -- by :user:
ngoldbaum.Related issues and pull requests on GitHub:
:issue:
1326.Improved documentation
Fixed
CIMultiDictProxydocumentation to state it inherits fromMultiDictProxy(notMultiDict), and fixed missing word inistrsection -- by :user:veeceey.Related issues and pull requests on GitHub:
:issue:
1298.Clarified that
istrpreserves the original casing andcompares as a regular
str; case-insensitive matching ishandled by
CIMultiDict-- by :user:gyanu2507.Related issues and pull requests on GitHub:
:issue:
1397.Fixed broken RST markup in
items()docstrings-- by :user:
veeceey.Related issues and pull requests on GitHub:
:issue:
1299.Packaging updates and notes for downstreams
Dropped support for free-threaded Python 3.13 -- by :user:
ngoldbaum.Related issues and pull requests on GitHub:
:issue:
1326.Wheels for iOS and Android (CPython 3.13+) are now published on
release-tag builds, so downstreams on those platforms no longer need a
local compiler to install
multidict; these wheels are build-verifiedonly, since running the test suite on mobile needs a simulator or an
emulator -- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1337.Added support for building and shipping riscv64 wheels
-- by :user:
justeph.Related issues and pull requests on GitHub:
:issue:
1293.The
setuptoolsbuild dependency lower bound has been restored to be>= 47after an incorrect automated increase in :pr:1315-- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1400.Contributor-facing changes
Added support for collecting code coverage of isolated multidict tests
-- by :user:
Vizonex.Related issues and pull requests on GitHub:
:issue:
1314.Switched to
mirrors-clang-formatand enabled clang-format inpre-commit.ci-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1318.Added a release-tag-gated CI job that cross-compiles the C extension
for iOS and Android through
cibuildwheel-- by :user:aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1337.Added an
AGENTS.mdorientation file at the repository root, covering thepull request template, the
CHANGES/news fragment conventions, thedraft-PR / human-review workflow, and the dual pure-Python and C-extension
parity rule, so LLM contributors land changes that match project style
-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1339.Added
init,installable,instantiation,parametrization,parametrized,postfix, andunparseableto the docs spelling listso news fragments and docs can use these words without failing the spell
check build -- by :user:
pctablet505.Related issues and pull requests on GitHub:
:issue:
1343.Documented three common agent failure modes in
:file:
AGENTS.md: the docs spell check(
make doc-spelling) catches unknown words in news fragmentsbefore CI does; coverage runs over the test tree too, so
unreachable defensive
raiseguards and one-sided cleanupbranches in tests will surface as uncovered on the codecov
patch report; and branch creation is restricted on
aio-libs/multidict, so PRs must be pushed from a fork-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1343, :issue:1345.Trimmed the CI test matrix to drop redundant
Py_DEBUGjobs onmacOS and Windows and to gate the
windows-11-armwheel build torelease-time only, cutting CI wall-clock by roughly a third without
reducing code coverage -- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1346.Added a
CLAUDE.mdat the repository root that imports:file:
AGENTS.mdvia Claude Code's@-syntax, so theproject's LLM contributor rules load automatically when
working in Claude Code
-- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1347.Enabled the
I(import sorting) rule in[tool.ruff.lint]soruff checkcovers import order on topof the existing
UP(pyupgrade) group, and updated theruff-checkpre-commit hook to run with--fix --exit-non-zero-on-fix --show-fixesso import-orderfixes apply on commit. Sorted imports in four
tests/filesto match the new rule -- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1348.Enabled the
PLC0415(import-outside-top-level) rule in[tool.ruff.lint]. Function-scoped imports must now opt inwith
# noqa: PLC0415plus a comment explaining the reason(typically avoiding a heavy optional dependency at import time).
This catches a pattern that LLM contributors frequently introduce.
There are currently no opt-outs in the tree, so the rule is
enforced everywhere
ruff checkruns-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1349.Switched the
cibuildwheelbuild frontend tobuild[uv]sothat
uvprovisions every build and test virtual environmentin the wheel matrix. Test-dependency installation in particular
drops from a multi-second
pip installper ABI to a roughlysub-second
uvresolve-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1350.Overrode
CIBW_BUILD_FRONTEND=buildfor the odd-arch wheelmatrix; the upstream
manylinux/musllinuximages forthose arches do not ship
uv-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1352.Allowed re-running the deploy job after a partial release failure: the
Make Releasestep now skips when the GitHub Release already exists,and the PyPI publish step uses
skip-existingso dists that werealready uploaded on a prior attempt do not break the retry
-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1353.Switched the aarch64 and armv7l wheel builds to GitHub's native ARM
runners. The aarch64 wheels now build without QEMU emulation, and
armv7l runs on aarch64 hosts so its 32-bit ARM execution is far
cheaper than the previous aarch64-on-x86_64 path
-- by :user:
bdraco.Related issues and pull requests on GitHub:
:issue:
1354.Documented design principles for pure-Python :class:
~multidict.istrimplementation-- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1360.Pinned
coverageto thectracemeasurement core so the test suite runson Python 3.14. Coverage picks
sysmonthere, which cannot record thedynamic contexts
pytest-covswitches at runtime, and the resulting warningbecame an error under the suite's
filterwarningssetting-- by :user:
rodrigobnogueira.Related issues and pull requests on GitHub:
:issue:
1393.Dependabot has been restricted to the
requirementssubdirectory toavoid unintended updates outside dependency requirement files
-- by :user:
aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1400.Enabled automatic upgrades from ruff after each python version that is dropped
-- by :user:
Vizonex.Related issues and pull requests on GitHub:
:issue:
1325.Miscellaneous internal changes
Explicitly marked
empty_htkeysasconst.Moved the structure to
.rodatalinker section.Unexpected modification of the structure will lead to segfault instead
of corrupting the data and fail fast.
.rodatais mount as read-only-mapped, it is shared well acrossmultiple threads without cache misses.
The change is pretty trivial, it is made for the sake of correctness,
not for speedup.
-- by :user:
asvetlov.Related issues and pull requests on GitHub:
:issue:
1405.Slightly reorganized multidict creating process.
md_init()creates an empty multidict.tp_allocslot is used in object cloning instead of bare :c:func:PyType_GenericNewcall.-- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1420.Dropped dead code,
_md_del_at()and_md_del_at_for_upd()never fails-- by :user:
asvetlovRelated issues and pull requests on GitHub:
:issue:
1423.Fixed a missing space in the :exc:
ValueErrormessageraised when constructing a multidict from a sequence
-- by :user:
veeceey.Related issues and pull requests on GitHub:
:issue:
1295.Renamed the benchmark script from
benchmarks/becnhmark.pytobenchmarks/benchmark.pyso it matches the invocation documented in:doc:
benchmark-- by :user:aiolibsbot.Related issues and pull requests on GitHub:
:issue:
1335.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.