Skip to content

Commit b819053

Browse files
Fix TSAN races
1 parent 44356d6 commit b819053

File tree

5 files changed

+22
-58
lines changed

5 files changed

+22
-58
lines changed

Include/internal/pycore_optimizer.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,7 @@ static inline int is_terminator(const _PyUOpInstruction *uop)
365365
extern void _PyExecutor_Free(_PyExecutorObject *self);
366366

367367
PyAPI_FUNC(int) _PyDumpExecutors(FILE *out);
368-
#ifdef _Py_TIER2
369-
extern void _Py_ClearExecutorDeletionList(PyThreadState *tstate);
370-
#endif
368+
371369

372370
int _PyJit_translate_single_bytecode_to_trace(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *next_instr, int stop_tracing_opcode);
373371

Lib/test/test_capi/test_opt.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ def f():
129129
self.assertTrue(exe.is_valid())
130130
sys._clear_internal_caches()
131131
self.assertFalse(exe.is_valid())
132+
gc.collect()
133+
exe = get_first_executor(f)
134+
self.assertIsNone(exe)
132135

133136

134137
@requires_jit_enabled

Python/instrumentation.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ force_instrument_lock_held(PyCodeObject *code, PyInterpreterState *interp)
17851785
if (code->co_executors != NULL) {
17861786
_PyCode_Clear_Executors(code);
17871787
}
1788-
_Py_Executors_InvalidateDependencyLockHeld(_PyInterpreterState_GET(), code, 1);
1788+
_Py_Executors_InvalidateDependencyLockHeld(interp, code, 1);
17891789
#endif
17901790
int code_len = (int)Py_SIZE(code);
17911791
/* Exit early to avoid creating instrumentation
@@ -1928,7 +1928,9 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
19281928
{
19291929
int res;
19301930
LOCK_CODE(code);
1931+
HEAD_LOCK(interp->runtime);
19311932
res = instrument_lock_held(code, interp);
1933+
HEAD_UNLOCK(interp->runtime);
19321934
UNLOCK_CODE();
19331935
return res;
19341936
}
@@ -2062,7 +2064,10 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
20622064
}
20632065
set_local_events(local, tool_id, events);
20642066

2065-
return force_instrument_lock_held(code, interp);
2067+
HEAD_LOCK(interp->runtime);
2068+
int res = force_instrument_lock_held(code, interp);
2069+
HEAD_UNLOCK(interp->runtime);
2070+
return res;
20662071
}
20672072

20682073
int

Python/optimizer.c

Lines changed: 10 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -260,46 +260,6 @@ _PyExecutor_Free(_PyExecutorObject *self)
260260
PyObject_GC_Del(self);
261261
}
262262

263-
void
264-
_Py_ClearExecutorDeletionList(PyThreadState *tstate)
265-
{
266-
_PyThreadStateImpl* ts = (_PyThreadStateImpl* )tstate;
267-
_PyExecutorObject **prev_to_next_ptr = &ts->jit_executor_state.executor_deletion_list_head;
268-
_PyExecutorObject *exec = *prev_to_next_ptr;
269-
// Mark the currently executing executor.
270-
if (tstate->current_executor != NULL) {
271-
((_PyExecutorObject *)tstate->current_executor)->vm_data.linked = 1;
272-
}
273-
while (exec != NULL) {
274-
if (exec->vm_data.linked) {
275-
// This executor is currently executing
276-
exec->vm_data.linked = 0;
277-
prev_to_next_ptr = &exec->vm_data.links.next;
278-
}
279-
else {
280-
*prev_to_next_ptr = exec->vm_data.links.next;
281-
assert(exec != _PyExecutor_GetColdDynamicExecutor());
282-
assert(exec != _PyExecutor_GetColdExecutor());
283-
_PyExecutor_Free(exec);
284-
}
285-
exec = *prev_to_next_ptr;
286-
}
287-
ts->jit_executor_state.executor_deletion_list_remaining_capacity = EXECUTOR_DELETE_LIST_MAX;
288-
}
289-
290-
static void
291-
add_to_pending_deletion_list(_PyExecutorObject *self)
292-
{
293-
_PyThreadStateImpl *_tstate = (_PyThreadStateImpl*)_PyThreadState_GET();
294-
self->vm_data.links.next = _tstate->jit_executor_state.executor_deletion_list_head;
295-
_tstate->jit_executor_state.executor_deletion_list_head = self;
296-
if (_tstate->jit_executor_state.executor_deletion_list_remaining_capacity > 0) {
297-
_tstate->jit_executor_state.executor_deletion_list_remaining_capacity--;
298-
}
299-
else {
300-
_Py_ClearExecutorDeletionList(&_tstate->base);
301-
}
302-
}
303263

304264
static void
305265
uop_dealloc(PyObject *op) {
@@ -309,7 +269,7 @@ uop_dealloc(PyObject *op) {
309269
unlink_executor(self);
310270
// Once unlinked it becomes impossible to invalidate an executor, so do it here.
311271
self->vm_data.valid = 0;
312-
add_to_pending_deletion_list(self);
272+
PyObject_GC_Del(self);
313273
}
314274

315275
const char *
@@ -1672,10 +1632,6 @@ executor_clear(PyObject *op)
16721632
assert(FT_ATOMIC_LOAD_UINT8_RELAXED(executor->vm_data.valid) == 0);
16731633
unlink_executor(executor);
16741634

1675-
/* It is possible for an executor to form a reference
1676-
* cycle with itself, so decref'ing a side exit could
1677-
* free the executor unless we hold a strong reference to it
1678-
*/
16791635
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
16801636
for (uint32_t i = 0; i < executor->exit_count; i++) {
16811637
executor->exits[i].temperature = initial_unreachable_backoff_counter();
@@ -1720,7 +1676,8 @@ invalidate_sub_executors(_PyThreadStateImpl *tstate, _PyExecutorObject *executor
17201676
invalidate_sub_executors(tstate, next);
17211677
}
17221678
}
1723-
executor_clear((PyObject *)executor);
1679+
// Let it be cleared up by cold executor invalidation later.
1680+
executor->vm_data.warm = 0;
17241681
}
17251682

