Skip to content

bpo-40521: Make MemoryError free list per interpreter #21086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 23, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ struct _Py_context_state {
int numfree;
};

struct _Py_exc_state {
// The dict mapping from errno codes to OSError subclasses
PyObject *errnomap;
PyBaseExceptionObject *memerrors_freelist;
int memerrors_numfree;
};


/* interpreter state */

Expand Down Expand Up @@ -251,6 +258,7 @@ struct _is {
struct _Py_frame_state frame;
struct _Py_async_gen_state async_gen;
struct _Py_context_state context;
struct _Py_exc_state exc_state;
};

/* Used by _PyImport_Cleanup() */
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ pycore_init_types(PyThreadState *tstate)
}
}

status = _PyExc_Init();
status = _PyExc_Init(tstate);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
Expand Down Expand Up @@ -1249,6 +1249,7 @@ flush_std_files(void)
static void
finalize_interp_types(PyThreadState *tstate, int is_main_interp)
{
_PyExc_Fini(tstate);
_PyFrame_Fini(tstate);
_PyAsyncGen_Fini(tstate);
_PyContext_Fini(tstate);
Expand Down Expand Up @@ -1289,10 +1290,6 @@ finalize_interp_clear(PyThreadState *tstate)

_PyWarnings_Fini(tstate->interp);

if (is_main_interp) {
_PyExc_Fini();
}

finalize_interp_types(tstate, is_main_interp);
}

Expand Down