Commit fe3ab385 authored by Victor Stinner's avatar Victor Stinner

Closes #19786: tracemalloc, remove the arbitrary limit of 100 frames

The limit is now 178,956,969 on 64 bit (it is greater on 32 bit because
structures are smaller).

Use int instead of Py_ssize_t to store the number of frames to have smaller
traceback_t objects.
parent 4f3802ed
...@@ -747,14 +747,14 @@ class TestCommandLine(unittest.TestCase): ...@@ -747,14 +747,14 @@ class TestCommandLine(unittest.TestCase):
self.assertEqual(stdout, b'10') self.assertEqual(stdout, b'10')
def test_env_var_invalid(self): def test_env_var_invalid(self):
for nframe in (-1, 0, 5000): for nframe in (-1, 0, 2**30):
with self.subTest(nframe=nframe): with self.subTest(nframe=nframe):
with support.SuppressCrashReport(): with support.SuppressCrashReport():
ok, stdout, stderr = assert_python_failure( ok, stdout, stderr = assert_python_failure(
'-c', 'pass', '-c', 'pass',
PYTHONTRACEMALLOC=str(nframe)) PYTHONTRACEMALLOC=str(nframe))
self.assertIn(b'PYTHONTRACEMALLOC must be an integer ' self.assertIn(b'PYTHONTRACEMALLOC: invalid '
b'in range [1; 100]', b'number of frames',
stderr) stderr)
def test_sys_xoptions(self): def test_sys_xoptions(self):
...@@ -770,13 +770,13 @@ class TestCommandLine(unittest.TestCase): ...@@ -770,13 +770,13 @@ class TestCommandLine(unittest.TestCase):
self.assertEqual(stdout, str(nframe).encode('ascii')) self.assertEqual(stdout, str(nframe).encode('ascii'))
def test_sys_xoptions_invalid(self): def test_sys_xoptions_invalid(self):
for nframe in (-1, 0, 5000): for nframe in (-1, 0, 2**30):
with self.subTest(nframe=nframe): with self.subTest(nframe=nframe):
with support.SuppressCrashReport(): with support.SuppressCrashReport():
args = ('-X', 'tracemalloc=%s' % nframe, '-c', 'pass') args = ('-X', 'tracemalloc=%s' % nframe, '-c', 'pass')
ok, stdout, stderr = assert_python_failure(*args) ok, stdout, stderr = assert_python_failure(*args)
self.assertIn(b'-X tracemalloc=NFRAME: number of frame must ' self.assertIn(b'-X tracemalloc=NFRAME: invalid '
b'be an integer in range [1; 100]', b'number of frames',
stderr) stderr)
......
...@@ -27,11 +27,6 @@ static struct { ...@@ -27,11 +27,6 @@ static struct {
PyMemAllocator obj; PyMemAllocator obj;
} allocators; } allocators;
/* Arbitrary limit of the number of frames in a traceback. The value was chosen
to not allocate too much memory on the stack (see TRACEBACK_STACK_SIZE
below). */
#define MAX_NFRAME 100
static struct { static struct {
/* Module initialized? /* Module initialized?
Variable protected by the GIL */ Variable protected by the GIL */
...@@ -88,7 +83,9 @@ typedef struct { ...@@ -88,7 +83,9 @@ typedef struct {
#define TRACEBACK_SIZE(NFRAME) \ #define TRACEBACK_SIZE(NFRAME) \
(sizeof(traceback_t) + sizeof(frame_t) * (NFRAME - 1)) (sizeof(traceback_t) + sizeof(frame_t) * (NFRAME - 1))
#define TRACEBACK_STACK_SIZE TRACEBACK_SIZE(MAX_NFRAME)
#define MAX_NFRAME \
((INT_MAX - sizeof(traceback_t)) / sizeof(frame_t) + 1)
static PyObject *unknown_filename = NULL; static PyObject *unknown_filename = NULL;
static traceback_t tracemalloc_empty_traceback; static traceback_t tracemalloc_empty_traceback;
...@@ -115,6 +112,10 @@ static size_t tracemalloc_peak_traced_memory = 0; ...@@ -115,6 +112,10 @@ static size_t tracemalloc_peak_traced_memory = 0;
Protected by the GIL */ Protected by the GIL */
static _Py_hashtable_t *tracemalloc_filenames = NULL; static _Py_hashtable_t *tracemalloc_filenames = NULL;
/* Buffer to store a new traceback in traceback_new().
Protected by the GIL. */
static traceback_t *tracemalloc_traceback = NULL;
/* Hash table used as a set to intern tracebacks: /* Hash table used as a set to intern tracebacks:
traceback_t* => traceback_t* traceback_t* => traceback_t*
Protected by the GIL */ Protected by the GIL */
...@@ -394,8 +395,7 @@ traceback_get_frames(traceback_t *traceback) ...@@ -394,8 +395,7 @@ traceback_get_frames(traceback_t *traceback)
static traceback_t * static traceback_t *
traceback_new(void) traceback_new(void)
{ {
char stack_buffer[TRACEBACK_STACK_SIZE]; traceback_t *traceback;
traceback_t *traceback = (traceback_t *)stack_buffer;
_Py_hashtable_entry_t *entry; _Py_hashtable_entry_t *entry;
#ifdef WITH_THREAD #ifdef WITH_THREAD
...@@ -403,6 +403,7 @@ traceback_new(void) ...@@ -403,6 +403,7 @@ traceback_new(void)
#endif #endif
/* get frames */ /* get frames */
traceback = tracemalloc_traceback;
traceback->nframe = 0; traceback->nframe = 0;
traceback_get_frames(traceback); traceback_get_frames(traceback);
if (traceback->nframe == 0) if (traceback->nframe == 0)
...@@ -788,9 +789,10 @@ tracemalloc_deinit(void) ...@@ -788,9 +789,10 @@ tracemalloc_deinit(void)
} }
static int static int
tracemalloc_start(void) tracemalloc_start(int max_nframe)
{ {
PyMemAllocator alloc; PyMemAllocator alloc;
size_t size;
if (tracemalloc_init() < 0) if (tracemalloc_init() < 0)
return -1; return -1;
...@@ -803,6 +805,18 @@ tracemalloc_start(void) ...@@ -803,6 +805,18 @@ tracemalloc_start(void)
if (tracemalloc_atexit_register() < 0) if (tracemalloc_atexit_register() < 0)
return -1; return -1;
assert(1 <= max_nframe && max_nframe <= MAX_NFRAME);
tracemalloc_config.max_nframe = max_nframe;
/* allocate a buffer to store a new traceback */
size = TRACEBACK_SIZE(max_nframe);
assert(tracemalloc_traceback == NULL);
tracemalloc_traceback = raw_malloc(size);
if (tracemalloc_traceback == NULL) {
PyErr_NoMemory();
return -1;
}
#ifdef TRACE_RAW_MALLOC #ifdef TRACE_RAW_MALLOC
alloc.malloc = tracemalloc_raw_malloc; alloc.malloc = tracemalloc_raw_malloc;
alloc.realloc = tracemalloc_raw_realloc; alloc.realloc = tracemalloc_raw_realloc;
...@@ -854,9 +868,10 @@ tracemalloc_stop(void) ...@@ -854,9 +868,10 @@ tracemalloc_stop(void)
/* release memory */ /* release memory */
tracemalloc_clear_traces(); tracemalloc_clear_traces();
raw_free(tracemalloc_traceback);
tracemalloc_traceback = NULL;
} }
static PyObject* static PyObject*
lineno_as_obj(int lineno) lineno_as_obj(int lineno)
{ {
...@@ -1194,6 +1209,7 @@ static PyObject* ...@@ -1194,6 +1209,7 @@ static PyObject*
py_tracemalloc_start(PyObject *self, PyObject *args) py_tracemalloc_start(PyObject *self, PyObject *args)
{ {
Py_ssize_t nframe = 1; Py_ssize_t nframe = 1;
int nframe_int;
if (!PyArg_ParseTuple(args, "|n:start", &nframe)) if (!PyArg_ParseTuple(args, "|n:start", &nframe))
return NULL; return NULL;
...@@ -1201,12 +1217,12 @@ py_tracemalloc_start(PyObject *self, PyObject *args) ...@@ -1201,12 +1217,12 @@ py_tracemalloc_start(PyObject *self, PyObject *args)
if (nframe < 1 || nframe > MAX_NFRAME) { if (nframe < 1 || nframe > MAX_NFRAME) {
PyErr_Format(PyExc_ValueError, PyErr_Format(PyExc_ValueError,
"the number of frames must be in range [1; %i]", "the number of frames must be in range [1; %i]",
MAX_NFRAME); (int)MAX_NFRAME);
return NULL; return NULL;
} }
tracemalloc_config.max_nframe = Py_SAFE_DOWNCAST(nframe, Py_ssize_t, int); nframe_int = Py_SAFE_DOWNCAST(nframe, Py_ssize_t, int);
if (tracemalloc_start() < 0) if (tracemalloc_start(nframe_int) < 0)
return NULL; return NULL;
Py_RETURN_NONE; Py_RETURN_NONE;
...@@ -1378,16 +1394,15 @@ _PyTraceMalloc_Init(void) ...@@ -1378,16 +1394,15 @@ _PyTraceMalloc_Init(void)
if ((p = Py_GETENV("PYTHONTRACEMALLOC")) && *p != '\0') { if ((p = Py_GETENV("PYTHONTRACEMALLOC")) && *p != '\0') {
char *endptr = p; char *endptr = p;
unsigned long value; long value;
value = strtoul(p, &endptr, 10); value = strtol(p, &endptr, 10);
if (*endptr != '\0' if (*endptr != '\0'
|| value < 1 || value < 1
|| value > MAX_NFRAME || value > MAX_NFRAME
|| (errno == ERANGE && value == ULONG_MAX)) || (errno == ERANGE && value == ULONG_MAX))
{ {
Py_FatalError("PYTHONTRACEMALLOC must be an integer " Py_FatalError("PYTHONTRACEMALLOC: invalid number of frames");
"in range [1; " STR(MAX_NFRAME) "]");
return -1; return -1;
} }
...@@ -1417,12 +1432,10 @@ _PyTraceMalloc_Init(void) ...@@ -1417,12 +1432,10 @@ _PyTraceMalloc_Init(void)
nframe = parse_sys_xoptions(value); nframe = parse_sys_xoptions(value);
Py_DECREF(value); Py_DECREF(value);
if (nframe < 0) { if (nframe < 0) {
Py_FatalError("-X tracemalloc=NFRAME: number of frame must be " Py_FatalError("-X tracemalloc=NFRAME: invalid number of frames");
"an integer in range [1; " STR(MAX_NFRAME) "]");
} }
} }
tracemalloc_config.max_nframe = nframe; return tracemalloc_start(nframe);
return tracemalloc_start();
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment