mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									3c0481d426
								
							
						
					
					
						commit
						f28ce60441
					
				
					 2 changed files with 40 additions and 27 deletions
				
			
		|  | @ -747,14 +747,14 @@ def test_env_var(self): | ||||||
|         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 @@ def test_sys_xoptions(self): | ||||||
|                 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 { | ||||||
|     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 { | ||||||
| 
 | 
 | ||||||
| #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; | ||||||
|    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) | ||||||
| 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) | ||||||
| #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) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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) | ||||||
|     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) | ||||||
| 
 | 
 | ||||||
|     /* 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* | ||||||
| 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) | ||||||
|     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) | ||||||
| 
 | 
 | ||||||
|     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) | ||||||
|         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(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Victor Stinner
						Victor Stinner