Skip to content

Commit

Permalink
bpo-40521: Make MemoryError free list per interpreter (pythonGH-21086)
Browse files Browse the repository at this point in the history
Each interpreter now has its own MemoryError free list: it is not
longer shared by all interpreters.

Add _Py_exc_state structure and PyInterpreterState.exc_state member.
Move also errnomap into _Py_exc_state.
  • Loading branch information
vstinner authored and fasihahmad committed Jun 29, 2020
1 parent 0f3ea2f commit 9b18700
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 39 deletions.
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

0 comments on commit 9b18700

Please sign in to comment.