Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import unittest
from test import support
from test.support import MISSING_C_DOCSTRINGS
from test.support.script_helper import assert_python_failure
from test.support.script_helper import assert_python_failure, assert_python_ok
try:
import _posixsubprocess
except ImportError:
Expand Down Expand Up @@ -243,6 +243,38 @@ def test_return_result_with_error(self):
def test_buildvalue_N(self):
_testcapi.test_buildvalue_N()

def test_set_nomemory(self):
code = """if 1:
import _testcapi

class C(): pass

# The first loop tests both functions and that remove_mem_hooks()
# can be called twice in a row. The second loop checks a call to
# set_nomemory() after a call to remove_mem_hooks(). The third
# loop checks the start and stop arguments of set_nomemory().
for outer_cnt in range(1, 4):
start = 10 * outer_cnt
for j in range(100):
if j == 0:
if outer_cnt != 3:
_testcapi.set_nomemory(start)
else:
_testcapi.set_nomemory(start, start + 1)
try:
C()
except MemoryError as e:
if outer_cnt != 3:
_testcapi.remove_mem_hooks()
print('MemoryError', outer_cnt, j)
_testcapi.remove_mem_hooks()
break
"""
rc, out, err = assert_python_ok('-c', code)
self.assertIn(b'MemoryError 1 10', out)
self.assertIn(b'MemoryError 2 20', out)
self.assertIn(b'MemoryError 3 30', out)


@unittest.skipUnless(threading, 'Threading required for this test.')
class TestPendingCalls(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add the `set_nomemory(start, stop)` and `remove_mem_hooks()` functions to
the _testcapi module.
128 changes: 128 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3420,6 +3420,130 @@ test_pyobject_setallocators(PyObject *self)
return test_setallocators(PYMEM_DOMAIN_OBJ);
}

/* Most part of the following code is inherited from the pyfailmalloc project
* written by Victor Stinner. */
static struct {
int installed;
PyMemAllocatorEx raw;
PyMemAllocatorEx mem;
PyMemAllocatorEx obj;
} FmHook;

static struct {
int start;
int stop;
Py_ssize_t count;
} FmData;

static int
fm_nomemory(void)
{
FmData.count++;
if (FmData.count > FmData.start &&
(FmData.stop <= 0 || FmData.count <= FmData.stop)) {
return 1;
}
return 0;
}

static void *
hook_fmalloc(void *ctx, size_t size)
{
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
if (fm_nomemory()) {
return NULL;
}
return alloc->malloc(alloc->ctx, size);
}

static void *
hook_fcalloc(void *ctx, size_t nelem, size_t elsize)
{
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
if (fm_nomemory()) {
return NULL;
}
return alloc->calloc(alloc->ctx, nelem, elsize);
}

static void *
hook_frealloc(void *ctx, void *ptr, size_t new_size)
{
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
if (fm_nomemory()) {
return NULL;
}
return alloc->realloc(alloc->ctx, ptr, new_size);
}

static void
hook_ffree(void *ctx, void *ptr)
{
PyMemAllocatorEx *alloc = (PyMemAllocatorEx *)ctx;
alloc->free(alloc->ctx, ptr);
}

static void
fm_setup_hooks(void)
{
PyMemAllocatorEx alloc;

if (FmHook.installed) {
return;
}
FmHook.installed = 1;

alloc.malloc = hook_fmalloc;
alloc.calloc = hook_fcalloc;
alloc.realloc = hook_frealloc;
alloc.free = hook_ffree;
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &FmHook.raw);
PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &FmHook.mem);
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &FmHook.obj);

alloc.ctx = &FmHook.raw;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);

alloc.ctx = &FmHook.mem;
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);

alloc.ctx = &FmHook.obj;
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
}

static void
fm_remove_hooks(void)
{
if (FmHook.installed) {
FmHook.installed = 0;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &FmHook.raw);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &FmHook.mem);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &FmHook.obj);
}
}

static PyObject*
set_nomemory(PyObject *self, PyObject *args)
{
/* Memory allocation fails after 'start' allocation requests, and until
* 'stop' allocation requests except when 'stop' is negative or equal
* to 0 (default) in which case allocation failures never stop. */
FmData.count = 0;
FmData.stop = 0;
if (!PyArg_ParseTuple(args, "i|i", &FmData.start, &FmData.stop)) {
return NULL;
}
fm_setup_hooks();
Py_RETURN_NONE;
}

static PyObject*
remove_mem_hooks(PyObject *self)
{
fm_remove_hooks();
Py_RETURN_NONE;
}

PyDoc_STRVAR(docstring_empty,
""
);
Expand Down Expand Up @@ -4287,6 +4411,10 @@ static PyMethodDef TestMethods[] = {
(PyCFunction)test_pymem_setallocators, METH_NOARGS},
{"test_pyobject_setallocators",
(PyCFunction)test_pyobject_setallocators, METH_NOARGS},
{"set_nomemory", (PyCFunction)set_nomemory, METH_VARARGS,
PyDoc_STR("set_nomemory(start:int, stop:int = 0)")},
{"remove_mem_hooks", (PyCFunction)remove_mem_hooks, METH_NOARGS,
PyDoc_STR("Remove memory hooks.")},
{"no_docstring",
(PyCFunction)test_with_docstring, METH_NOARGS},
{"docstring_empty",
Expand Down