Skip to content

Commit b4b5386

Browse files
authored
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().
1 parent ac4bf42 commit b4b5386

File tree

3 files changed

+162
-80
lines changed

3 files changed

+162
-80
lines changed

Objects/dictobject.c

+33-4
Original file line numberDiff line numberDiff line change
@@ -250,16 +250,26 @@ static uint64_t pydict_global_version = 0;
250250
#ifndef PyDict_MAXFREELIST
251251
#define PyDict_MAXFREELIST 80
252252
#endif
253+
254+
/* bpo-40521: dict free lists are shared by all interpreters. */
255+
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
256+
# undef PyDict_MAXFREELIST
257+
# define PyDict_MAXFREELIST 0
258+
#endif
259+
260+
#if PyDict_MAXFREELIST > 0
253261
static PyDictObject *free_list[PyDict_MAXFREELIST];
254262
static int numfree = 0;
255263
static PyDictKeysObject *keys_free_list[PyDict_MAXFREELIST];
256264
static int numfreekeys = 0;
265+
#endif
257266

258267
#include "clinic/dictobject.c.h"
259268

260269
void
261270
_PyDict_ClearFreeList(void)
262271
{
272+
#if PyDict_MAXFREELIST > 0
263273
while (numfree) {
264274
PyDictObject *op = free_list[--numfree];
265275
assert(PyDict_CheckExact(op));
@@ -268,14 +278,17 @@ _PyDict_ClearFreeList(void)
268278
while (numfreekeys) {
269279
PyObject_FREE(keys_free_list[--numfreekeys]);
270280
}
281+
#endif
271282
}
272283

273284
/* Print summary info about the state of the optimized allocator */
274285
void
275286
_PyDict_DebugMallocStats(FILE *out)
276287
{
288+
#if PyDict_MAXFREELIST > 0
277289
_PyDebugAllocatorStats(out,
278290
"free PyDictObject", numfree, sizeof(PyDictObject));
291+
#endif
279292
}
280293

281294

@@ -553,10 +566,13 @@ static PyDictKeysObject *new_keys_object(Py_ssize_t size)
553566
es = sizeof(Py_ssize_t);
554567
}
555568

569+
#if PyDict_MAXFREELIST > 0
556570
if (size == PyDict_MINSIZE && numfreekeys > 0) {
557571
dk = keys_free_list[--numfreekeys];
558572
}
559-
else {
573+
else
574+
#endif
575+
{
560576
dk = PyObject_MALLOC(sizeof(PyDictKeysObject)
561577
+ es * size
562578
+ sizeof(PyDictKeyEntry) * usable);
@@ -587,10 +603,12 @@ free_keys_object(PyDictKeysObject *keys)
587603
Py_XDECREF(entries[i].me_key);
588604
Py_XDECREF(entries[i].me_value);
589605
}
606+
#if PyDict_MAXFREELIST > 0
590607
if (keys->dk_size == PyDict_MINSIZE && numfreekeys < PyDict_MAXFREELIST) {
591608
keys_free_list[numfreekeys++] = keys;
592609
return;
593610
}
611+
#endif
594612
PyObject_FREE(keys);
595613
}
596614

