Skip to content

Commit dd9b784

Browse files
committed
Add safe memory reclamation scheme based on FreeBSD's GUS
The scheme will be used to allow safe reads from dicts and lists that don't acquire the collection's lock.
1 parent fc173e3 commit dd9b784

10 files changed

+137
-3
lines changed

Include/internal/pycore_interp.h

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ typedef struct PyThreadStateImpl {
6868
PyThreadState tstate;
6969

7070
struct brc_state brc;
71+
72+
struct qsbr *qsbr;
7173
} PyThreadStateImpl;
7274

7375

Include/internal/pycore_qsbr.h

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#ifndef Py_INTERNAL_QSBR_H
2+
#define Py_INTERNAL_QSBR_H
3+
4+
#include <stdbool.h>
5+
#include "pyatomic.h"
6+
#include "pycore_llist.h"
7+
#include "pycore_initconfig.h"
8+
#include "pycore_pystate.h"
9+
#include "pycore_runtime.h"
10+
11+
/* Per-thread state */
12+
struct qsbr {
13+
uint64_t t_seq;
14+
struct qsbr_shared *t_shared;
15+
struct qsbr *t_next;
16+
PyThreadState *tstate;
17+
};
18+
19+
struct qsbr_pad {
20+
struct qsbr qsbr;
21+
char __padding[64 - sizeof(struct qsbr)];
22+
};
23+
24+
static inline uint64_t
25+
_Py_qsbr_shared_current(struct qsbr_shared *shared)
26+
{
27+
return _Py_atomic_load_uint64(&shared->s_wr);
28+
}
29+
30+
static inline void
31+
_Py_qsbr_quiescent_state(PyThreadState *ts)
32+
{
33+
struct qsbr *qsbr = ((struct PyThreadStateImpl *)ts)->qsbr;
34+
uint64_t seq = _Py_qsbr_shared_current(qsbr->t_shared); // need acquire
35+
_Py_atomic_store_uint64_relaxed(&qsbr->t_seq, seq); // probably release
36+
}
37+
38+
PyStatus
39+
_Py_qsbr_init(struct qsbr_shared *shared);
40+
41+
uint64_t
42+
_Py_qsbr_advance(struct qsbr_shared *shared);
43+
44+
bool
45+
_Py_qsbr_poll(struct qsbr *qsbr, uint64_t goal);
46+
47+
void
48+
_Py_qsbr_online(struct qsbr *qsbr);
49+
50+
void
51+
_Py_qsbr_offline(struct qsbr *qsbr);
52+
53+
struct qsbr *
54+
_Py_qsbr_recycle(struct qsbr_shared *shared, PyThreadState *tsate);
55+
56+
struct qsbr *
57+
_Py_qsbr_register(struct qsbr_shared *shared, PyThreadState *tsate, struct qsbr *qsbr);
58+
59+
void
60+
_Py_qsbr_unregister(struct qsbr *qsbr);
61+
62+
void
63+
_Py_qsbr_after_fork(struct qsbr_shared *shared, struct qsbr *qsbr);
64+
65+
#endif /* !Py_INTERNAL_QSBR_H */

Include/internal/pycore_runtime.h

+14
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ typedef struct _Py_AuditHookEntry {
5656
void *userData;
5757
} _Py_AuditHookEntry;
5858

59+
/* See pycore_qsbr.h for full definition */
60+
struct qsbr;
61+
5962
/* Full Python runtime state */
6063

6164
/* _PyRuntimeState holds the global state for the CPython runtime.
@@ -118,6 +121,17 @@ typedef struct pyruntimestate {
118121
struct _xidregitem *head;
119122
} xidregistry;
120123

124+
struct qsbr_shared {
125+
/* always odd, incremented by two */
126+
uint64_t s_wr;
127+
128+
/* Minimum observed read sequence. */
129+
uint64_t s_rd_seq;
130+
131+
struct qsbr *head;
132+
uintptr_t n_free;
133+
} qsbr_shared;
134+
121135
unsigned long main_thread;
122136
PyThreadState *main_tstate;
123137

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ PYTHON_OBJS= \
415415
Python/pyrefcnt.o \
416416
Python/pythonrun.o \
417417
Python/pytime.o \
418+
Python/qsbr.o \
418419
Python/bootstrap_hash.o \
419420
Python/specialize.o \
420421
Python/structmember.o \