17261683
/* Invalidate all executors that depend on `obj`
@@ -1737,6 +1694,7 @@ _Py_Executors_InvalidateDependencyLockHeld(PyInterpreterState *interp, void *obj
17371694
/* Walk the list of executors */
17381695
/* Clearing an executor can deallocate others, so we need to make a list of
17391696
* executors to invalidate first */
1697+
assert(PyMutex_IsLocked(&(interp->runtime)->interpreters.mutex));
17401698
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
17411699
jit_tracer_invalidate_dependency(p, obj);
17421700
for (_PyExecutorObject *exec = ((_PyThreadStateImpl *)p)->jit_executor_state.executor_list_head; exec != NULL;) {
@@ -1755,9 +1713,9 @@ _Py_Executors_InvalidateDependencyLockHeld(PyInterpreterState *interp, void *obj
17551713
void
17561714
_Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation)
17571715
{
1758-
HEAD_LOCK(&_PyRuntime);
1716+
HEAD_LOCK(interp->runtime);
17591717
_Py_Executors_InvalidateDependencyLockHeld(interp, obj, is_invalidation);
1760-
HEAD_UNLOCK(&_PyRuntime);
1718+
HEAD_UNLOCK(interp->runtime);
17611719
}
17621720

17631721
// To avoid deadlocks due to stop the world, we just invalidate the executors but leave them to be freed
@@ -1772,6 +1730,8 @@ _Py_Executors_InvalidateAllLockHeld(PyInterpreterState *interp, int is_invalidat
17721730
for (_PyExecutorObject *exec = ((_PyThreadStateImpl *)p)->jit_executor_state.executor_list_head; exec != NULL;) {
17731731
assert(exec->tstate == p);
17741732
FT_ATOMIC_STORE_UINT8(exec->vm_data.valid, 0);
1733+
// Let it be cleared up by cold executor invalidation later.
1734+
exec->vm_data.warm = 0;
17751735
if (is_invalidation) {
17761736
OPT_STAT_INC(executors_invalidated);
17771737
}
@@ -1854,8 +1814,7 @@ _Py_Executors_InvalidateColdGC(PyInterpreterState *interp)
18541814
return;
18551815
}
18561816
_PyEval_StopTheWorld(interp);
1857-
HEAD_LOCK(interp->runtime);
1858-
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p)
1817+
_Py_FOR_EACH_TSTATE_BEGIN(interp, p)
18591818
{
18601819
PyObject *more_invalidate = _Py_Executors_CollectCold(p);
18611820
if (more_invalidate == NULL) {
@@ -1867,7 +1826,7 @@ _Py_Executors_InvalidateColdGC(PyInterpreterState *interp)
18671826
goto error;
18681827
}
18691828
}
1870-
HEAD_UNLOCK(interp->runtime);
1829+
_Py_FOR_EACH_TSTATE_END(interp);
18711830
_PyEval_StartTheWorld(interp);
18721831
// We can only clear the list after unlocking the runtime.
18731832
// Otherwise, it may deadlock.

Python/pystate.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,8 +1757,7 @@ PyThreadState_Clear(PyThreadState *tstate)
17571757
Py_CLEAR(tstate->context);
17581758

17591759
#ifdef _Py_TIER2
1760-
((_PyThreadStateImpl *)(tstate))->jit_executor_state.jit = false;
1761-
_Py_ClearExecutorDeletionList(tstate);
1760+
FT_ATOMIC_STORE_CHAR_RELAXED(((_PyThreadStateImpl *)(tstate))->jit_executor_state.jit, false);
17621761

17631762
// We can't clear the cold executors here until the interpreter
17641763
// clear process starts.

0 commit comments

Comments
 (0)