head 1.3; access; symbols pkgsrc-2026Q3:1.1.0.2 pkgsrc-2026Q3-base:1.1; locks; strict; comment @# @; 1.3 date 2026.09.28.15.45.34; author wiz; state Exp; branches; next 1.2; commitid KyscVow3vaTt5pXG; 1.2 date 2026.09.26.10.18.39; author wiz; state Exp; branches; next 1.1; commitid 15bKz7TnriOcl7XG; 1.1 date 2026.09.10.11.32.24; author adam; state Exp; branches; next ; commitid neYAT3Ang9ayg4VG; desc @@ 1.3 log @py-multidict: update to 7.0.0. 7.0.0 ===== *(2026-09-26)* 7.0.0 is a major release. It makes :class:`~multidict.istr` final, adds a public C API for third-party extensions, and makes the C extension substantially faster across the board. **Breaking change.** :class:`~multidict.istr` can no longer be subclassed, on either backend. Code that subclassed it has to wrap or convert instead. The C extension also started rejecting an argument passed both positionally and by name with :exc:`TypeError`, where it used to silently misbehave. **Public C and Cython API.** Other C extensions and Cython modules can now create, read and mutate multidicts through a capsule, without going through the Python-level API. The capsule comes with ``multidict_capi.h``, a ``cimport``-able ``multidict/__init__.pxd`` and :func:`multidict.get_include`. The API includes ``MultiDict_ForEach()`` and a watchers API modeled on CPython's dict watchers. See the :doc:`C API ` and :doc:`Cython API ` references. **Performance.** On CodSpeed's GIL-build benchmarks against 6.9.1, 82 C-extension benchmarks got faster and none got slower. Rough figures: - :class:`~multidict.CIMultiDict` keyed by plain :class:`str`: construction, ``add()``, ``extend()``, ``update()`` and item assignment got 2 to 3.8 times faster, and lookups about 2 times faster. - :class:`~multidict.CIMultiDict` keyed by :class:`~multidict.istr`, and case-sensitive :class:`~multidict.MultiDict`: lookups, insertion and deletion got 10% to 70% faster. - :meth:`~multidict.MultiDict.getall` and iterating :meth:`~multidict.MultiDict.items` got about 35% faster, and the view set operations 10% to 85% faster. - :meth:`~multidict.MultiDict.popitem` on a whole mapping went from quadratic to linear time. - On the free-threaded build, measured in instructions against 6.9.1, lookups got 24% to 33% cheaper on :class:`~multidict.MultiDict` and 4 to 5 times cheaper on :class:`~multidict.CIMultiDict` with lowercase :class:`str` keys, and item assignment 15% to 19% cheaper. Construction and deletion on :class:`~multidict.MultiDict` cost 2% to 7% more. With several threads mutating at once, a ``d[key] = value`` got about four times faster. The pure-Python backend is unchanged in speed. **Robustness.** This release fixed several crashes and leaks in the C extension, including use-after-free bugs when a key's ``lower()`` or a value's ``__eq__`` mutated a multidict, a crash at interpreter shutdown, and table and reference leaks on the free-threaded build. Bug fixes --------- - Fixed a segmentation fault in the C extension when a finalizer let the same multidict be resized while ``del md[key]`` or :meth:`~multidict.MultiDict.popall` was still scanning the table, and a spurious :exc:`RuntimeError` from :meth:`~multidict.MultiDict.getall` when a garbage collection triggered while building its result mutated the multidict -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1492`, :issue:`1512`. - Fixed a reference leak of the key and value in the C extension when growing the hash table fails with :exc:`MemoryError` during :meth:`~multidict.MultiDict.add`, :meth:`~multidict.MultiDict.update`, :meth:`~multidict.MultiDict.merge`, :meth:`~multidict.MultiDict.setdefault` or item assignment -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1507`. - Fixed a data race on the free-threaded build of the C extension where a ``MultiDict``'s own version counter was read by :func:`~multidict.getversion` and written on every mutation with a plain, non-atomic load and store. Both sides switched to a relaxed atomic load/store, matching the pattern already used for the object's item count -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1514`. - Fixed the C extension discarding a :exc:`MemoryError` or :exc:`KeyboardInterrupt` raised by a positional argument's ``__len__`` while the size of an update was estimated. ``extend()``, ``update()``, ``merge()`` and subclass construction started propagating it, matching the pure-Python backend; only the :exc:`TypeError` from an unusable ``__length_hint__`` stayed ignored -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1517`. - Ensured that canonical keys in pure-Python :class:`~multidict.MultiDict` and :class:`~multidict.CIMultiDict` are converted to exact :class:`str` instances without invoking :meth:`object.__str__` overrides, matching the C extension -- by :user:`agustin18`. *Related issues and pull requests on GitHub:* :issue:`1535`. - Fixed the argument counts reported by the C extension when a method is called with too many positional arguments; methods with an optional second argument claimed to take exactly one, and methods requiring two claimed to take a range. A method called with none of its two required arguments now reports both as missing -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1545`. - Fixed the C extension accepting an argument passed both positionally and by name instead of raising :exc:`TypeError`. ``md.add("a", key="k", value="v")`` stored a wrong value, and ``md.get("a", "b", key="c")`` silently ignored the surplus arguments -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1545`. - Fixed a memory leak in the free-threaded build of the C extension. :meth:`~multidict.MultiDict.clear` hands its hash table to a drain that frees it once no lock-free reader is in flight, and a drain that finds one leaves the table for the next drain to free. Deallocation ran no drain of its own, so a multidict dropped after such a clear, with no operation in between, never freed that table nor released the references its entries still held. Builds with the GIL enabled emit byte-identical code and are unaffected -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1555`, :issue:`1562`. - Fixed a crash during interpreter shutdown in the C extension: a multidict started keeping a reference to its own module, so the module state it reads while being torn down could not be freed first -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1563`. - Fixed retired hash tables lingering in the free-threaded build of the C extension. A drain that finds a lock-free reader in flight puts the tables it holds back for the next drain to run, and the reader it saw could already have looked, so nothing was left to free them: they stayed until the multidict was next used, and if it was dropped instead, the references their entries held were never released. Builds with the GIL enabled emit byte-identical code and are unaffected -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1565`. - Fixed reference cycles through a multidict going uncollected in the free-threaded build of the C extension. A hash table waiting to be freed still owns the references of the entries left in it, and those were not reported to the garbage collector, which then read the values as reachable from outside the cycle and kept it alive. Builds with the GIL enabled emit byte-identical code and are unaffected -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1566`. - Fixed ``extend()``, ``update()`` and ``merge()`` of the C extension raising :exc:`SystemError` when given an object whose ``__length_hint__()`` reported close to :data:`sys.maxsize` together with keyword arguments, and overflowing the table size computation for such a hint; a hint too large to reserve for is ignored, as :meth:`list.extend` does -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1577`. - Fixed a use-after-free in the C extension's ``MultiDict.__eq__`` when a value's ``__eq__`` mutated either multidict; comparing exact :class:`str` values also became faster -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1579`. - Fixed a use-after-free in the C extension when a :class:`str` subclass key's ``lower()`` mutated the source while a :class:`~multidict.CIMultiDict` was built from, extended, updated or merged with a :class:`dict` or a multidict of the other case sensitivity. The extra references this takes are limited to the keys whose ``lower()`` can run Python code, so other updates do not pay for them -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1581`, :issue:`1598`. Features -------- - Added a public C and Cython API for third-party extensions. Its ``MultiDict_ForEach()`` family hands a visitor each entry's key, value, identity (the canonical key the mapping looks entries up by, lower-cased on a :class:`~multidict.CIMultiDict`) and the identity's hash -- by :user:`Vizonex` and :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1478`. - Changed the C extension to track the entries that :meth:`~multidict.MultiDict.getall`, item assignment, :meth:`~multidict.MultiDict.update`, :meth:`~multidict.MultiDict.merge` and :meth:`~multidict.MultiDict.to_dict` have visited in a bitmap private to the call, instead of temporarily marking entry hashes in the table itself. Concurrent readers on free-threaded builds no longer have to tolerate marked entries. Item assignment, :meth:`~multidict.MultiDict.to_dict` and lookups of keys with many values got faster, while :meth:`~multidict.MultiDict.update` with many repeated keys got somewhat slower -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1505`. - Replaced the C extension's per-key finder object with a single callback-driven walk in a new ``_multilib/walk.h`` header. The entries a walk has already reported were tracked by the walk itself rather than by every caller, and the walk kept its position in registers instead of reloading it from a struct on each step, so :meth:`~multidict.MultiDict.getall` and the items and keys view set operations got faster on keys with many values -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1522`. - Sped up ``CIMultiDict`` operations keyed by a plain ``str`` in the C extension. An all-ASCII key was lowered directly instead of through a ``str.lower()`` call, and a key that is already lowercase became its own identity, which saved both a copy and a rehash. Looking up a lowercase key got about three times faster and inserting one about twice as fast, keys that do carry uppercase gained 20% to 35%, and bulk operations on already-lowercase keys, such as :meth:`~multidict.MultiDict.extend` and construction from a :class:`dict`, got up to three times faster -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1527`. - Sped up building an ``istr`` in the C extension. The class gained its own vectorcall, so the one-argument form stopped packing its argument into a tuple for :c:func:`!type_call` only to unpack it again with :c:func:`PyArg_ParseTupleAndKeywords`. Building an ``istr`` from a :class:`str` got about 19% faster, and building one from an ``istr``, which hands back the original, about three times faster. In exchange :meth:`~multidict.MultiDict.getall` costs nine more instructions per call, since the added code moves where the compiler draws its inlining line -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1537`. - Sped up building and dropping multidicts in the C extension by reusing freed hash tables instead of returning them to the allocator. Small tables are kept in a bounded per-size-class pool held in the module state, the way CPython reuses key objects for :class:`dict`. Only on builds with the GIL: a shared pool needs an atomic exchange to take a table and another to put one back, which measured slower than the per-thread allocator a free-threaded build already has -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1539`. - Sped up :meth:`~multidict.MultiDict.copy`, constructing a multidict from another one, and growing one past its current size in the C extension, by not zeroing the parts of a hash table that are about to be overwritten. A copied table is overwritten in full, and a resized one has the entries it carries over written straight onto it -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1540`. - Sped up :meth:`~multidict.MultiDict.items`, :meth:`~multidict.MultiDict.keys`, :meth:`~multidict.MultiDict.values` and iterating a multidict in the C extension, by reusing the view and iterator objects they allocate instead of returning them to the allocator -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1541`. - Sped up constructing a :class:`~multidict.MultiDict`, :class:`~multidict.CIMultiDict`, :class:`~multidict.MultiDictProxy` or :class:`~multidict.CIMultiDictProxy` in the C extension, by reusing the object itself as well as its hash table. Subclasses are unaffected and keep allocating as before -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1542`. - Sped up :meth:`~multidict.MultiDict.getall` in the C extension, by shrinking the scratch buffer the walk carries on the stack from 4 KB to 1 KB. The buffer was large enough to stop the compiler inlining the walk into its callers, which cost more than it saved -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1543`. - Sped up lock-free reads on the free-threaded build of the C extension. Every reader that leaves a :class:`~multidict.MultiDict` last used to call into the retired-table drain, whose first act is a sequentially consistent read-modify-write, even though the retired list is empty almost every time; a plain load took over that case and the drain itself moved out of line. ``key in md`` got about 10% cheaper, ``md[key]`` about 8% and a missing ``md.get(key)`` about 5%, measured as instruction counts on CPython 3.14. Builds with the GIL enabled emit byte-identical code and are unaffected -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1546`. - Added a watchers C API, so another C extension can be notified when a :class:`~multidict.MultiDict` or :class:`~multidict.CIMultiDict` changes. It follows CPython's :c:func:`!PyDict_AddWatcher` family, with two differences: the callback is passed two context pointers, one fixed per registered callback and one per watched multidict, and events are delivered once the operation that produced them has finished rather than mid-mutation. The events are multi-value aware, and each one that concerns a key carries the identity and its hash alongside it. Up to :c:macro:`MULTIDICT_MAX_WATCHERS` (32) watchers can be registered, and a failing callback is reported through :func:`sys.unraisablehook` with the event and the multidict's type and address, as CPython does for dict watchers -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1551`. - Sped up lookups, insertions and deletions in the C extension by always inlining the hash-table probe's initializer. Left to itself the compiler emitted it out of line, so every probe opened with a call for five stores. Free-threaded builds already inlined it and are unchanged -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1554`. - Sped up every method call on :class:`~multidict.CIMultiDict` and :class:`~multidict.CIMultiDictProxy` in the C extension by binding their method descriptors to the class itself instead of inheriting them from :class:`~multidict.MultiDict` and :class:`~multidict.MultiDictProxy`; CPython only specializes a method call when the descriptor belongs to the exact type of the receiver, so every such call was taking the generic path -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1567`. - Made :meth:`~multidict.MultiDict.popitem` in the C extension drop the trailing deleted entries it walked over, so that popping every item out of a mapping costs linear time rather than quadratic; the pure-Python implementation already did this -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1568`. - Sped up iterating over :meth:`~multidict.MultiDict.items` in the C extension by handing out the same ``(key, value)`` tuple again when the caller has already let go of it, as ``for key, value in d.items()`` does on every step; a tuple the caller keeps is left alone and a new one is used from then on -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1569`. - Sped up lookups on the free-threaded build by comparing the hash before touching an entry's key and by matching a key that is the very same object as the one looked up without taking a reference to it, so the common hit costs two fewer atomic operations -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1570`. - Sped up mutations on the free-threaded build when several threads mutated multidicts at the same time: each thread reserved version numbers in batches instead of bumping one counter shared by every thread on each mutation, so with six threads a ``d[key] = value`` was about four times faster -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1587`, :issue:`1599`. - Sped up the C extension by keeping the table resize on the insert path and the operations behind Python slots out of line, so they stopped crowding the hot paths out of the compiler's inlining budget. On GIL builds, building a multidict from 200 pairs got about 4% faster in instruction count and inserting a new key about 2%; on free-threaded builds, key lookups got up to 5% faster -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1600`, :issue:`1601`. Removals and backward incompatible breaking changes --------------------------------------------------- - Made :class:`~multidict.istr` a final class to disallow subclassing in both the C extension and pure-Python implementations -- by :user:`agustin18`. *Related issues and pull requests on GitHub:* :issue:`1535`. Improved documentation ---------------------- - Documented how :class:`~multidict.MultiDict` and :class:`~multidict.CIMultiDict` compare with :class:`dict` on both the default and the free-threaded builds, with per-operation measurements, and how to run the benchmarks in a stable environment -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1532`. - Documented what :class:`~multidict.istr` keys are worth on a :class:`~multidict.CIMultiDict`, as instruction counts next to the equivalent plain :class:`str` keys. The advice to create them once and reuse them was already there; the numbers behind it were not -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1547`. - Documented that a few rows of the benchmarking reference are measured on an empty or 20-item mapping rather than the 200-item one the section describes -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1558`. - Mentioned in the README and docs index that ``multidict`` is optimized for both the GIL and free-threaded builds, and that its performance is typically within 20-30% of :class:`dict` for common operations, with a link to the benchmarks page for the full breakdown -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1559`. - Fixed the C extension's ``popone()`` and ``pop()`` docstrings, which claimed the last occurrence of the key was removed; the first one is, as on the pure-Python backend. Added a note on rejecting duplicate values for keys that must carry exactly one, such as security-sensitive HTTP headers -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1578`. - Added a C API example of a watcher that invalidates a cache: it unwatches from its own callback, watches again on the next rebuild, and gives up on a multidict that keeps changing -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1586`. - Refreshed the tables in the benchmarking reference for the 7.0.0 release, and replaced the range quoted for the pure-Python backend with its average slowdown -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1602`. Packaging updates and notes for downstreams ------------------------------------------- - Removed the C extension code path that existed only for free-threaded Python 3.13, whose support was dropped in v6.8.0. The free-threaded build started requiring CPython 3.14 or newer and failing with a clear compiler error on older free-threaded interpreters; builds with the GIL enabled were unaffected and still go down to Python 3.10 -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1524`. Contributor-facing changes -------------------------- - Added CodSpeed benchmarks for ``getall()`` on a large table, ``update()`` with duplicate keys, ``merge()``, ``to_dict()`` and items view containment with duplicate keys -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1506`. - Added CodSpeed benchmarks for ``getall()`` on keys with many values, both in a small table and in a table large enough to need a heap allocated bitmap -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1509`. - Moved the compiler hint macros used by the C extension out of ``htkeys.h`` into ``compiler.h`` and dropped the ``HT_`` prefix from their names -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1510`. - Moved the debug-only ``ASSERT_CONSISTENT`` macro and the ``_md_check_consistency()`` and ``_md_dump()`` helpers used by the C extension out of ``hashtable.h`` into their own ``debug.h`` header -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1513`. - Added CodSpeed benchmarks for ``CIMultiDict`` keyed by plain ``str`` instead of ``istr``, covering already-lowercase, mixed-case and non-ASCII keys -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1521`. - Documented that the pure-Python test leg needs ``--no-c-extensions`` alongside ``MULTIDICT_NO_EXTENSIONS=1``; the environment variable picks the backend that gets imported, but only the flag deselects the C-extension half of the test matrix -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1523`. - Documented how to run the Hypothesis property tests, which a default ``pytest`` run excludes via the ``-m "not hypothesis"`` marker expression in ``pytest.ini`` and whose pin ``make install-dev`` does not install -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1525`. - Added ``tools/codegen_diff.py``, which reports the functions GCC emits differently before and after a change to the C extension. Addresses, RIP-relative displacements and branch offsets inside a symbol are normalized away first, so a single instruction-length change no longer makes every later function look modified, while constants and structure offsets are left alone so that a real change is not folded away. A tree that fails to build is reported by name rather than compared -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1529`. - Fixed ``benchmarks/istr.py`` to import ``pyperf`` under its current name; it had been failing to start since the package was renamed from ``perf`` -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1531`. - Rewrote the benchmark scripts around a single registry of benchmarked operations, and added a Callgrind driver that records deterministic instruction counts per operation. The measurements of destructive operations such as ``pop()`` and ``clear()`` stopped counting the cost of rebuilding the mapping -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1532`. - Moved the bulk ``extend()``, ``update()`` and ``merge()`` paths of the C extension out of ``hashtable.h`` into their own ``bulk_update.h`` header, leaving ``hashtable.h`` to the table itself and the single-key operations -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1533`. - Moved the two list-reading helpers behind ``itemsview.__contains__()`` and the ``extend()``/``update()``/``merge()`` sequence parser out of ``hashtable.h`` and into a new ``multidict/_multilib/unpack.h``, together with a shared ``unpack_pair()`` that both call to read a ``(key, value)`` pair out of an exact two-element tuple or list. C extension only; the pure Python implementation has no header layout to mirror -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1534`. - Added a ``MULTIDICT_NO_FREELIST=1`` build option that stops the C extension reusing freed blocks. A reused block never reaches ``free()``, so AddressSanitizer can neither poison it nor report a use-after-free on it; an instrumented run wants a second pass with this set, as ``AGENTS.md`` describes. Also added a private ``_freelist_clear()`` to the C extension, for tests that inject an allocation failure: a reused block lets an operation run without calling the allocator at all, which would put the recovery path out of reach -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1539`. - Added a visitor to the public C API test harnesses that mutates the multidict it is walking, covering the reentrancy guard in ``MultiDict_ForEach()`` from both ``multidict._testcapi`` and ``multidict._testcyapi`` -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1544`. - Added benchmarks for the shapes where an allocation is most of the work: an empty mapping, a 20-item one built or copied, a view or an iterator taken on its own, and a proxy. The allocation an operation makes is a fixed cost, so at the existing 200-item size it was divided across 200 entries and all but vanished; ``d.items()`` measured 1.7% faster where it is 46% faster -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1556`. - Sorted ``docs/spelling_wordlist.txt`` case-insensitively, and documented in ``AGENTS.md`` that it is to be re-sorted rather than appended to. The list had grown in append order, which made a word hard to find and put every PR adding one on the same last line -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1557`. - Stopped asking every performance pull request to regenerate the tables in the benchmarking reference, since each run shifts every row slightly and the churn hides the rows that moved, and moved the refresh to once per release. Added :file:`RELEASE.md`, which documents the release procedure, including that refresh -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1564`. - Renamed the C extension's internal helpers in the ``_multilib`` headers so that a leading underscore marks exactly the functions used only within their own header; type slots and method callbacks keep their names -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1571`. - Spelled out in ``AGENTS.md`` that every verb in a news fragment is past tense, and added a bad and good example pair plus a matching "Things not to do" entry -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1588`. - Changed the release workflow to rewrite the Sphinx roles of the changelog section as Markdown before publishing it as the GitHub Release body, so that class, method and exception references, contributor handles and documentation links render there instead of showing as raw markup -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1596`. Miscellaneous internal changes ------------------------------ - Changed the internal ``htkeys_build_indices()`` helper in the C extension to return ``void``; it cannot fail, so both callers were checking for an error that never occurred -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1516`. - Routed every field of the C extension's hash table that more than one thread can reach through a named accessor in the new ``_multilib/freethreading.h`` header, instead of each call site choosing between an atomic and a plain access behind ``#ifdef Py_GIL_DISABLED``. Each accessor kept the operation and memory ordering its call sites already used, so neither build changed behavior -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1518`. - Removed ``get_mod_state_by_cls()``, an unused helper in the C extension -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1520`. - Documented why ``md_clear()`` keeps clearing entry fields with ``Py_CLEAR()`` on the GIL build rather than routing them through ``_multilib/freethreading.h``: ``Py_CLEAR()`` skips the store when a field is already ``NULL``, so no accessor there is a substitute for it -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1526`. - Collapsed the two ``#ifdef Py_GIL_DISABLED`` arms that fill in a freshly inserted hash table entry in the C extension into one. Both builds started writing the entry's fields in the order the free-threaded build needs, and publishing the value without first loading and releasing the one already there, since a fresh entry holds none -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1528`. - Merged the C extension's deferred-decref accumulator into ``reflist_t``, so one collector of strong references, in the ``_multilib/reflist.h`` header, came to serve both the callers that build a result list and the ones that postpone a decref until a mutation finishes. Its storage became an inline array that spills into a chain of heap blocks, which also made :meth:`~multidict.MultiDict.getall` a little faster on keys with many values, since the collected values stopped being copied to a bigger buffer as they accumulate -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1530`. - Dropped two module state lookups in the C extension that had already been answered elsewhere. ``MultiDict.__init__()`` and ``CIMultiDict.__init__()`` switched to reading the state pointer that ``tp_new()`` stored on the object instead of resolving it from the type a second time, and an ``istr`` stopped carrying a state pointer of its own, which was written on every creation and never read. Constructing a subclass of either container got about 1% cheaper, and every ``istr`` is eight bytes smaller -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1536`. - Narrowed the critical section of the single-key methods (``add()``, ``__setitem__()``, ``__delitem__()``, ``setdefault()``, ``pop()``, ``popone()``, ``popall()`` and ``getall()``): a key is converted to its identity and hashed before the lock is taken, rather than while a free-threaded build holds the multidict locked -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1538`. - Changed ``MultiDict_ForEach()`` to walk every entry with a linear scan instead of the resumable iterator cursor, which its caller does not need while it holds the critical section for the whole walk -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1544`. - Sped up the argument parsing of the C extension. The all-positional form of every method taking ``key`` started binding its arguments inline instead of calling into the keyword machinery, and keyword names were matched against interned parameter names by identity before falling back to a string comparison -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1549`. - Stopped building the implicit :data:`None` default of ``MultiDict.get()`` on every call in the C extension, and produced it only when the key is actually missing -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1549`. - Laid out the common branch in line in the single-key lookup probe loop and in the key to identity conversion: the deleted-slot check and the ``str`` subclass check were marked as the rare cases, so the compiler stopped putting the frequent path behind a taken branch -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1550`. - Stopped building the implicit :data:`None` default of ``MultiDict.setdefault()`` on every call in the C extension -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1553`. - Reordered the fields of the C extension's multidict object so the ones a lookup reads share a cache line, and dropped its separate module reference in favor of the one the module state already holds, which made every multidict 16 bytes smaller -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1584`. - Dropped the C extension's ``multidict_tp_traverse()`` and ``multidict_tp_clear()`` wrappers; ``md_traverse()`` and ``md_clear()`` now serve as the type's GC slots directly -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1589`. - Removed a redundant ``NULL`` check and a stray ``PyErr_Format()`` argument from the C constructors, and an always true condition from the pure Python ``getall()`` -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1590`. - Merged the duplicated ``__init__()`` bodies of the C ``MultiDict`` and ``CIMultiDict`` into one helper -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1591`. - Replaced the allocate, set state and take the module reference sequence repeated across the C constructors with one helper -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1592`. - Renamed internal helpers whose names no longer matched what they did, and fixed misspelled docstring identifiers in the C extension -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1593`. - Renamed the C extension's functions that are used only as type slots after the slot they fill, as ``_tp_``, ``_nb_``, ``_sq_`` or ``_mp_`` -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1594`. - Replaced the custom ``tp_new`` slots that blocked direct instantiation of the C view and iterator types with ``Py_TPFLAGS_DISALLOW_INSTANTIATION`` -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1595`. @ text @$NetBSD: patch-setup.py,v 1.2 2026/09/26 10:18:39 wiz Exp $ Use CFLAGS from pkgsrc. --- setup.py.orig 2026-09-26 17:37:59.797439000 +0000 +++ setup.py @@@@ -13,7 +13,7 @@@@ if sys.implementation.name != "cpython": if sys.implementation.name != "cpython": NO_EXTENSIONS = True -CFLAGS = ["-O0", "-g3", "-UNDEBUG"] if DEBUG_BUILD else ["-O3", "-DNDEBUG"] +CFLAGS = ["-O0", "-g3", "-UNDEBUG"] if DEBUG_BUILD else ["-DNDEBUG"] LDFLAGS = [] if NO_FREELIST: @ 1.2 log @py-multidict: update to 6.9.1. 6.9.1 ===== *(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.merge` or the :py:class:`~multidict.MultiDict` and :py:class:`~multidict.CIMultiDict` constructors, a ``[key, value]`` item inside any iterable handed to them, or a list tested with ``in`` against :py:meth:`~multidict.MultiDict.items`, is changed 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-exit decrement 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 lose the 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 could insert 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 the ``items()``/ ``keys()``/``values()`` equality path could raise ``KeyError`` or report a present, 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() - operand`` leaked one key and one value reference per element of ``operand``, letting a large operand grow memory without bound (:gh:`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 by calling ``time.sleep()``) while ``update()``, ``merge()``, ``__setitem__()``, ``__delitem__()``, ``pop()``, ``popone()``, or ``popall()`` was dropping a replaced or removed value. ``Py_BEGIN_CRITICAL_SECTION`` compiles to a no-op on this build, so nothing else was stopping a second thread from mutating the very same ``MultiDict`` concurrently 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.CIMultiDict` whose keys are plain :py:class:`str`. Converting such a key to :py:class:`~multidict.istr` could run Python code (a :py:class:`str` subclass's ``__str__`` or ``__del__``) or, on free-threaded builds, 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.copy` and re-initializing from 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()``, ``[]`` or ``in`` could read a hash table that a concurrent resize 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 ``include`` and a duplicated ``exclude`` line from ``MANIFEST.in``; sdist contents are unchanged -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1478`. - Added ``.claudeignore`` file -- by :user:`asvetlov` *Related 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:`asvetlov` *Related 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 and apply the operation in a loop, like the ``add()`` and ``extend()`` benchmarks already do. The insertion, ``update()`` and ``clear()`` 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 their operation 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 ``-I`` from the AddressSanitizer test command in ``AGENTS.md`` and in the CI job. It implies ``-E``, which made Python ignore ``PYTHONMALLOC=malloc``, so small hash tables were still served from ``pymalloc`` arenas 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-existent allocation-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 blocks with 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`. ---- 6.9.0 ========== *(2026-09-18)* Bug fixes --------- - Protected ``repr()`` of ``MultiDict``, ``MultiDictProxy``, and their views in 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()`` of ``MultiDictProxy`` in the C extension and of ``KeysView`` in both the C extension and the pure-Python implementation against infinite recursion on self-referential containers, matching the existing guard on ``MultiDict``, ``ItemsView``, and ``ValuesView`` -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1431`. - Protected ``MultiDict.update()``, ``.extend()``, ``.merge()``, ``.clear()``, ``.copy()``, and the ``MultiDict``/``CIMultiDict`` constructors in the C extension with a critical section, using the two-object form when a second multidict, multidict proxy, or plain ``dict`` instance is involved, avoiding data races and a segmentation fault on the free-threaded build of CPython. ``.clear()`` now also publishes the empty 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 the C 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.used`` was written non-atomically while ``len()`` read it with a relaxed atomic load -- 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 their critical section transiently suspended during that allocation, and then use a keys-table pointer or size a concurrent operation had already invalidated; ``_md_del_at()`` (plain ``del``/``pop()``) could likewise leave ``self`` in an 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->keys`` was published 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 critical section 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->retired`` for the rest of the object's lifetime 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 every multidict instance and used to derive ``getversion()``, was bumped with a 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()`` and ``update()``/``extend()``/``merge()`` temporarily marked and unmarked a matching entry's hash with a plain, non-atomic store, while a lock-free ``__contains__``/``get()`` on another thread 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``/``CIMultiDict`` fallback 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``/``CIMultiDict`` instance now holds its own lock for the 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 another thread 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()`` read ``self``'s module state without holding ``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 no longer corrupts or hangs when a compared value's ``__eq__()`` re-enters the same :class:`~multidict.MultiDict`, for example by calling ``getall()`` on it. 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 table index slot with a plain, non-atomic store, while a lock-free ``get()``/``getone()``/``__getitem__``/``__contains__`` on another thread walked 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.istr` keys in the case-sensitive :class:`~multidict.MultiDict`, so both the C and the pure Python 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()`` (and ``merge()``, which shares the same code path) silently failing to invalidate an in-progress ``keys()``/ ``items()``/``values()`` iterator when the call only overwrote an already-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``/``CIMultiDict`` iterator guard so that mutating the mapping before the first ``next()`` call on an ``items()``, ``keys()``, or ``values()`` iterator (or on ``iter(md)``) now reliably raises ``RuntimeError``, matching the C extension. Previously a mutation that happened before the iterator was ever advanced, such as ``clear()`` or a ``del``/``popone()`` that removed the only remaining entry, could make the iterator silently raise ``StopIteration`` instead -- 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 another thread 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_dict` method returning a plain :class:`dict` that maps every key to the list of all its values. Unlike ``dict(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 per spelling of a case-insensitive key, it groups by key identity -- by :user:`rodrigobnogueira`. *Related issues and pull requests on GitHub:* :issue:`783`. - ``MultiDict``, ``CIMultiDict``, ``MultiDictProxy`` and ``CIMultiDictProxy`` in the C extension implemented the vectorcall calling convention for construction. This sped up ``MultiDict(...)``-style calls by avoiding an intermediate positional-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, instead of 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``/``CIMultiDict`` implementation safe to use 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.MultiDict` or :class:`~multidict.CIMultiDict`, each added 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.rst` builds 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.MultiDict` from 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``/``UndefinedBehaviorSanitizer`` and ``ThreadSanitizer`` CI jobs. The former runs the suite under a regular CPython with ``MULTIDICT_ASAN_BUILD=1``; the latter builds a free-threaded CPython instrumented with ``--with-thread-sanitizer`` and 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.CIMultiDict` semantics, 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:`asvetlov` *Related issues and pull requests on GitHub:* :issue:`1470`. - Add benchmarks for ``__setitem__`` -- by :user:`asvetlov` *Related 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 process before 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 ``hypothesis`` pin out of ``requirements/pytest.txt`` into its own ``requirements/pytest-hypothesis.txt``, installed only by the dedicated Hypothesis CI jobs. ``hypothesis`` now ships a Rust extension with no prebuilt 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 newer ``PyIter_NextItem()`` API in the C extension, so the iterator-exhausted and error cases are told apart by the return code instead of an ambiguous ``NULL`` result -- by :user:`asvetlov`. *Related issues and pull requests on GitHub:* :issue:`1440`. @ text @d1 1 a1 1 $NetBSD: patch-setup.py,v 1.1 2026/09/10 11:32:24 adam Exp $ d5 1 a5 1 --- setup.py.orig 2026-09-21 16:18:55.298134000 +0000 d7 1 a7 1 @@@@ -12,7 +12,7 @@@@ if sys.implementation.name != "cpython": d15 1 a15 1 if platform.system() != "Windows": @ 1.1 log @py-multidict: add the new patch @ text @d1 1 a1 1 $NetBSD$ d5 1 a5 1 --- setup.py.orig 2026-09-09 12:34:28.056868300 +0000 d7 1 a7 1 @@@@ -10,7 +10,7 @@@@ DEBUG_BUILD = bool(os.environ.get("MULTI d13 1 a15 1 CFLAGS.extend( @