Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES/1328.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed global counter system using an atomic variable.
-- by :user:`Vizonex`.
8 changes: 6 additions & 2 deletions multidict/_multilib/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
extern "C" {
#endif

#include <stdatomic.h>

/* State of the _multidict module */
typedef struct {
PyTypeObject *IStrType;
Expand All @@ -26,7 +28,7 @@ typedef struct {
PyObject *str_lower;
PyObject *str_name;

uint64_t global_version;
_Atomic uint64_t global_version;
} mod_state;

static inline mod_state *
Expand Down Expand Up @@ -128,7 +130,9 @@ get_mod_state_by_def(PyObject *self)
static inline uint64_t
NEXT_VERSION(mod_state *state)
{
return ++state->global_version;
return atomic_fetch_add_explicit(
&state->global_version, 1, memory_order_relaxed) +
1;
}

#ifdef __cplusplus
Expand Down
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
"-Werror",
]
)
else:
Comment thread
bdraco marked this conversation as resolved.
Outdated
CFLAGS.extend(
[
"/std:c11",
"/experimental:c11atomics",
]
)

extensions = [
Extension(
Expand Down
32 changes: 32 additions & 0 deletions tests/isolated/multidict_global_counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import threading
import multidict
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
bdraco marked this conversation as resolved.
Dismissed
from multidict import MultiDict
import sysconfig
Comment thread
Vizonex marked this conversation as resolved.
Outdated

FREETHREADED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))


md: MultiDict[int] = MultiDict()
N, M = 3, 100
baseline = multidict.getversion(md) # type: ignore[arg-type]


def worker(tid: int) -> None:
for i in range(M):
md[f"k{tid}_{i}"] = i


if (__name__ == "__main__") and FREETHREADED:
threads = [threading.Thread(target=worker, args=(tid,)) for tid in range(N)]
for t in threads:
t.start()
for t in threads:
t.join()

observed = multidict.getversion(md) - baseline # type: ignore[arg-type]
expected = N * M
assert expected == observed, (
f"expected delta: {expected}"
f" observed: {observed} "
f"lost: {expected - observed}"
)
1 change: 1 addition & 0 deletions tests/test_leaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"multidict_type_leak.py",
"multidict_type_leak_items_values.py",
"multidict_pop.py",
"multidict_global_counter.py",
),
)
@pytest.mark.leaks
Expand Down
Loading