Skip to content

Commit aaf6a3d

Browse files
miss-islingtonxdegaye
authored andcommitted
[3.6] bpo-30695: Add set_nomemory(start, stop) to _testcapi (GH-2406) (#4083)
(cherry picked from commit 85f6430)
1 parent 77af0a3 commit aaf6a3d

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
@@ -13,7 +13,7 @@
1313
import unittest
1414
from test import support
1515
from test.support import MISSING_C_DOCSTRINGS
16-
from test.support.script_helper import assert_python_failure
16+
from test.support.script_helper import assert_python_failure, assert_python_ok
1717
try:
1818
import _posixsubprocess
1919
except ImportError:
@@ -241,6 +241,38 @@ def test_return_result_with_error(self):
241241
def test_buildvalue_N(self):
242242
_testcapi.test_buildvalue_N()
243243

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

245277
@unittest.skipUnless(threading, 'Threading required for this test.')
246278
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
@@ -3437,6 +3437,130 @@ test_pyobject_setallocators(PyObject *self)
34373437
return test_setallocators(PYMEM_DOMAIN_OBJ);
34383438
}
34393439

3440+
/* Most part of the following code is inherited from the pyfailmalloc project
3441+
* written by Victor Stinner. */
3442+
static struct {
3443+
int installed;
3444+
PyMemAllocatorEx raw;
3445+
PyMemAllocatorEx mem;
3446+
PyMemAllocatorEx obj;
3447+
} FmHook;
3448+
3449+
static struct {
3450+
int start;
3451+
int stop;
3452+
Py_ssize_t count;
3453+
} FmData;
3454+
3455+
static int
3456+
fm_nomemory(void)
3457+
{
3458+
FmData.count++;
3459+
if (FmData.count > FmData.start &&
3460+
(FmData.stop <= 0 || FmData.count <= FmData.stop)) {
3461+
return 1;
3462+
}
3463+
return 0;
3464+
}
3465+
3466+
static void *
3467+
hook_fmalloc(void *ctx, size_t size)
3468+
{
3469+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3470+
if (fm_nomemory()) {
3471+
return NULL;
3472+
}
3473+
return alloc->malloc(alloc->ctx, size);
3474+
}
3475+
3476+
static void *
3477+
hook_fcalloc(void *ctx, size_t nelem, size_t elsize)
3478+
{
3479+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3480+
if (fm_nomemory()) {
3481+
return NULL;
3482+
}
3483+
return alloc->calloc(alloc->ctx, nelem, elsize);
3484+
}
3485+
3486+
static void *
3487+
hook_frealloc(void *ctx, void *ptr, size_t new_size)
3488+
{
3489+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3490+
if (fm_nomemory()) {
3491+
return NULL;
3492+
}
3493+
return alloc->realloc(alloc->ctx, ptr, new_size);
3494+
}
3495+
3496+
static void
3497+
hook_ffree(void *ctx, void *ptr)
3498+
{
3499+
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
3500+
alloc->free(alloc->ctx, ptr);
3501+
}
3502+
3503+
static void
3504+
fm_setup_hooks(void)
3505+
{
3506+
PyMemAllocatorEx alloc;
3507+
3508+
if (FmHook.installed) {
3509+
return;
3510+
}
3511+
FmHook.installed = 1;
3512+
3513+
alloc.malloc = hook_fmalloc;
3514+
alloc.calloc = hook_fcalloc;
3515+
alloc.realloc = hook_frealloc;
3516+
alloc.free = hook_ffree;
3517+
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &FmHook.raw);
3518+
PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &FmHook.mem);
3519+
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &FmHook.obj);
3520+
3521+
alloc.ctx = &FmHook.raw;
3522+
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
3523+
3524+
alloc.ctx = &FmHook.mem;
3525+
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
3526+
3527+
alloc.ctx = &FmHook.obj;
3528+
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
3529+
}
3530+
3531+
static void
3532+
fm_remove_hooks(void)
3533+
{
3534+
if (FmHook.installed) {
3535+
FmHook.installed = 0;
3536+
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &FmHook.raw);
3537+
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &FmHook.mem);
3538+
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &FmHook.obj);
3539+
}
3540+
}
3541+
3542+
static PyObject*
3543+
set_nomemory(PyObject *self, PyObject *args)
3544+
{
3545+
/* Memory allocation fails after 'start' allocation requests, and until
3546+
* 'stop' allocation requests except when 'stop' is negative or equal
3547+
* to 0 (default) in which case allocation failures never stop. */
3548+
FmData.count = 0;
3549+
FmData.stop = 0;
3550+
if (!PyArg_ParseTuple(args, "i|i", &FmData.start, &FmData.stop)) {
3551+
return NULL;
3552+
}
3553+
fm_setup_hooks();
3554+
Py_RETURN_NONE;
3555+
}
3556+
3557+
static PyObject*
3558+
remove_mem_hooks(PyObject *self)
3559+
{
3560+
fm_remove_hooks();
3561+
Py_RETURN_NONE;
3562+
}
3563+
34403564
PyDoc_STRVAR(docstring_empty,
34413565
""
34423566
);
@@ -4318,6 +4442,10 @@ static PyMethodDef TestMethods[] = {
43184442
(PyCFunction)test_pymem_setallocators, METH_NOARGS},
43194443
{"test_pyobject_setallocators",
43204444
(PyCFunction)test_pyobject_setallocators, METH_NOARGS},
4445+
{"set_nomemory", (PyCFunction)set_nomemory, METH_VARARGS,
4446+
PyDoc_STR("set_nomemory(start:int, stop:int = 0)")},
4447+
{"remove_mem_hooks", (PyCFunction)remove_mem_hooks, METH_NOARGS,
4448+
PyDoc_STR("Remove memory hooks.")},
43214449
{"no_docstring",
43224450
(PyCFunction)test_with_docstring, METH_NOARGS},
43234451
{"docstring_empty",

0 commit comments

Comments
 (0)