From 030abcd5dafd601779f0db5d5ba41834384b0aed Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 16 Jun 2025 22:31:56 +0000 Subject: [PATCH 1/3] gh-135106: Randomly return `1` in _Py_RecursionLimit_GetMargin Returns `1` a little less than 1% of the time in _Py_RecursionLimit_GetMargin() to force the use of the trashcan mechanism. --- Include/cpython/pystate.h | 2 ++ Include/internal/pycore_pystate.h | 10 ++++++++++ Python/pystate.c | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 54d7e62292966e..51ea2d2f0bd37c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -208,6 +208,8 @@ struct _ts { */ PyObject *threading_local_sentinel; _PyRemoteDebuggerSupport remote_debugger_support; + + uint64_t prng; }; /* other API */ diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 633e5cf77db918..addff6e59a143e 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -325,6 +325,16 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; assert(_tstate->c_stack_hard_limit != 0); intptr_t here_addr = _Py_get_machine_stack_pointer(); + + // splitmix64 from https://prng.di.unimi.it/splitmix64.c + uint64_t z = (tstate->prng += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + uint64_t r = z ^ (z >> 31); + if ((r & 0xFF) < 2) { // 2/256 chance = ~ 0.8% chance + return 1; + } + return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT); } diff --git a/Python/pystate.c b/Python/pystate.c index 0544b15aad1cc8..45a53dd399aaba 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1486,6 +1486,10 @@ init_threadstate(_PyThreadStateImpl *_tstate, tstate->state = _Py_THREAD_SUSPENDED; } + PyTime_t now; + PyTime_MonotonicRaw(&now); + tstate->prng = (uint64_t)now; + tstate->_status.initialized = 1; } From 92e82acb1be29b7d0169f31d8cbc66293ca6a137 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Mon, 16 Jun 2025 19:30:30 -0400 Subject: [PATCH 2/3] Update Include/internal/pycore_pystate.h Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- Include/internal/pycore_pystate.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index addff6e59a143e..c69cb02684d894 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -328,9 +328,9 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) // splitmix64 from https://prng.di.unimi.it/splitmix64.c uint64_t z = (tstate->prng += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - uint64_t r = z ^ (z >> 31); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + uint64_t r = z ^ (z >> 31); if ((r & 0xFF) < 2) { // 2/256 chance = ~ 0.8% chance return 1; } From 7c86a25034ce89bcad987ba4d9d2c579dcd46916 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Tue, 17 Jun 2025 16:53:37 +0000 Subject: [PATCH 3/3] Update test so that RNG is in _Py_Dealloc --- Include/cpython/pystate.h | 1 + Include/internal/pycore_pystate.h | 10 ---------- Objects/object.c | 14 +++++++++++++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 51ea2d2f0bd37c..5af979ea2a79e6 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -210,6 +210,7 @@ struct _ts { _PyRemoteDebuggerSupport remote_debugger_support; uint64_t prng; + Py_ssize_t ob_dealloc_depth; }; /* other API */ diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index c69cb02684d894..633e5cf77db918 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -325,16 +325,6 @@ _Py_RecursionLimit_GetMargin(PyThreadState *tstate) _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; assert(_tstate->c_stack_hard_limit != 0); intptr_t here_addr = _Py_get_machine_stack_pointer(); - - // splitmix64 from https://prng.di.unimi.it/splitmix64.c - uint64_t z = (tstate->prng += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - uint64_t r = z ^ (z >> 31); - if ((r & 0xFF) < 2) { // 2/256 chance = ~ 0.8% chance - return 1; - } - return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, here_addr - (intptr_t)_tstate->c_stack_soft_limit, PYOS_STACK_MARGIN_SHIFT); } diff --git a/Objects/object.c b/Objects/object.c index eff3a9862129a2..5c2afdc581cd8f 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -3155,6 +3155,16 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg, Py_FatalError("_PyObject_AssertFailed"); } +static int +should_randomly_deposit_object(PyThreadState *tstate) +{ + // splitmix64 from https://prng.di.unimi.it/splitmix64.c + uint64_t z = (tstate->prng += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + uint64_t r = z ^ (z >> 31); + return (r & 0xFF) < 2; +} /* When deallocating a container object, it's possible to trigger an unbounded @@ -3170,7 +3180,7 @@ _Py_Dealloc(PyObject *op) destructor dealloc = type->tp_dealloc; PyThreadState *tstate = _PyThreadState_GET(); intptr_t margin = _Py_RecursionLimit_GetMargin(tstate); - if (margin < 2) { + if (margin < 2 || (tstate->ob_dealloc_depth > 0 && should_randomly_deposit_object(tstate))) { _PyTrash_thread_deposit_object(tstate, (PyObject *)op); return; } @@ -3192,7 +3202,9 @@ _Py_Dealloc(PyObject *op) _Py_ForgetReference(op); #endif _PyReftracerTrack(op, PyRefTracer_DESTROY); + tstate->ob_dealloc_depth++; (*dealloc)(op); + tstate->ob_dealloc_depth--; #ifdef Py_DEBUG // gh-89373: The tp_dealloc function must leave the current exception