Skip to content

Commit

Permalink
bpo-40521: Disable free lists in subinterpreters (GH-19937)
Browse files Browse the repository at this point in the history
When Python is built with experimental isolated interpreters, disable
tuple, dict and free free lists.

Temporary workaround until these caches are made per-interpreter.

Add frame_alloc() and frame_get_builtins() subfunctions to simplify
_PyFrame_New_NoTrack().
  • Loading branch information
vstinner authored May 5, 2020
1 parent ac4bf42 commit b4b5386
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 80 deletions.
37 changes: 33 additions & 4 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,26 @@ static uint64_t pydict_global_version = 0;
#ifndef PyDict_MAXFREELIST
#define PyDict_MAXFREELIST 80
#endif

/* bpo-40521: dict free lists are shared by all interpreters. */
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
# undef PyDict_MAXFREELIST
# define PyDict_MAXFREELIST 0
#endif

#if PyDict_MAXFREELIST > 0
static PyDictObject *free_list[PyDict_MAXFREELIST];
static int numfree = 0;
static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
static int numfreekeys = 0;
#endif

#include "clinic/dictobject.c.h"

void
_PyDict_ClearFreeList(void)
{
#if PyDict_MAXFREELIST > 0
while (numfree) {
PyDictObject *op = free_list[--numfree];
assert(PyDict_CheckExact(op));
Expand All @@ -268,14 +278,17 @@ _PyDict_ClearFreeList(void)
while (numfreekeys) {
PyObject_FREE(keys_free_list[--numfreekeys]);
}
#endif
}

/* Print summary info about the state of the optimized allocator */
void
_PyDict_DebugMallocStats(FILE *out)
{
#if PyDict_MAXFREELIST > 0
_PyDebugAllocatorStats(out,
"free PyDictObject", numfree, sizeof(PyDictObject));
#endif
}


Expand Down Expand Up @@ -553,10 +566,13 @@ static PyDictKeysObject *new_keys_object(Py_ssize_t size)
es = sizeof(Py_ssize_t);
}

#if PyDict_MAXFREELIST > 0
if (size == PyDict_MINSIZE && numfreekeys > 0) {
dk = keys_free_list[--numfreekeys];
}
else {
else
#endif
{
dk = PyObject_MALLOC(sizeof(PyDictKeysObject)
+ es * size
+ sizeof(PyDictKeyEntry) * usable);
Expand Down Expand Up @@ -587,10 +603,12 @@ free_keys_object(PyDictKeysObject *keys)
Py_XDECREF(entries[i].me_key);
Py_XDECREF(entries[i].me_value);
}
#if PyDict_MAXFREELIST > 0
if (keys->dk_size == PyDict_MINSIZE && numfreekeys < PyDict_MAXFREELIST) {
keys_free_list[numfreekeys++] = keys;
return;
}
#endif
PyObject_FREE(keys);
}