@@ -603,13 +621,16 @@ new_dict(PyDictKeysObject *keys, PyObject **values)
603621
{
604622
PyDictObject *mp;
605623
assert(keys != NULL);
624+
#if PyDict_MAXFREELIST > 0
606625
if (numfree) {
607626
mp = free_list[--numfree];
608627
assert (mp != NULL);
609628
assert (Py_IS_TYPE(mp, &PyDict_Type));
610629
_Py_NewReference((PyObject *)mp);
611630
}
612-
else {
631+
else
632+
#endif
633+
{
613634
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
614635
if (mp == NULL) {
615636
dictkeys_decref(keys);
@@ -1258,12 +1279,15 @@ dictresize(PyDictObject *mp, Py_ssize_t minsize)
12581279
#ifdef Py_REF_DEBUG
12591280
_Py_RefTotal--;
12601281
#endif
1282+
#if PyDict_MAXFREELIST > 0
12611283
if (oldkeys->dk_size == PyDict_MINSIZE &&
12621284
numfreekeys < PyDict_MAXFREELIST)
12631285
{
12641286
keys_free_list[numfreekeys++] = oldkeys;
12651287
}
1266-
else {
1288+
else
1289+
#endif
1290+
{
12671291
PyObject_FREE(oldkeys);
12681292
}
12691293
}
@@ -2005,10 +2029,15 @@ dict_dealloc(PyDictObject *mp)
20052029
assert(keys->dk_refcnt == 1);
20062030
dictkeys_decref(keys);
20072031
}
2008-
if (numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type))
2032+
#if PyDict_MAXFREELIST > 0
2033+
if (numfree < PyDict_MAXFREELIST && Py_IS_TYPE(mp, &PyDict_Type)) {
20092034
free_list[numfree++] = mp;
2035+
}
20102036
else
2037+
#endif
2038+
{
20112039
Py_TYPE(mp)->tp_free((PyObject *)mp);
2040+
}
20122041
Py_TRASHCAN_END
20132042
}
20142043

Objects/frameobject.c

+121-76
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,19 @@ static PyGetSetDef frame_getsetlist[] = {
556556
free_list. Else programs creating lots of cyclic trash involving
557557
frames could provoke free_list into growing without bound.
558558
*/
559+
/* max value for numfree */
560+
#define PyFrame_MAXFREELIST 200
561+
562+
/* bpo-40521: frame free lists are shared by all interpreters. */
563+
#ifdef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
564+
# undef PyFrame_MAXFREELIST
565+
# define PyFrame_MAXFREELIST 0
566+
#endif
559567

568+
#if PyFrame_MAXFREELIST > 0
560569
static PyFrameObject *free_list = NULL;
561570
static int numfree = 0; /* number of frames currently in free_list */
562-
/* max value for numfree */
563-
#define PyFrame_MAXFREELIST 200
571+
#endif
564572

565573
static void _Py_HOT_FUNCTION
566574
frame_dealloc(PyFrameObject *f)
@@ -590,15 +598,19 @@ frame_dealloc(PyFrameObject *f)
590598
Py_CLEAR(f->f_trace);
591599

592600
co = f->f_code;
593-
if (co->co_zombieframe == NULL)
601+
if (co->co_zombieframe == NULL) {
594602
co->co_zombieframe = f;
603+
}
604+
#if PyFrame_MAXFREELIST > 0
595605
else if (numfree < PyFrame_MAXFREELIST) {
596606
++numfree;
597607
f->f_back = free_list;
598608
free_list = f;
599609
}
600-
else
610+
#endif
611+
else {
601612
PyObject_GC_Del(f);
613+
}
602614

603615
Py_DECREF(co);
604616
Py_TRASHCAN_SAFE_END(f)
@@ -759,98 +771,127 @@ PyTypeObject PyFrame_Type = {
759771

760772
_Py_IDENTIFIER(__builtins__);
761773

762-
PyFrameObject* _Py_HOT_FUNCTION
763-
_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
764-
PyObject *globals, PyObject *locals)
774+
static inline PyFrameObject*
775+
frame_alloc(PyCodeObject *code)
765776
{
766-
PyFrameObject *back = tstate->frame;
767777
PyFrameObject *f;
768-
PyObject *builtins;
769-
Py_ssize_t i;
770778

771-
#ifdef Py_DEBUG
772-
if (code == NULL || globals == NULL || !PyDict_Check(globals) ||
773-
(locals != NULL && !PyMapping_Check(locals))) {
774-
PyErr_BadInternalCall();
775-
return NULL;
779+
f = code->co_zombieframe;
780+
if (f != NULL) {
781+
code->co_zombieframe = NULL;
782+
_Py_NewReference((PyObject *)f);
783+
assert(f->f_code == code);
784+
return f;
776785
}
786+
787+
Py_ssize_t ncells = PyTuple_GET_SIZE(code->co_cellvars);
788+
Py_ssize_t nfrees = PyTuple_GET_SIZE(code->co_freevars);
789+
Py_ssize_t extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
790+
#if PyFrame_MAXFREELIST > 0
791+
if (free_list == NULL)
777792
#endif
778-
if (back == NULL || back->f_globals != globals) {
779-
builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
780-
if (builtins) {
781-
if (PyModule_Check(builtins)) {
782-
builtins = PyModule_GetDict(builtins);
783-
assert(builtins != NULL);
784-
}
793+
{
794+
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
795+
if (f == NULL) {
796+
return NULL;
785797
}
786-
if (builtins == NULL) {
787-
if (PyErr_Occurred()) {
798+
}
799+
#if PyFrame_MAXFREELIST > 0
800+
else {
801+
assert(numfree > 0);
802+
--numfree;
803+
f = free_list;
804+
free_list = free_list->f_back;
805+
if (Py_SIZE(f) < extras) {
806+
PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
807+
if (new_f == NULL) {
808+
PyObject_GC_Del(f);
788809
return NULL;
789810
}
790-
/* No builtins! Make up a minimal one
791-
Give them 'None', at least. */
792-
builtins = PyDict_New();
793-
if (builtins == NULL ||
794-
PyDict_SetItemString(
795-
builtins, "None", Py_None) < 0)
796-
return NULL;
811+
f = new_f;
797812
}
798-
else
799-
Py_INCREF(builtins);
813+
_Py_NewReference((PyObject *)f);
814+
}
815+
#endif
800816

817+
f->f_code = code;
818+
extras = code->co_nlocals + ncells + nfrees;
819+
f->f_valuestack = f->f_localsplus + extras;
820+
for (Py_ssize_t i=0; i<extras; i++) {
821+
f->f_localsplus[i] = NULL;
801822
}
802-
else {
823+
f->f_locals = NULL;
824+
f->f_trace = NULL;
825+
return f;
826+
}
827+
828+
829+
static inline PyObject *
830+
frame_get_builtins(PyFrameObject *back, PyObject *globals)
831+
{
832+
PyObject *builtins;
833+
834+
if (back != NULL && back->f_globals == globals) {
803835
/* If we share the globals, we share the builtins.
804836
Save a lookup and a call. */
805837
builtins = back->f_builtins;
806838
assert(builtins != NULL);
807839
Py_INCREF(builtins);
840+
return builtins;
808841
}
809-
if (code->co_zombieframe != NULL) {
810-
f = code->co_zombieframe;
811-
code->co_zombieframe = NULL;
812-
_Py_NewReference((PyObject *)f);
813-
assert(f->f_code == code);
842+
843+
builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
844+
if (builtins != NULL && PyModule_Check(builtins)) {
845+
builtins = PyModule_GetDict(builtins);
846+
assert(builtins != NULL);
814847
}
815-
else {
816-
Py_ssize_t extras, ncells, nfrees;
817-
ncells = PyTuple_GET_SIZE(code->co_cellvars);
818-
nfrees = PyTuple_GET_SIZE(code->co_freevars);
819-
extras = code->co_stacksize + code->co_nlocals + ncells +
820-
nfrees;
821-
if (free_list == NULL) {
822-
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type,
823-
extras);
824-
if (f == NULL) {
825-
Py_DECREF(builtins);
826-
return NULL;
827-
}
828-
}
829-
else {
830-
assert(numfree > 0);
831-
--numfree;
832-
f = free_list;
833-
free_list = free_list->f_back;
834-
if (Py_SIZE(f) < extras) {
835-
PyFrameObject *new_f = PyObject_GC_Resize(PyFrameObject, f, extras);
836-
if (new_f == NULL) {
837-
PyObject_GC_Del(f);
838-
Py_DECREF(builtins);
839-
return NULL;
840-
}
841-
f = new_f;
842-
}
843-
_Py_NewReference((PyObject *)f);
844-
}
848+
if (builtins != NULL) {
849+
Py_INCREF(builtins);
850+
return builtins;
851+
}
852+
853+
if (PyErr_Occurred()) {
854+
return NULL;
855+
}
856+
857+
/* No builtins! Make up a minimal one.
858+
Give them 'None', at least. */
859+
builtins = PyDict_New();
860+
if (builtins == NULL) {
861+
return NULL;
862+
}
863+
if (PyDict_SetItemString(builtins, "None", Py_None) < 0) {
864+
Py_DECREF(builtins);
865+
return NULL;
866+
}
867+
return builtins;
868+
}
845869

846-
f->f_code = code;
847-
extras = code->co_nlocals + ncells + nfrees;
848-
f->f_valuestack = f->f_localsplus + extras;
849-
for (i=0; i<extras; i++)
850-
f->f_localsplus[i] = NULL;
851-
f->f_locals = NULL;
852-
f->f_trace = NULL;
870+
871+
PyFrameObject* _Py_HOT_FUNCTION
872+
_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
873+
PyObject *globals, PyObject *locals)
874+
{
875+
#ifdef Py_DEBUG
876+
if (code == NULL || globals == NULL || !PyDict_Check(globals) ||
877+
(locals != NULL && !PyMapping_Check(locals))) {
878+
PyErr_BadInternalCall();
879+
return NULL;
880+
}
881+
#endif
882+
883+
PyFrameObject *back = tstate->frame;
884+
PyObject *builtins = frame_get_builtins(back, globals);
885+
if (builtins == NULL) {
886+
return NULL;
853887
}
888+
889+
PyFrameObject *f = frame_alloc(code);
890+
if (f == NULL) {
891+
Py_DECREF(builtins);
892+
return NULL;
893+
}
894+
854895
f->f_stacktop = f->f_valuestack;
855896
f->f_builtins = builtins;
856897
Py_XINCREF(back);
@@ -1142,13 +1183,15 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
11421183
void
11431184
_PyFrame_ClearFreeList(void)
11441185
{
1186+
#if PyFrame_MAXFREELIST > 0
11451187
while (free_list != NULL) {
11461188
PyFrameObject *f = free_list;
11471189
free_list = free_list->f_back;
11481190
PyObject_GC_Del(f);
11491191
--numfree;
11501192
}
11511193
assert(numfree == 0);
1194+
#endif
11521195
}
11531196

11541197
void
@@ -1161,9 +1204,11 @@ _PyFrame_Fini(void)
11611204
void
11621205
_PyFrame_DebugMallocStats(FILE *out)
11631206
{
1207+
#if PyFrame_MAXFREELIST > 0
11641208
_PyDebugAllocatorStats(out,
11651209
"free PyFrameObject",
11661210
numfree, sizeof(PyFrameObject));
1211+
#endif
11671212
}
11681213

11691214

0 commit comments

Comments
 (0)