bpo-40521: Make context free list per-interpreter (GH-20644)
Each interpreter now has its own context free list:
* Move context free list into PyInterpreterState.
* Add _Py_context_state structure.
* Add tstate parameter to _PyContext_ClearFreeList()
and _PyContext_Fini().
* Pass tstate to clear_freelists().
diff --git a/Include/internal/pycore_context.h b/Include/internal/pycore_context.h
index f665ad5..ea4b3c8 100644
--- a/Include/internal/pycore_context.h
+++ b/Include/internal/pycore_context.h
@@ -37,6 +37,6 @@
int _PyContext_Init(void);
-void _PyContext_Fini(void);
+void _PyContext_Fini(PyThreadState *tstate);
#endif /* !Py_INTERNAL_CONTEXT_H */
diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h
index ad2e552..fd3fb7f 100644
--- a/Include/internal/pycore_gc.h
+++ b/Include/internal/pycore_gc.h
@@ -171,7 +171,7 @@
extern void _PyList_ClearFreeList(PyThreadState *tstate);
extern void _PyDict_ClearFreeList(void);
extern void _PyAsyncGen_ClearFreeLists(PyThreadState *tstate);
-extern void _PyContext_ClearFreeList(void);
+extern void _PyContext_ClearFreeList(PyThreadState *tstate);
#ifdef __cplusplus
}
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index d624218..4f81102 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -124,6 +124,12 @@
int asend_numfree;
};
+struct _Py_context_state {
+ // List of free PyContext objects
+ PyContext *freelist;
+ int numfree;
+};
+
/* interpreter state */
@@ -223,6 +229,7 @@
struct _Py_float_state float_state;
struct _Py_frame_state frame;
struct _Py_async_gen_state async_gen;
+ struct _Py_context_state context;
/* Using a cache is very effective since typically only a single slice is
created and then deleted again. */
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst b/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst
index f0fd5a1..39cb804 100644
--- a/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-05-20-01-17-34.bpo-40521.wvAehI.rst
@@ -1,4 +1,4 @@
The tuple free lists, the empty tuple singleton, the list free list, the float
free list, the slice cache, the frame free list, the asynchronous generator
-free lists are no longer shared by all interpreters: each interpreter now its
-has own free lists and caches.
+free lists, and the context free list are no longer shared by all interpreters:
+each interpreter now its has own free lists and caches.
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c
index 89e2db7..f68258d 100644
--- a/Modules/gcmodule.c
+++ b/Modules/gcmodule.c
@@ -1023,16 +1023,15 @@
* Clearing the free lists may give back memory to the OS earlier.
*/
static void
-clear_freelists(void)
+clear_freelists(PyThreadState *tstate)
{
- PyThreadState *tstate = _PyThreadState_GET();
_PyFrame_ClearFreeList(tstate);
_PyTuple_ClearFreeList(tstate);
_PyFloat_ClearFreeList(tstate);
_PyList_ClearFreeList(tstate);
_PyDict_ClearFreeList();
_PyAsyncGen_ClearFreeLists(tstate);
- _PyContext_ClearFreeList();
+ _PyContext_ClearFreeList(tstate);
}
// Show stats for objects in each generations
@@ -1306,7 +1305,7 @@
/* Clear free list only during the collection of the highest
* generation */
if (generation == NUM_GENERATIONS-1) {
- clear_freelists();
+ clear_freelists(tstate);
}
if (_PyErr_Occurred(tstate)) {
diff --git a/Python/context.c b/Python/context.c
index bacc701..3cf8db4 100644
--- a/Python/context.c
+++ b/Python/context.c
@@ -10,8 +10,6 @@
#define CONTEXT_FREELIST_MAXLEN 255
-static PyContext *ctx_freelist = NULL;
-static int ctx_freelist_len = 0;
#include "clinic/context.c.h"
@@ -334,11 +332,13 @@
static inline PyContext *
_context_alloc(void)
{
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _Py_context_state *state = &interp->context;
PyContext *ctx;
- if (ctx_freelist_len) {
- ctx_freelist_len--;
- ctx = ctx_freelist;
- ctx_freelist = (PyContext *)ctx->ctx_weakreflist;
+ if (state->numfree) {
+ state->numfree--;
+ ctx = state->freelist;
+ state->freelist = (PyContext *)ctx->ctx_weakreflist;
ctx->ctx_weakreflist = NULL;
_Py_NewReference((PyObject *)ctx);
}
@@ -458,10 +458,12 @@
}
(void)context_tp_clear(self);
- if (ctx_freelist_len < CONTEXT_FREELIST_MAXLEN) {
- ctx_freelist_len++;
- self->ctx_weakreflist = (PyObject *)ctx_freelist;
- ctx_freelist = self;
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ struct _Py_context_state *state = &interp->context;
+ if (state->numfree < CONTEXT_FREELIST_MAXLEN) {
+ state->numfree++;
+ self->ctx_weakreflist = (PyObject *)state->freelist;
+ state->freelist = self;
}
else {
Py_TYPE(self)->tp_free(self);
@@ -1271,11 +1273,12 @@
void
-_PyContext_ClearFreeList(void)
+_PyContext_ClearFreeList(PyThreadState *tstate)
{
- for (; ctx_freelist_len; ctx_freelist_len--) {
- PyContext *ctx = ctx_freelist;
- ctx_freelist = (PyContext *)ctx->ctx_weakreflist;
+ struct _Py_context_state *state = &tstate->interp->context;
+ for (; state->numfree; state->numfree--) {
+ PyContext *ctx = state->freelist;
+ state->freelist = (PyContext *)ctx->ctx_weakreflist;
ctx->ctx_weakreflist = NULL;
PyObject_GC_Del(ctx);
}
@@ -1283,10 +1286,10 @@
void
-_PyContext_Fini(void)
+_PyContext_Fini(PyThreadState *tstate)
{
Py_CLEAR(_token_missing);
- _PyContext_ClearFreeList();
+ _PyContext_ClearFreeList(tstate);
_PyHamt_Fini();
}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 073973e..6d2eb1d 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -1273,10 +1273,7 @@
}
_PyAsyncGen_Fini(tstate);
-
- if (is_main_interp) {
- _PyContext_Fini();
- }
+ _PyContext_Fini(tstate);
/* Cleanup Unicode implementation */
_PyUnicode_Fini(tstate);