Expand All @@ -603,13 +621,16 @@ new_dict(PyDictKeysObject *keys, PyObject **values)
{
PyDictObject *mp;
assert(keys != NULL);
#if PyDict_MAXFREELIST > 0
if (numfree) {
mp = free_list[--numfree];
assert (mp != NULL);
assert (Py_IS_TYPE(mp, &PyDict_Type));
_Py_NewReference((PyObject *)mp);
}
else {
else
#endif
{
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
if (mp == NULL) {
dictkeys_decref(keys);
Expand Down Expand Up @@ -1258,12 +1279,15 @@ dictresize(PyDictObject *mp, Py_ssize_t minsize)
#ifdef Py_REF_DEBUG
_Py_RefTotal--;
#endif
#if PyDict_MAXFREELIST > 0
if (oldkeys->dk_size == PyDict_MINSIZE &&
numfreekeys < PyDict_MAXFREELIST)
{
keys_free_list[numfreekeys++] = oldkeys;
}
else {
else
#endif
{
PyObject_FREE(oldkeys);
}
}
Expand Down Expand Up @@ -2005,10 +2029,15 @@ dict_dealloc(PyDictObject *mp)
assert(keys->dk_refcnt == 1);
dictkeys_decref(keys);
}
if (numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type))
#if PyDict_MAXFREELIST > 0
if (numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
free_list[numfree++] = mp;
}
else
#endif
{
Py_TYPE(mp)->tp_free((PyObject *)mp);
}
Py_TRASHCAN_END
}

Expand Down
197 changes: 121 additions & 76 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -556,11 +556,19 @@ static PyGetSetDef frame_getsetlist[] = {
free_list. Else programs creating lots of cyclic trash involving
frames could provoke free_list into growing without bound.
*/
/* max value for numfree */
#define PyFrame_MAXFREELIST 200

/* bpo-40521: frame free lists are shared by all interpreters. */
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
# undef PyFrame_MAXFREELIST
# define PyFrame_MAXFREELIST 0
#endif

#if PyFrame_MAXFREELIST > 0
static PyFrameObject *free_list = NULL;
static int numfree = 0; /* number of frames currently in free_list */
/* max value for numfree */
#define PyFrame_MAXFREELIST 200
#endif

static void _Py_HOT_FUNCTION
frame_dealloc(PyFrameObject *f)
Expand Down Expand Up @@ -590,15 +598,19 @@ frame_dealloc(PyFrameObject *f)
Py_CLEAR(f->f_trace);

co = f->f_code;
if (co->co_zombieframe == NULL)
if (co->co_zombieframe == NULL) {
co->co_zombieframe = f;
}
#if PyFrame_MAXFREELIST > 0
else if (numfree < PyFrame_MAXFREELIST) {
++numfree;
f->f_back = free_list;
free_list = f;
}
else
#endif
else {
PyObject_GC_Del(f);
}

Py_DECREF(co);
Py_TRASHCAN_SAFE_END(f)
Expand Down Expand Up @@ -759,98 +771,127 @@ PyTypeObject PyFrame_Type = {

_Py_IDENTIFIER(__builtins__);

PyFrameObject* _Py_HOT_FUNCTION
_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
PyObject *globals, PyObject *locals)
static inline PyFrameObject*
frame_alloc(PyCodeObject *code)
{
PyFrameObject *back = tstate->frame;
PyFrameObject *f;
PyObject *builtins;
Py_ssize_t i;

#ifdef Py_DEBUG
if (code == NULL || globals == NULL || !PyDict_Check(globals) ||
(locals != NULL && !PyMapping_Check(locals))) {
PyErr_BadInternalCall();
return NULL;
f = code->co_zombieframe;
if (f != NULL) {
code->co_zombieframe = NULL;
_Py_NewReference((PyObject *)f);
assert(f->f_code == code);
return f;
}

Py_ssize_t ncells = PyTuple_GET_SIZE(code->co_cellvars);
Py_ssize_t nfrees = PyTuple_GET_SIZE(code->co_freevars);
Py_ssize_t extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
#if PyFrame_MAXFREELIST > 0
if (free_list == NULL)
#endif
if (back == NULL || back->f_globals != globals) {
builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
if (builtins) {
if (PyModule_Check(builtins)) {
builtins = PyModule_GetDict(builtins);
assert(builtins != NULL);
}
{
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
if (f == NULL) {
return NULL;
}
if (builtins == NULL) {
if (PyErr_Occurred()) {
}
#if PyFrame_MAXFREELIST > 0
else {
assert(numfree > 0);
--numfree;
f = free_list;
free_list = free_list->f_back;
if (Py_SIZE(f) < extras) {
PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
if (new_f == NULL) {
PyObject_GC_Del(f);
return NULL;
}
/* No builtins! Make up a minimal one
Give them 'None', at least. */
builtins = PyDict_New();
if (builtins == NULL ||
PyDict_SetItemString(
builtins, "None", Py_None) < 0)
return NULL;
f = new_f;
}
else
Py_INCREF(builtins);
_Py_NewReference((PyObject *)f);
}
#endif

f->f_code = code;
extras = code->co_nlocals + ncells + nfrees;
f->f_valuestack = f->f_localsplus + extras;
for (Py_ssize_t i=0; i<extras; i++) {
f->f_localsplus[i] = NULL;
}
else {
f->f_locals = NULL;
f->f_trace = NULL;
return f;
}


static inline PyObject *
frame_get_builtins(PyFrameObject *back, PyObject *globals)
{
PyObject *builtins;

if (back != NULL && back->f_globals == globals) {
/* If we share the globals, we share the builtins.
Save a lookup and a call. */
builtins = back->f_builtins;
assert(builtins != NULL);
Py_INCREF(builtins);
return builtins;
}
if (code->co_zombieframe != NULL) {
f = code->co_zombieframe;
code->co_zombieframe = NULL;
_Py_NewReference((PyObject *)f);
assert(f->f_code == code);

builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
if (builtins != NULL && PyModule_Check(builtins)) {
builtins = PyModule_GetDict(builtins);
assert(builtins != NULL);
}
else {
Py_ssize_t extras, ncells, nfrees;
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->co_stacksize + code->co_nlocals + ncells +
nfrees;
if (free_list == NULL) {
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type,
extras);
if (f == NULL) {
Py_DECREF(builtins);
return NULL;
}
}
else {
assert(numfree > 0);
--numfree;
f = free_list;
free_list = free_list->f_back;
if (Py_SIZE(f) < extras) {
PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
if (new_f == NULL) {
PyObject_GC_Del(f);
Py_DECREF(builtins);
return NULL;
}
f = new_f;
}
_Py_NewReference((PyObject *)f);
}
if (builtins != NULL) {
Py_INCREF(builtins);
return builtins;
}

if (PyErr_Occurred()) {
return NULL;
}

/* No builtins! Make up a minimal one.
Give them 'None', at least. */
builtins = PyDict_New();
if (builtins == NULL) {
return NULL;
}
if (PyDict_SetItemString(builtins, "None", Py_None) < 0) {
Py_DECREF(builtins);
return NULL;
}
return builtins;
}

f->f_code = code;
extras = code->co_nlocals + ncells + nfrees;
f->f_valuestack = f->f_localsplus + extras;
for (i=0; i<extras; i++)
f->f_localsplus[i] = NULL;
f->f_locals = NULL;
f->f_trace = NULL;

PyFrameObject* _Py_HOT_FUNCTION
_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
PyObject *globals, PyObject *locals)
{
#ifdef Py_DEBUG
if (code == NULL || globals == NULL || !PyDict_Check(globals) ||
(locals != NULL && !PyMapping_Check(locals))) {
PyErr_BadInternalCall();
return NULL;
}
#endif

PyFrameObject *back = tstate->frame;
PyObject *builtins = frame_get_builtins(back, globals);
if (builtins == NULL) {
return NULL;
}

PyFrameObject *f = frame_alloc(code);
if (f == NULL) {
Py_DECREF(builtins);
return NULL;
}

f->f_stacktop = f->f_valuestack;
f->f_builtins = builtins;
Py_XINCREF(back);
Expand Down Expand Up @@ -1142,13 +1183,15 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
void
_PyFrame_ClearFreeList(void)
{
#if PyFrame_MAXFREELIST > 0
while (free_list != NULL) {
PyFrameObject *f = free_list;
free_list = free_list->f_back;
PyObject_GC_Del(f);
--numfree;
}
assert(numfree == 0);
#endif
}

void
Expand All @@ -1161,9 +1204,11 @@ _PyFrame_Fini(void)
void
_PyFrame_DebugMallocStats(FILE *out)
{
#if PyFrame_MAXFREELIST > 0
_PyDebugAllocatorStats(out,
"free PyFrameObject",
numfree, sizeof(PyFrameObject));
#endif
}


Expand Down
Loading

0 comments on commit b4b5386

Please sign in to comment.