summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2020-05-05 17:55:29 (GMT)
committerGitHub <noreply@github.com>2020-05-05 17:55:29 (GMT)
commitb4b53868d7d6cd13505321d3802fd00865b25e05 (patch)
tree850dd2ad175f058b4d040bd38b5757c2149cc3b5
parentac4bf424119d1300f57929120968e216a85d3a25 (diff)
downloadcpython-b4b53868d7d6cd13505321d3802fd00865b25e05.zip
cpython-b4b53868d7d6cd13505321d3802fd00865b25e05.tar.gz
cpython-b4b53868d7d6cd13505321d3802fd00865b25e05.tar.bz2
bpo-40521: Disable free lists in subinterpreters (GH-19937)
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().
-rw-r--r--Objects/dictobject.c37
-rw-r--r--Objects/frameobject.c197
-rw-r--r--Objects/tupleobject.c8
3 files changed, 162 insertions, 80 deletions
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 9c35f3c..fa35d16 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -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));
@@ -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
}
@@ -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);
@@ -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);
}
@@ -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);
@@ -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);
}
}
@@ -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
}
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 4f5054d..af32276 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -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)
@@ -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)
@@ -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);
@@ -1142,6 +1183,7 @@ 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;
@@ -1149,6 +1191,7 @@ _PyFrame_ClearFreeList(void)
--numfree;
}
assert(numfree == 0);
+#endif
}
void
@@ -1161,9 +1204,11 @@ _PyFrame_Fini(void)
void
_PyFrame_DebugMallocStats(FILE *out)
{
+#if PyFrame_MAXFREELIST > 0
_PyDebugAllocatorStats(out,
"free PyFrameObject",
numfree, sizeof(PyFrameObject));
+#endif
}
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index f8648d2..c0b59c0 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -22,6 +22,12 @@ class tuple "PyTupleObject *" "&PyTuple_Type"
#define PyTuple_MAXFREELIST 2000 /* Maximum number of tuples of each size to save */
#endif
+/* bpo-40521: tuple free lists are shared by all interpreters. */
+#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
+# undef PyTuple_MAXSAVESIZE
+# define PyTuple_MAXSAVESIZE 0
+#endif
+
#if PyTuple_MAXSAVESIZE > 0
/* Entries 1 up to PyTuple_MAXSAVESIZE are free lists, entry 0 is the empty
tuple () of which at most one instance will be allocated.
@@ -248,7 +254,9 @@ tupledealloc(PyTupleObject *op)
#endif
}
Py_TYPE(op)->tp_free((PyObject *)op);
+#if PyTuple_MAXSAVESIZE > 0
done:
+#endif
Py_TRASHCAN_END
}