PCbuild/_freeze_module.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@
232232
<ClCompile Include="..\Python\pythonrun.c" />
233233
<ClCompile Include="..\Python\Python-tokenize.c" />
234234
<ClCompile Include="..\Python\pytime.c" />
235+
<ClCompile Include="..\Python\qsbr.c" />
235236
<ClCompile Include="..\Python\specialize.c" />
236237
<ClCompile Include="..\Python\structmember.c" />
237238
<ClCompile Include="..\Python\suggestions.c" />

PCbuild/_freeze_module.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,9 @@
340340
<ClCompile Include="..\Python\pytime.c">
341341
<Filter>Source Files</Filter>
342342
</ClCompile>
343+
<ClCompile Include="..\Python\qsbr.c">
344+
<Filter>Source Files</Filter>
345+
</ClCompile>
343346
<ClCompile Include="..\Objects\rangeobject.c">
344347
<Filter>Source Files</Filter>
345348
</ClCompile>

PCbuild/pythoncore.vcxproj

+2
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@
253253
<ClInclude Include="..\Include\internal\pycore_pymem_init.h" />
254254
<ClInclude Include="..\Include\internal\pycore_pystate.h" />
255255
<ClInclude Include="..\Include\internal\pycore_pythread.h" />
256+
<ClInclude Include="..\Include\internal\pycore_qsbr.h" />
256257
<ClInclude Include="..\Include\internal\pycore_range.h" />
257258
<ClInclude Include="..\Include\internal\pycore_refcnt.h" />
258259
<ClInclude Include="..\Include\internal\pycore_runtime.h" />
@@ -556,6 +557,7 @@
556557
<ClCompile Include="..\Python\Python-ast.c" />
557558
<ClCompile Include="..\Python\Python-tokenize.c" />
558559
<ClCompile Include="..\Python\pythonrun.c" />
560+
<ClCompile Include="..\Python\qsbr.c" />
559561
<ClCompile Include="..\Python\specialize.c" />
560562
<ClCompile Include="..\Python\suggestions.c" />
561563
<ClCompile Include="..\Python\structmember.c" />

PCbuild/pythoncore.vcxproj.filters

+6
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,9 @@
663663
<ClInclude Include="..\Include\internal\pycore_pythread.h">
664664
<Filter>Include\internal</Filter>
665665
</ClInclude>
666+
<ClInclude Include="..\Include\internal\pycore_qsbr.h">
667+
<Filter>Include</Filter>
668+
</ClInclude>
666669
<ClInclude Include="..\Include\internal\pycore_range.h">
667670
<Filter>Include\internal</Filter>
668671
</ClInclude>
@@ -1253,6 +1256,9 @@
12531256
<ClCompile Include="..\Python\pythonrun.c">
12541257
<Filter>Python</Filter>
12551258
</ClCompile>
1259+
<ClCompile Include="..\Python\qsbr.c">
1260+
<Filter>Python</Filter>
1261+
</ClCompile>
12561262
<ClCompile Include="..\Python\specialize.c">
12571263
<Filter>Python</Filter>
12581264
</ClCompile>

Python/pylifecycle.c

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "pycore_pylifecycle.h" // _PyErr_Print()
2222
#include "pycore_pymem.h" // _PyObject_DebugMallocStats()
2323
#include "pycore_pystate.h" // _PyThreadState_GET()
24+
#include "pycore_qsbr.h" // _Py_qsbr_init()
2425
#include "pycore_runtime.h" // _Py_ID()
2526
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
2627
#include "pycore_sliceobject.h" // _PySlice_Fini()
@@ -619,6 +620,11 @@ pycore_init_runtime(_PyRuntimeState *runtime,
619620
if (_PyStatus_EXCEPTION(status)) {
620621
return status;
621622
}
623+
624+
status = _Py_qsbr_init(&runtime->qsbr_shared);
625+
if (_PyStatus_EXCEPTION(status)) {
626+
return status;
627+
}
622628
return _PyStatus_OK();
623629
}
624630

Python/pystate.c

