Skip to content

Commit eefa59b

Browse files
committed
gh-161456: refresh C stack limits after setrlimit(RLIMIT_STACK)
1 parent d746542 commit eefa59b

5 files changed

Lines changed: 65 additions & 2 deletions

File tree

‎Include/internal/pycore_ceval.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) {
245245
}
246246

247247
PyAPI_FUNC(void) _Py_InitializeRecursionLimits(PyThreadState *tstate);
248+
PyAPI_FUNC(void) _Py_UpdateRecursionLimits(PyThreadState *tstate);
248249

249250
// Export for test_peg_generator
250251
PyAPI_FUNC(int) _Py_ReachedRecursionLimitWithMargin(

‎Lib/test/test_resource.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import contextlib
22
import sys
3+
import textwrap
34
import unittest
45
from test import support
56
from test.support import import_helper
67
from test.support import os_helper
8+
from test.support import script_helper
79

810
resource = import_helper.import_module('resource')
911

@@ -234,6 +236,35 @@ def __getitem__(self, key):
234236
self.assertEqual(resource.prlimit(0, resource.RLIMIT_AS, BadSeq()),
235237
limits)
236238

239+
@unittest.skipUnless(sys.platform == 'linux', 'Linux only')
240+
@unittest.skipIf(support.linked_to_musl(),
241+
'no C stack detection on musl, gh-159876')
242+
@unittest.skipUnless(hasattr(resource, 'RLIMIT_STACK'),
243+
'requires resource.RLIMIT_STACK')
244+
def test_lowered_stack_limit(self):
245+
code = textwrap.dedent('''
246+
import resource, sys
247+
soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
248+
limits = (1024 * 1024, hard)
249+
if sys.argv[1] == 'setrlimit':
250+
resource.setrlimit(resource.RLIMIT_STACK, limits)
251+
else:
252+
resource.prlimit(0, resource.RLIMIT_STACK, limits)
253+
value = None
254+
for _ in range(100_000):
255+
value = [value]
256+
try:
257+
repr(value)
258+
except RecursionError:
259+
pass
260+
del value
261+
''')
262+
for func in 'setrlimit', 'prlimit':
263+
with self.subTest(func=func):
264+
if not hasattr(resource, func):
265+
self.skipTest(f'requires resource.{func}')
266+
script_helper.assert_python_ok('-c', code, func)
267+
237268

238269
if __name__ == "__main__":
239270
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash on deep recursion after :func:`resource.setrlimit` or
2+
:func:`resource.prlimit` lowers :data:`~resource.RLIMIT_STACK`.

‎Modules/resource.c‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#endif
44

55
#include "Python.h"
6+
#include "pycore_ceval.h" // _Py_UpdateRecursionLimits()
7+
#include "pycore_pystate.h" // _PyThreadState_GET()
68
#include <errno.h> // errno
79
#include <string.h>
810
#include <sys/resource.h> // getrusage()
@@ -283,6 +285,11 @@ resource_setrlimit_impl(PyObject *module, int resource, PyObject *limits)
283285
PyErr_SetFromErrno(PyExc_OSError);
284286
return NULL;
285287
}
288+
#ifdef RLIMIT_STACK
289+
if (resource == RLIMIT_STACK) {
290+
_Py_UpdateRecursionLimits(_PyThreadState_GET());
291+
}
292+
#endif
286293
Py_RETURN_NONE;
287294
}
288295

@@ -335,6 +342,13 @@ resource_prlimit_impl(PyObject *module, pid_t pid, int resource,
335342
}
336343
return NULL;
337344
}
345+
#ifdef RLIMIT_STACK
346+
if (limits != Py_None && resource == RLIMIT_STACK
347+
&& (pid == 0 || pid == getpid()))
348+
{
349+
_Py_UpdateRecursionLimits(_PyThreadState_GET());
350+
}
351+
#endif
338352
return rlimit2py(old_limit);
339353
}
340354
#endif /* HAVE_PRLIMIT */

‎Python/ceval.c‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,20 +176,35 @@ tstate_set_stack(PyThreadState *tstate,
176176
}
177177

178178

179-
void
180-
_Py_InitializeRecursionLimits(PyThreadState *tstate)
179+
static void
180+
set_recursion_limits(PyThreadState *tstate, uintptr_t min_size)
181181
{
182182
uintptr_t base, top;
183183
uintptr_t here_addr = _Py_get_machine_stack_pointer();
184184
hardware_stack_limits(&base, &top, here_addr);
185185
assert(top != 0);
186+
if (top - base < min_size) {
187+
return;
188+
}
186189

187190
tstate_set_stack(tstate, base, top);
188191
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)tstate;
189192
ts->c_stack_init_base = base;
190193
ts->c_stack_init_top = top;
191194
}
192195

196+
void
197+
_Py_InitializeRecursionLimits(PyThreadState *tstate)
198+
{
199+
set_recursion_limits(tstate, 0);
200+
}
201+
202+
void
203+
_Py_UpdateRecursionLimits(PyThreadState *tstate)
204+
{
205+
set_recursion_limits(tstate, _PyOS_MIN_STACK_SIZE);
206+
}
207+
193208

194209
int
195210
PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate,

0 commit comments

Comments
 (0)