bpo-40521: Make MemoryError free list per interpreter by vstinner · Pull Request #21086 · python/cpython · GitHub
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
8 changes: 8 additions & 0 deletions Include/internal/pycore_interp.h
4 changes: 2 additions & 2 deletions Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ extern PyStatus _PySys_Create(
extern PyStatus _PySys_ReadPreinitWarnOptions(PyWideStringList *options);
extern PyStatus _PySys_ReadPreinitXOptions(PyConfig *config);
extern int _PySys_InitMain(PyThreadState *tstate);
extern PyStatus _PyExc_Init(void);
extern PyStatus _PyExc_Init(PyThreadState *tstate);
extern PyStatus _PyErr_Init(void);
extern PyStatus _PyBuiltins_AddExceptions(PyObject * bltinmod);
extern PyStatus _PyImportHooks_Init(PyThreadState *tstate);
Expand All @@ -69,7 +69,7 @@ extern void _PyAsyncGen_Fini(PyThreadState *tstate);

extern void PyOS_FiniInterrupts(void);

extern void _PyExc_Fini(void);
extern void _PyExc_Fini(PyThreadState *tstate);
extern void _PyImport_Fini(void);
extern void _PyImport_Fini2(void);
extern void _PyGC_Fini(PyThreadState *tstate);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
Each interpreter now its has own free lists, singletons and caches:

* Free lists: float, tuple, list, dict, frame, context,
asynchronous generator.
asynchronous generator, MemoryError.
* Singletons: empty tuple, empty bytes string,
single byte character.
* Slice cache.

They are no longer shared by all interpreters.

75 changes: 45 additions & 30 deletions Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ PyObject *PyExc_IOError = NULL;
PyObject *PyExc_WindowsError = NULL;
#endif

/* The dict map from errno codes to OSError subclasses */
static PyObject *errnomap = NULL;

static struct _Py_exc_state*
get_exc_state(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
return &interp->exc_state;
}


/* NOTE: If the exception class hierarchy changes, don't forget to update
Expand Down Expand Up @@ -985,10 +990,11 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
))
goto error;

struct _Py_exc_state *state = get_exc_state();
if (myerrno && PyLong_Check(myerrno) &&
errnomap && (PyObject *) type == PyExc_OSError) {
state->errnomap && (PyObject *) type == PyExc_OSError) {
PyObject *newtype;
newtype = PyDict_GetItemWithError(errnomap, myerrno);
newtype = PyDict_GetItemWithError(state->errnomap, myerrno);
if (newtype) {
assert(PyType_Check(newtype));
type = (PyTypeObject *) newtype;
Expand Down Expand Up @@ -2274,8 +2280,6 @@ SimpleExtendsException(PyExc_Exception, ReferenceError,
*/

#define MEMERRORS_SAVE 16
static PyBaseExceptionObject *memerrors_freelist = NULL;
static int memerrors_numfree = 0;

static PyObject *
MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Expand All @@ -2284,16 +2288,22 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

if (type != (PyTypeObject *) PyExc_MemoryError)
return BaseException_new(type, args, kwds);
if (memerrors_freelist == NULL)

struct _Py_exc_state *state = get_exc_state();
if (state->memerrors_freelist == NULL) {
return BaseException_new(type, args, kwds);
}

/* Fetch object from freelist and revive it */
self = memerrors_freelist;
self = state->memerrors_freelist;
self->args = PyTuple_New(0);
/* This shouldn't happen since the empty tuple is persistent */
if (self->args == NULL)
if (self->args == NULL) {
return NULL;
memerrors_freelist = (PyBaseExceptionObject *) self->dict;
memerrors_numfree--;
}

state->memerrors_freelist = (PyBaseExceptionObject *) self->dict;
state->memerrors_numfree--;
self->dict = NULL;
_Py_NewReference((PyObject *)self);
_PyObject_GC_TRACK(self);
Expand All @@ -2305,12 +2315,15 @@ MemoryError_dealloc(PyBaseExceptionObject *self)
{
_PyObject_GC_UNTRACK(self);
BaseException_clear(self);
if (memerrors_numfree >= MEMERRORS_SAVE)

struct _Py_exc_state *state = get_exc_state();
if (state->memerrors_numfree >= MEMERRORS_SAVE) {
Py_TYPE(self)->tp_free((PyObject *)self);
}
else {
self->dict = (PyObject *) memerrors_freelist;
memerrors_freelist = self;
memerrors_numfree++;
self->dict = (PyObject *) state->memerrors_freelist;
state->memerrors_freelist = self;
state->memerrors_numfree++;
}
}

Expand All @@ -2335,11 +2348,11 @@ preallocate_memerrors(void)
}

