Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

bpo-40521: Disable free lists in subinterpreters #19937

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
May 5, 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
bpo-40521: Disable free lists in subinterpreters
When Python is built with experimental isolated interpreters, disable
tuple, dict and free free lists.

Add frame_alloc() and frame_get_builtins() subfunctions to simplify
_PyFrame_New_NoTrack().
  • Loading branch information
vstinner committed May 5, 2020
commit 39630d22ee00fa8d760d3f109e440b0ff85ce101
37 changes: 33 additions & 4 deletions 37 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 197 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
Morty Proxy This is a proxified and sanitized view of the page, visit original site.