+37-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "pycore_pylifecycle.h"
1414
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
1515
#include "pycore_pystate.h" // _PyThreadState_GET()
16+
#include "pycore_qsbr.h"
1617
#include "pycore_runtime_init.h" // _PyRuntimeState_INIT
1718
#include "pycore_sysmodule.h"
1819
#include "pycore_refcnt.h"
@@ -164,12 +165,13 @@ _PyThreadState_Attach(PyThreadState *tstate)
164165
&tstate->status,
165166
_Py_THREAD_DETACHED,
166167
_Py_THREAD_ATTACHED)) {
168+
// online for QSBR too
169+
_Py_qsbr_online(((PyThreadStateImpl *)tstate)->qsbr);
167170

168171
// resume previous critical section
169172
if (tstate->critical_section != 0) {
170173
_Py_critical_section_resume(tstate);
171174
}
172-
173175
return 1;
174176
}
175177
return 0;
@@ -178,6 +180,8 @@ _PyThreadState_Attach(PyThreadState *tstate)
178180
static void
179181
_PyThreadState_Detach(PyThreadState *tstate)
180182
{
183+
_Py_qsbr_offline(((PyThreadStateImpl *)tstate)->qsbr);
184+
181185
if (tstate->critical_section != 0) {
182186
_Py_critical_section_end_all(tstate);
183187
}
@@ -737,7 +741,8 @@ free_threadstate(PyThreadState *tstate)
737741
static void
738742
init_threadstate(PyThreadState *tstate,
739743
PyInterpreterState *interp, uint64_t id,
740-
PyThreadState *next)
744+
PyThreadState *next,
745+
struct qsbr *empty_qsbr)
741746
{
742747
if (tstate->_initialized) {
743748
Py_FatalError("thread state already initialized");
@@ -763,6 +768,18 @@ init_threadstate(PyThreadState *tstate,
763768
tstate->native_thread_id = PyThread_get_thread_native_id();
764769
#endif
765770

771+
// First try to recycle an existing qsbr structure
772+
PyThreadStateImpl *tstate_impl = (PyThreadStateImpl *)tstate;
773+
struct qsbr *recycled = _Py_qsbr_recycle(&_PyRuntime.qsbr_shared, tstate);
774+
if (recycled) {
775+
tstate_impl->qsbr = recycled;
776+
}
777+
else {
778+
// If no recycled struct, use the newly allocated empty qsbr struct
779+
tstate_impl->qsbr = empty_qsbr;
780+
_Py_qsbr_register(&_PyRuntime.qsbr_shared, tstate, empty_qsbr);
781+
}
782+
766783
tstate->py_recursion_limit = interp->ceval.recursion_limit,
767784
tstate->py_recursion_remaining = interp->ceval.recursion_limit,
768785
tstate->c_recursion_remaining = C_RECURSION_LIMIT;
@@ -791,6 +808,12 @@ new_threadstate(PyInterpreterState *interp)
791808
if (new_tstate == NULL) {
792809
return NULL;
793810
}
811+
struct qsbr *qsbr = PyMem_RawCalloc(1, sizeof(struct qsbr_pad));
812+
if (qsbr == NULL) {
813+
PyMem_RawFree(new_tstate);
814+
return NULL;
815+
}
816+
794817
/* We serialize concurrent creation to protect global state. */
795818
HEAD_LOCK(runtime);
796819

@@ -818,13 +841,17 @@ new_threadstate(PyInterpreterState *interp)
818841
}
819842
interp->threads.head = tstate;
820843

821-
init_threadstate(tstate, interp, id, old_head);
844+
init_threadstate(tstate, interp, id, old_head, qsbr);
822845

823846
HEAD_UNLOCK(runtime);
824847
if (!used_newtstate) {
825848
// Must be called with lock unlocked to avoid re-entrancy deadlock.
826849
PyMem_RawFree(new_tstate);
827850
}
851+
if (qsbr->tstate == NULL) {
852+
// If the qsbr structure wasn't used, free it here after the unlock.
853+
PyMem_RawFree(qsbr);
854+
}
828855
return tstate;
829856
}
830857

@@ -1062,6 +1089,13 @@ tstate_delete_common(PyThreadState *tstate,
10621089
PyThread_tss_set(&gilstate->autoTSSkey, NULL);
10631090
}
10641091

1092+
PyThreadStateImpl *tstate_impl = (PyThreadStateImpl *)tstate;
1093+
if (is_current) {
1094+
_Py_qsbr_offline(tstate_impl->qsbr);
1095+
}
1096+
_Py_qsbr_unregister(tstate_impl->qsbr);
1097+
tstate_impl->qsbr = NULL;
1098+
10651099
_PyRuntimeState *runtime = interp->runtime;
10661100
HEAD_LOCK(runtime);
10671101
if (tstate->prev) {

0 commit comments

Comments
 (0)