static void
free_preallocated_memerrors(void)
free_preallocated_memerrors(struct _Py_exc_state *state)
{
while (memerrors_freelist != NULL) {
PyObject *self = (PyObject *) memerrors_freelist;
memerrors_freelist = (PyBaseExceptionObject *) memerrors_freelist->dict;
while (state->memerrors_freelist != NULL) {
PyObject *self = (PyObject *) state->memerrors_freelist;
state->memerrors_freelist = (PyBaseExceptionObject *)state->memerrors_freelist->dict;
Py_TYPE(self)->tp_free((PyObject *)self);
}
}
Expand Down Expand Up @@ -2507,8 +2520,10 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning,
#endif /* MS_WINDOWS */

PyStatus
_PyExc_Init(void)
_PyExc_Init(PyThreadState *tstate)
{
struct _Py_exc_state *state = &tstate->interp->exc_state;

#define PRE_INIT(TYPE) \
if (!(_PyExc_ ## TYPE.tp_flags & Py_TPFLAGS_READY)) { \
if (PyType_Ready(&_PyExc_ ## TYPE) < 0) { \
Expand All @@ -2521,7 +2536,7 @@ _PyExc_Init(void)
do { \
PyObject *_code = PyLong_FromLong(CODE); \
assert(_PyObject_RealIsSubclass(PyExc_ ## TYPE, PyExc_OSError)); \
if (!_code || PyDict_SetItem(errnomap, _code, PyExc_ ## TYPE)) \
if (!_code || PyDict_SetItem(state->errnomap, _code, PyExc_ ## TYPE)) \
return _PyStatus_ERR("errmap insertion problem."); \
Py_DECREF(_code); \
} while (0)
Expand Down Expand Up @@ -2595,15 +2610,14 @@ _PyExc_Init(void)
PRE_INIT(TimeoutError);

if (preallocate_memerrors() < 0) {
return _PyStatus_ERR("Could not preallocate MemoryError object");
return _PyStatus_NO_MEMORY();
}

/* Add exceptions to errnomap */
if (!errnomap) {
errnomap = PyDict_New();
if (!errnomap) {
return _PyStatus_ERR("Cannot allocate map from errnos to OSError subclasses");
}
assert(state->errnomap == NULL);
state->errnomap = PyDict_New();
if (!state->errnomap) {
return _PyStatus_NO_MEMORY();
}

ADD_ERRNO(BlockingIOError, EAGAIN);
Expand Down Expand Up @@ -2741,10 +2755,11 @@ _PyBuiltins_AddExceptions(PyObject *bltinmod)
}

void
_PyExc_Fini(void)
_PyExc_Fini(PyThreadState *tstate)
{
free_preallocated_memerrors();
Py_CLEAR(errnomap);
struct _Py_exc_state *state = &tstate->interp->exc_state;
free_preallocated_memerrors(state);
Py_CLEAR(state->errnomap);
}

/* Helper to do the equivalent of "raise X from Y" in C, but always using
Expand Down
7 changes: 2 additions & 5 deletions Python/pylifecycle.c