From 999fe83b02f734241b7b5a9a92b25fec65103c50 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 8 Mar 2019 13:23:25 -0700 Subject: [PATCH 1/7] Add a note about clear an interpreter's objects during shutdown. --- Python/pystate.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 49497b7c376768..aa5965552c256a 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -223,6 +223,9 @@ PyInterpreterState_Clear(PyInterpreterState *interp) Py_CLEAR(interp->after_forkers_parent); Py_CLEAR(interp->after_forkers_child); #endif + // XXX Once we have one allocator per interpreter (i.e. + // per-interpreter GC) we must ensure that all of the interpreter's + // objects have been cleaned up at the point. } From 88cd0365c19861dae425bc1cbb7d90504f90d1a6 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 8 Mar 2019 10:52:44 -0700 Subject: [PATCH 2/7] Simplify DISPATCH by hoisting eval_breaker ahead of time. --- Python/ceval.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index b311248c6a2073..ab6a5e0f12182f 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -602,6 +602,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) PyObject **fastlocals, **freevars; PyObject *retval = NULL; /* Return value */ PyThreadState *tstate = _PyThreadState_GET(); + _Py_atomic_int *eval_breaker = &_PyRuntime.ceval.eval_breaker; PyCodeObject *co; /* when tracing we set things up so that @@ -687,7 +688,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) #define DISPATCH() \ { \ - if (!_Py_atomic_load_relaxed(&_PyRuntime.ceval.eval_breaker)) { \ + if (!_Py_atomic_load_relaxed(eval_breaker)) { \ FAST_DISPATCH(); \ } \ continue; \ @@ -989,7 +990,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag) async I/O handler); see Py_AddPendingCall() and Py_MakePendingCalls() above. */ - if (_Py_atomic_load_relaxed(&_PyRuntime.ceval.eval_breaker)) { + if (_Py_atomic_load_relaxed(eval_breaker)) { opcode = _Py_OPCODE(*next_instr); if (opcode == SETUP_FINALLY || opcode == SETUP_WITH || From 5b3260c8124758c8b5f33dba84cfaac8539d2e32 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 10 Sep 2018 15:31:30 -0600 Subject: [PATCH 3/7] Zero-out PyInterpreterState when initializing. --- Python/pystate.c | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index aa5965552c256a..54c38b6d6f415c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -133,28 +133,12 @@ PyInterpreterState_New(void) return NULL; } + memset(interp, 0, sizeof(*interp)); interp->id_refcount = -1; - interp->id_mutex = NULL; - interp->modules = NULL; - interp->modules_by_index = NULL; - interp->sysdict = NULL; - interp->builtins = NULL; - interp->builtins_copy = NULL; - interp->tstate_head = NULL; interp->check_interval = 100; - interp->num_threads = 0; - interp->pythread_stacksize = 0; - interp->codec_search_path = NULL; - interp->codec_search_cache = NULL; - interp->codec_error_registry = NULL; - interp->codecs_initialized = 0; - interp->fscodec_initialized = 0; interp->core_config = _PyCoreConfig_INIT; interp->config = _PyMainInterpreterConfig_INIT; - interp->importlib = NULL; - interp->import_func = NULL; interp->eval_frame = _PyEval_EvalFrameDefault; - interp->co_extra_user_count = 0; #ifdef HAVE_DLOPEN #if HAVE_DECL_RTLD_NOW interp->dlopenflags = RTLD_NOW; @@ -162,13 +146,6 @@ PyInterpreterState_New(void) interp->dlopenflags = RTLD_LAZY; #endif #endif -#ifdef HAVE_FORK - interp->before_forkers = NULL; - interp->after_forkers_parent = NULL; - interp->after_forkers_child = NULL; -#endif - interp->pyexitfunc = NULL; - interp->pyexitmodule = NULL; HEAD_LOCK(); if (_PyRuntime.interpreters.next_id < 0) { From 53d1e56c93bbe52a62e7920551c7ac791a4b4565 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 28 Jan 2019 11:58:04 -0700 Subject: [PATCH 4/7] Lock "HEAD" around looking up intepreter by ID. --- Python/pystate.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index 54c38b6d6f415c..20929616e01a29 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -314,28 +314,39 @@ PyInterpreterState_GetID(PyInterpreterState *interp) } -PyInterpreterState * -_PyInterpreterState_LookUpID(PY_INT64_T requested_id) +static PyInterpreterState * +interp_look_up_id(PY_INT64_T requested_id) { - if (requested_id < 0) - goto error; - PyInterpreterState *interp = PyInterpreterState_Head(); while (interp != NULL) { PY_INT64_T id = PyInterpreterState_GetID(interp); - if (id < 0) + if (id < 0) { return NULL; - if (requested_id == id) + } + if (requested_id == id) { return interp; + } interp = PyInterpreterState_Next(interp); } - -error: - PyErr_Format(PyExc_RuntimeError, - "unrecognized interpreter ID %lld", requested_id); return NULL; } +PyInterpreterState * +_PyInterpreterState_LookUpID(PY_INT64_T requested_id) +{ + PyInterpreterState *interp = NULL; + if (requested_id >= 0) { + HEAD_LOCK(); + interp = interp_look_up_id(requested_id); + HEAD_UNLOCK(); + } + if (interp == NULL && !PyErr_Occurred()) { + PyErr_Format(PyExc_RuntimeError, + "unrecognized interpreter ID %lld", requested_id); + } + return interp; +} + int _PyInterpreterState_IDInitref(PyInterpreterState *interp) From b382ad8ed1a92c20f2b9ba134cb1e6b08a6ad440 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 8 Mar 2019 16:02:09 -0700 Subject: [PATCH 5/7] Add PyInterpreterState.finalizing. --- Include/internal/pycore_pystate.h | 2 ++ Python/pylifecycle.c | 1 + 2 files changed, 3 insertions(+) diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 7796223b59e650..1335ae0533e0e8 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -31,6 +31,8 @@ struct _is { int64_t id_refcount; PyThread_type_lock id_mutex; + int finalizing; + PyObject *modules; PyObject *modules_by_index; PyObject *sysdict; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 08107296be0658..0902508429a3e7 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1460,6 +1460,7 @@ Py_EndInterpreter(PyThreadState *tstate) Py_FatalError("Py_EndInterpreter: thread is not current"); if (tstate->frame != NULL) Py_FatalError("Py_EndInterpreter: thread still has a frame"); + interp->finalizing = 1; wait_for_thread_shutdown(); From deafc771c2e0d16009540ec0c7660b1c2a2a0b78 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 4 Dec 2018 13:57:24 -0800 Subject: [PATCH 6/7] Factor out _push_pending_call() and _pop_pending_call(). --- Python/ceval.c | 58 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/Python/ceval.c b/Python/ceval.c index ab6a5e0f12182f..0f694fce57b730 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -323,6 +323,35 @@ _PyEval_SignalReceived(void) SIGNAL_PENDING_SIGNALS(); } +/* Push one item onto the queue while holding the lock. */ +static int +_push_pending_call(int (*func)(void *), void *arg) +{ + int i = _PyRuntime.ceval.pending.last; + int j = (i + 1) % NPENDINGCALLS; + if (j == _PyRuntime.ceval.pending.first) { + return -1; /* Queue full */ + } + _PyRuntime.ceval.pending.calls[i].func = func; + _PyRuntime.ceval.pending.calls[i].arg = arg; + _PyRuntime.ceval.pending.last = j; + return 0; +} + +/* Pop one item off the queue while holding the lock. */ +static void +_pop_pending_call(int (**func)(void *), void **arg) +{ + int i = _PyRuntime.ceval.pending.first; + if (i == _PyRuntime.ceval.pending.last) { + return; /* Queue empty */ + } + + *func = _PyRuntime.ceval.pending.calls[i].func; + *arg = _PyRuntime.ceval.pending.calls[i].arg; + _PyRuntime.ceval.pending.first = (i + 1) % NPENDINGCALLS; +} + /* This implementation is thread-safe. It allows scheduling to be made from any thread, and even from an executing callback. @@ -331,7 +360,6 @@ _PyEval_SignalReceived(void) int Py_AddPendingCall(int (*func)(void *), void *arg) { - int i, j, result=0; PyThread_type_lock lock = _PyRuntime.ceval.pending.lock; /* try a few times for the lock. Since this mechanism is used @@ -346,6 +374,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg) * this function is called before any bytecode evaluation takes place. */ if (lock != NULL) { + int i; for (i = 0; i<100; i++) { if (PyThread_acquire_lock(lock, NOWAIT_LOCK)) break; @@ -354,15 +383,8 @@ Py_AddPendingCall(int (*func)(void *), void *arg) return -1; } - i = _PyRuntime.ceval.pending.last; - j = (i + 1) % NPENDINGCALLS; - if (j == _PyRuntime.ceval.pending.first) { - result = -1; /* Queue full */ - } else { - _PyRuntime.ceval.pending.calls[i].func = func; - _PyRuntime.ceval.pending.calls[i].arg = arg; - _PyRuntime.ceval.pending.last = j; - } + int result = _push_pending_call(func, arg); + /* signal main loop */ SIGNAL_PENDING_CALLS(); if (lock != NULL) @@ -428,24 +450,18 @@ make_pending_calls(void) /* perform a bounded number of calls, in case of recursion */ for (int i=0; i Date: Sat, 15 Sep 2018 12:48:03 -0600 Subject: [PATCH 7/7] Add _PyRuntimeState.main_thread. --- Include/internal/pycore_ceval.h | 1 - Include/internal/pycore_pystate.h | 2 ++ Python/ceval.c | 22 +++++++++++----------- Python/pystate.c | 2 ++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index b9f2d7d1758537..c8e09bac074dba 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -12,7 +12,6 @@ extern "C" { #include "pythread.h" struct _pending_calls { - unsigned long main_thread; PyThread_type_lock lock; /* Request for running pending calls. */ _Py_atomic_int calls_to_do; diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index 1335ae0533e0e8..2b913de076aa2b 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -209,6 +209,8 @@ typedef struct pyruntimestate { struct _xidregitem *head; } xidregistry; + unsigned long main_thread; + #define NEXITFUNCS 32 void (*exitfuncs[NEXITFUNCS])(void); int nexitfuncs; diff --git a/Python/ceval.c b/Python/ceval.c index 0f694fce57b730..356335a7c391b6 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -174,9 +174,11 @@ PyEval_InitThreads(void) PyThread_init_thread(); create_gil(); take_gil(_PyThreadState_GET()); - _PyRuntime.ceval.pending.main_thread = PyThread_get_thread_ident(); - if (!_PyRuntime.ceval.pending.lock) + // Set it to the ID of the main thread of the main interpreter. + _PyRuntime.main_thread = PyThread_get_thread_ident(); + if (!_PyRuntime.ceval.pending.lock) { _PyRuntime.ceval.pending.lock = PyThread_allocate_lock(); + } } void @@ -243,9 +245,9 @@ PyEval_ReInitThreads(void) if (!gil_created()) return; recreate_gil(); - _PyRuntime.ceval.pending.lock = PyThread_allocate_lock(); take_gil(current_tstate); - _PyRuntime.ceval.pending.main_thread = PyThread_get_thread_ident(); + _PyRuntime.main_thread = PyThread_get_thread_ident(); + _PyRuntime.ceval.pending.lock = PyThread_allocate_lock(); /* Destroy all threads except the current one */ _PyThreadState_DeleteExcept(current_tstate); @@ -395,10 +397,10 @@ Py_AddPendingCall(int (*func)(void *), void *arg) static int handle_signals(void) { - /* Only handle signals on main thread. */ - if (_PyRuntime.ceval.pending.main_thread && - PyThread_get_thread_ident() != _PyRuntime.ceval.pending.main_thread) - { + /* Only handle signals on main thread. PyEval_InitThreads must + * have been called already. + */ + if (PyThread_get_thread_ident() != _PyRuntime.main_thread) { return 0; } /* @@ -423,9 +425,7 @@ make_pending_calls(void) static int busy = 0; /* only service pending calls on main thread */ - if (_PyRuntime.ceval.pending.main_thread && - PyThread_get_thread_ident() != _PyRuntime.ceval.pending.main_thread) - { + if (PyThread_get_thread_ident() != _PyRuntime.main_thread) { return 0; } diff --git a/Python/pystate.c b/Python/pystate.c index 20929616e01a29..ec8dba8ee58d88 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -60,6 +60,8 @@ _PyRuntimeState_Init_impl(_PyRuntimeState *runtime) return _Py_INIT_ERR("Can't initialize threads for cross-interpreter data registry"); } + // runtime->main_thread is set in PyEval_InitThreads(). + return _Py_INIT_OK(); }