From 0251311e5d570c8727800060b670735eb0d9305d Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 3 Nov 2026 10:00:07 +0100 Subject: [PATCH 1/3] Add a freelist for traceback objects A traceback object is created for every raised exception. Keep up to 8 of them on a per-thread freelist, and replace the argument check in tb_create_raw() with asserts since all callers validate their arguments. Co-Authored-By: Claude Fable 5.1 --- Include/internal/pycore_freelist_state.h | 2 ++ Objects/object.c | 1 + Python/traceback.c | 16 +++++++++------- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_freelist_state.h b/Include/internal/pycore_freelist_state.h index 46e2a82ea03456..266703a63b61f1 100644 --- a/Include/internal/pycore_freelist_state.h +++ b/Include/internal/pycore_freelist_state.h @@ -31,6 +31,7 @@ extern "C" { # define Py_pycfunctionobject_MAXFREELIST 16 # define Py_pycmethodobject_MAXFREELIST 16 # define Py_pymethodobjects_MAXFREELIST 20 +# define Py_tracebacks_MAXFREELIST 8 // A generic freelist of either PyObjects or other data structures. struct _Py_freelist { @@ -66,6 +67,7 @@ struct _Py_freelists { struct _Py_freelist pycfunctionobject; struct _Py_freelist pycmethodobject; struct _Py_freelist pymethodobjects; + struct _Py_freelist tracebacks; }; #ifdef __cplusplus diff --git a/Objects/object.c b/Objects/object.c index f8206e5d3eca32..757e54aa1d487d 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -956,6 +956,7 @@ _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization) clear_freelist(&freelists->pycfunctionobject, is_finalization, PyObject_GC_Del); clear_freelist(&freelists->pycmethodobject, is_finalization, PyObject_GC_Del); clear_freelist(&freelists->pymethodobjects, is_finalization, free_object); + clear_freelist(&freelists->tracebacks, is_finalization, PyObject_GC_Del); } /* diff --git a/Python/traceback.c b/Python/traceback.c index 7dc72d48274567..2b267bbe3b0d38 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -4,6 +4,7 @@ #include "Python.h" #include "pycore_call.h" // _PyObject_CallMethod() #include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH +#include "pycore_freelist.h" // _Py_FREELIST_POP() #include "pycore_frame.h" // PyFrameObject #include "pycore_interp.h" // PyInterpreterState.gc #include "pycore_interpframe.h" // _PyFrame_GetCode() @@ -79,15 +80,15 @@ tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti, int lineno) { PyTracebackObject *tb; - if ((next != NULL && !PyTraceBack_Check(next)) || - frame == NULL || !PyFrame_Check(frame)) { - PyErr_BadInternalCall(); - return NULL; + assert(next == NULL || PyTraceBack_Check(next)); + assert(frame != NULL && PyFrame_Check(frame)); + tb = _Py_FREELIST_POP(PyTracebackObject, tracebacks); + if (tb == NULL) { + tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type); } - tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type); if (tb != NULL) { tb->tb_next = (PyTracebackObject*)Py_XNewRef(next); - tb->tb_frame = (PyFrameObject*)Py_XNewRef(frame); + tb->tb_frame = (PyFrameObject*)Py_NewRef(frame); tb->tb_lasti = lasti; tb->tb_lineno = lineno; PyObject_GC_Track(tb); @@ -237,7 +238,8 @@ tb_dealloc(PyObject *op) PyObject_GC_UnTrack(tb); Py_XDECREF(tb->tb_next); Py_XDECREF(tb->tb_frame); - PyObject_GC_Del(tb); + assert(PyTraceBack_Check(tb)); + _Py_FREELIST_FREE(tracebacks, tb, PyObject_GC_Del); } static int From 6765b383bed06786448dc6b4c81e53d65c88d5e2 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 3 Nov 2026 17:34:40 +0100 Subject: [PATCH 2/3] Apply suggestion from @eendebakpt --- Python/traceback.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/traceback.c b/Python/traceback.c index 2b267bbe3b0d38..5ff6133191bb76 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -81,7 +81,8 @@ tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti, { PyTracebackObject *tb; assert(next == NULL || PyTraceBack_Check(next)); - assert(frame != NULL && PyFrame_Check(frame)); + assert(frame != NULL); + assert(PyFrame_Check(frame)); tb = _Py_FREELIST_POP(PyTracebackObject, tracebacks); if (tb == NULL) { tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type); From ed4db4c8f5bd7323fc1b1c45144d5fe44f0d91ca Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 6 Nov 2026 09:25:34 +0100 Subject: [PATCH 3/3] Apply suggestion from @eendebakpt --- Python/traceback.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/traceback.c b/Python/traceback.c index 5ff6133191bb76..7e99dcb5a99249 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -239,7 +239,6 @@ tb_dealloc(PyObject *op) PyObject_GC_UnTrack(tb); Py_XDECREF(tb->tb_next); Py_XDECREF(tb->tb_frame); - assert(PyTraceBack_Check(tb)); _Py_FREELIST_FREE(tracebacks, tb, PyObject_GC_Del); }