Skip to content

Commit b9ecbaf

Browse files
Address review feedback: use fixtures, RST roles, and modern type syntax
- Use `any_multidict_class` fixture instead of manual `@pytest.mark.parametrize` - Use modern type annotations (`type[]` instead of `Type[]`, `|` instead of `Union`) - Add explanatory comments to empty except clauses (CodeQL feedback) - Use RST roles in changelog (`:class:`, `:c:func:`) for highlighted identifiers - Remove manual pure-python skip (fixture handles both variants) - Use `MutableMultiMapping` for internal type hints
1 parent ab0b478 commit b9ecbaf

2 files changed

Lines changed: 11 additions & 15 deletions

File tree

CHANGES/1317.bugfix.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Fixed a memory-safety race condition resulting in segmentation faults (Use-After-Free) when iterating and modifying a ``MultiDict`` concurrently in CPython free-threaded mode (3.13t+). Read/Write accesses to the internal ``keys`` buffer are now wrapped in ``Py_BEGIN_CRITICAL_SECTION``
1+
Fixed a memory-safety race condition resulting in segmentation faults (Use-After-Free) when iterating and modifying a :class:`MultiDict` concurrently in CPython free-threaded mode (3.13t+). Read/Write accesses to the internal ``keys`` buffer are now wrapped in :c:func:`Py_BEGIN_CRITICAL_SECTION`
22
-- by :user:`rodrigobnogueira`.

tests/test_free_threading.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import threading
2-
from typing import Type, Union
1+
from __future__ import annotations
32

4-
import pytest
3+
import threading
54

6-
from multidict import CIMultiDict, MultiDict
5+
from multidict import CIMultiDict, MultiDict, MutableMultiMapping
76

87

9-
@pytest.mark.parametrize("cls", [CIMultiDict, MultiDict])
108
def test_race_condition_iterator_vs_mutation(
11-
cls: Union[Type[CIMultiDict[str]], Type[MultiDict[str]]],
9+
any_multidict_class: type[CIMultiDict[str]] | type[MultiDict[str]],
1210
) -> None:
1311
"""Test that concurrent iterations and mutations do not cause a memory safety violation.
1412
@@ -20,36 +18,34 @@ def test_race_condition_iterator_vs_mutation(
2018
a standard Python ``RuntimeError`` ('MultiDict is changed during iteration'), preventing
2119
crashes.
2220
"""
23-
if cls.__module__ == "multidict._multidict_py":
24-
pytest.skip("Test is only applicable to the C extension")
25-
26-
md: Union[CIMultiDict[str], MultiDict[str]] = cls()
21+
md: MutableMultiMapping[str] = any_multidict_class()
2722
for i in range(8):
2823
md[f"init-{i}"] = f"v{i}"
2924

3025
errors: list[tuple[str, int, str, str, str]] = []
3126

32-
def writer(target: Union[CIMultiDict[str], MultiDict[str]]) -> None:
27+
def writer(target: MutableMultiMapping[str]) -> None:
3328
for i in range(256):
3429
try:
3530
target[f"k-{i % 64}"] = f"v{i}"
3631
except RuntimeError: # pragma: no cover
37-
pass # "MultiDict is changed during iteration" is expected
32+
# "MultiDict changed during iteration" is expected under contention
33+
pass
3834
except Exception as e: # pragma: no cover
3935
import traceback
4036

4137
errors.append(
4238
("writer", i, type(e).__name__, str(e), traceback.format_exc())
4339
)
4440

45-
def reader(target: Union[CIMultiDict[str], MultiDict[str]]) -> None:
41+
def reader(target: MutableMultiMapping[str]) -> None:
4642
for i in range(256):
4743
try:
4844
list(target.items())
4945
list(target.keys())
5046
list(target.values())
5147
except RuntimeError:
52-
# "MultiDict is changed during iteration" is exactly the expected
48+
# "MultiDict changed during iteration" is exactly the expected
5349
# and memory-safe outcome when iterating a resizing dictionary.
5450
pass
5551
except Exception as e: # pragma: no cover

0 commit comments

Comments
 (0)