Skip to content

Commit 85f6430

Browse files
authored
bpo-30695: Add set_nomemory(start, stop) to _testcapi (GH-2406)
1 parent 49bc743 commit 85f6430

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

Lib/test/test_capi.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import unittest
1616
from test import support
1717
from test.support import MISSING_C_DOCSTRINGS
18-
from test.support.script_helper import assert_python_failure
18+
from test.support.script_helper import assert_python_failure, assert_python_ok
1919
try:
2020
import _posixsubprocess
2121
except ImportError:
@@ -243,6 +243,38 @@ def test_return_result_with_error(self):
243243
def test_buildvalue_N(self):
244244
_testcapi.test_buildvalue_N()
245245

246+
def test_set_nomemory(self):
247+
code = """if 1:
248+
import _testcapi
249+
250+
class C(): pass
251+
252+
# The first loop tests both functions and that remove_mem_hooks()
253+
# can be called twice in a row. The second loop checks a call to
254+
# set_nomemory() after a call to remove_mem_hooks(). The third
255+
# loop checks the start and stop arguments of set_nomemory().
256+
for outer_cnt in range(1, 4):
257+
start = 10 * outer_cnt
258+
for j in range(100):
259+
if j == 0:
260+
if outer_cnt != 3:
261+
_testcapi.set_nomemory(start)
262+
else:
263+
_testcapi.set_nomemory(start, start + 1)
264+
try:
265+
C()
266+
except MemoryError as e:
267+
if outer_cnt != 3:
268+
_testcapi.remove_mem_hooks()
269+
print('MemoryError', outer_cnt, j)
270+
_testcapi.remove_mem_hooks()
271+
break
272+
"""
273+
rc, out, err = assert_python_ok('-c', code)
274+
self.assertIn(b'MemoryError 1 10', out)
275+
self.assertIn(b'MemoryError 2 20', out)
276+
self.assertIn(b'MemoryError 3 30', out)
277+
246278

247279
@unittest.skipUnless(threading, 'Threading required for this test.')
248280
class TestPendingCalls(unittest.TestCase):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add the `set_nomemory(start, stop)` and `remove_mem_hooks()` functions to
2+
the _testcapi module.

Modules/_testcapimodule.c

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3420,6 +3420,130 @@ test_pyobject_setallocators(PyObject *self)
34203420
return test_setallocators(PYMEM_DOMAIN_OBJ);
34213421
}
34223422

3423+
/* Most part of the following code is inherited from the pyfailmalloc project
3424+
* written by Victor Stinner. */
3425+
static struct {
3426+
int installed;
3427+
PyMemAllocatorEx raw;
3428+
PyMemAllocatorEx mem;
3429+
PyMemAllocatorEx obj;
3430+
} FmHook;
3431+
3432+
static struct {
3433+
int start;
3434+
int stop;
3435+
Py_ssize_t count;
3436+
} FmData;
3437+
3438+
static int
3439+
fm_nomemory(void)
3440+
{
3441+
FmData.count++;
3442+
if (FmData.count > FmData.start &&
3443+
(FmData.stop <= 0 || FmData.count <= FmData.stop)) {
3444+
return 1;
3445+
}
3446+
return 0;
3447+
}
3448+
3449+
static void *
3450+
hook_fmalloc(void *ctx, size_t size)
3451+
{
3452+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3453+
if (fm_nomemory()) {
3454+
return NULL;
3455+
}
3456+
return alloc->malloc(alloc->ctx, size);
3457+
}
3458+
3459+
static void *
3460+
hook_fcalloc(void *ctx, size_t nelem, size_t elsize)
3461+
{
3462+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3463+
if (fm_nomemory()) {
3464+
return NULL;
3465+
}
3466+
return alloc->calloc(alloc->ctx, nelem, elsize);
3467+
}
3468+
3469+
static void *
3470+
hook_frealloc(void *ctx, void *ptr, size_t new_size)
3471+
{
3472+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3473+
if (fm_nomemory()) {
3474+
return NULL;
3475+
}
3476+
return alloc->realloc(alloc->ctx, ptr, new_size);
3477+
}
3478+
3479+
static void
3480+
hook_ffree(void *ctx, void *ptr)
3481+
{
3482+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3483+
alloc->free(alloc->ctx, ptr);
3484+
}
3485+
3486+
static void
3487+
fm_setup_hooks(void)
3488+
{
3489+
PyMemAllocatorEx alloc;
3490+
3491+
if (FmHook.installed) {
3492+
return;
3493+
}
3494+
FmHook.installed = 1;
3495+
3496+
alloc.malloc = hook_fmalloc;
3497+
alloc.calloc = hook_fcalloc;
3498+
alloc.realloc = hook_frealloc;
3499+
alloc.free = hook_ffree;
3500+
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &FmHook.raw);
3501+
PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &FmHook.mem);
3502+
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &FmHook.obj);
3503+
3504+
alloc.ctx = &FmHook.raw;
3505+
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
3506+
3507+
alloc.ctx = &FmHook.mem;
3508+
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
3509+
3510+
alloc.ctx = &FmHook.obj;
3511+
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
3512+
}
3513+
3514+
static void
3515+
fm_remove_hooks(void)
3516+
{
3517+
if (FmHook.installed) {
3518+
FmHook.installed = 0;
3519+
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &FmHook.raw);
3520+
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &FmHook.mem);
3521+
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &FmHook.obj);
3522+
}
3523+
}
3524+
3525+
static PyObject*
3526+
set_nomemory(PyObject *self, PyObject *args)
3527+
{
3528+
/* Memory allocation fails after 'start' allocation requests, and until
3529+
* 'stop' allocation requests except when 'stop' is negative or equal
3530+
* to 0 (default) in which case allocation failures never stop. */
3531+
FmData.count = 0;
3532+
FmData.stop = 0;
3533+
if (!PyArg_ParseTuple(args, "i|i", &FmData.start, &FmData.stop)) {
3534+
return NULL;
3535+
}
3536+
fm_setup_hooks();
3537+
Py_RETURN_NONE;
3538+
}
3539+
3540+
static PyObject*
3541+
remove_mem_hooks(PyObject *self)
3542+
{
3543+
fm_remove_hooks();
3544+
Py_RETURN_NONE;
3545+
}
3546+
34233547
PyDoc_STRVAR(docstring_empty,
34243548
""
34253549
);
@@ -4287,6 +4411,10 @@ static PyMethodDef TestMethods[] = {
42874411
(PyCFunction)test_pymem_setallocators, METH_NOARGS},
42884412
{"test_pyobject_setallocators",
42894413
(PyCFunction)test_pyobject_setallocators, METH_NOARGS},
4414+
{"set_nomemory", (PyCFunction)set_nomemory, METH_VARARGS,
4415+
PyDoc_STR("set_nomemory(start:int, stop:int = 0)")},
4416+
{"remove_mem_hooks", (PyCFunction)remove_mem_hooks, METH_NOARGS,
4417+
PyDoc_STR("Remove memory hooks.")},
42904418
{"no_docstring",
42914419
(PyCFunction)test_with_docstring, METH_NOARGS},
42924420
{"docstring_empty",

0 commit comments

Comments
 (0)