Skip to content

Add a Per-Interpreter "HEAD" Lock #114940

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

Open
ericsnowcurrently opened this issue Feb 2, 2024 · 10 comments
Open

Add a Per-Interpreter "HEAD" Lock #114940

ericsnowcurrently opened this issue Feb 2, 2024 · 10 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-subinterpreters type-feature A feature request or enhancement

Comments

@ericsnowcurrently
Copy link
Member

ericsnowcurrently commented Feb 2, 2024

Feature or enhancement

We have a lock that we use for thread-safety when working with the set of interpreters: _PyRuntimeState.interpreters.mutex. We use two macros in several core files to manage that lock: HEAD_LOCK() and HEAD_UNLOCK() (from pycore_pystate.h).

Recently we've begun using that runtime-global lock for thread-safety when working with a single interpreter's set of thread states (PyInterpreterState.threads). The problem is that this means we are potentially blocking interpreters unnecessarily.

Thus it makes sense to introduce an interpreter-specific "head" lock: PyInterpreterState.threads.mutex (plus corresponding macros).

Linked PRs

@ericsnowcurrently ericsnowcurrently added type-feature A feature request or enhancement interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-subinterpreters 3.13 bugs and security fixes labels Feb 2, 2024
@Eclips4 Eclips4 removed the 3.13 bugs and security fixes label Feb 3, 2024
@rruuaanng
Copy link
Contributor

rruuaanng commented Oct 14, 2024

Can this issue be closed? It seems to have been merged.

@ericsnowcurrently
Copy link
Member Author

What change do you mean? AFAIK, we still only have the global HEAD_LOCK() and HEAD_UNLOCK() from pycore_pystate.h. Nothing has been done for this issue yet, has it?.

@rruuaanng
Copy link
Contributor

rruuaanng commented Oct 15, 2024

Oh, I'm sorry, I didn't seem to check the content of PR carefully. Please forgive my negligence.

Edit:
If allowed, I would like to take over this task. I‘ll add a mutex member to threads of PyInterpreterState and add corresponding macros for locking/unlocking. Finally, I'll try to replace the part that uses HEAD_LOCK(_PyRuntime).
(Or maybe you just need to add mutex member and macros).

@ericsnowcurrently
Copy link
Member Author

Any help would be very welcome! 😄 I'd be glad to review your PR.

@rruuaanng
Copy link
Contributor

Maybe you can wait for my good news. I'll launch it today :).

@rruuaanng
Copy link
Contributor

rruuaanng commented Oct 16, 2024

I'm not sure if this‘s what you want, please review!
rruuaanng@414cfef
If the sections using the GIL need to be replaced with statements for this change, I'd be happy to go ahead and make the change.

Edit:
for example

--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -2371,7 +2371,6 @@ _PyEval_StartTheWorld(PyInterpreterState *interp)
 int
 PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
 {
-    _PyRuntimeState *runtime = &_PyRuntime;
     PyInterpreterState *interp = _PyInterpreterState_GET();
 
     /* Although the GIL is held, a few C API functions can be called
@@ -2380,7 +2379,7 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
      * list of thread states we're traversing, so to prevent that we lock
      * head_mutex for the duration.
      */
-    HEAD_LOCK(runtime);
+    INTERP_THREAD_LOCK(interp);
     for (PyThreadState *tstate = interp->threads.head; tstate != NULL; tstate = tstate->next) {
         if (tstate->thread_id != id) {
             continue;
@@ -2401,7 +2400,7 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
         _Py_set_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT);
         return 1;
     }
-    HEAD_UNLOCK(runtime);
+    INTERP_THREAD_UNLOCK(interp);
     return 0;
 }

@kumaraditya303
Copy link
Contributor

@ericsnowcurrently I wonder whether it's time to change this lock to recursive lock, we have had issues about deadlocks where re-entrant calls can deadlock, ex Py_DECREF.

@rruuaanng
Copy link
Contributor

If we consider recursive locks, we can try to add a count field to threads and maintain it through atomic operations.

@ericsnowcurrently
Copy link
Member Author

Yeah, a recursive lock could simplify a few things and/or provide better thread-safety. We do have a number of places where we cut some corners in that regard. That said, there still may be a risk of deadlock, e.g. finalizers that create threads, which we'd have to resolve.

@ericsnowcurrently
Copy link
Member Author

FWIW, having looked at the PR, I think we should take a little time to make sure we have a clear picture of what should change to the per-interpreter lock. There are also several cases that have possible races that we need to sort out.

ericsnowcurrently added a commit that referenced this issue Nov 21, 2024
This is a precursor to the actual fix for gh-114940, where we will change these macros to use the new lock.  This change is almost entirely mechanical; the exceptions are the loops in codeobject.c and ceval.c, which now hold the "head" lock.  Note that almost all of the uses of _Py_FOR_EACH_TSTATE_UNLOCKED() here will change to _Py_FOR_EACH_TSTATE_BEGIN() once we add the new per-interpreter lock.
ebonnal pushed a commit to ebonnal/cpython that referenced this issue Jan 12, 2025
…hongh-127077)

This is a precursor to the actual fix for pythongh-114940, where we will change these macros to use the new lock.  This change is almost entirely mechanical; the exceptions are the loops in codeobject.c and ceval.c, which now hold the "head" lock.  Note that almost all of the uses of _Py_FOR_EACH_TSTATE_UNLOCKED() here will change to _Py_FOR_EACH_TSTATE_BEGIN() once we add the new per-interpreter lock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) topic-subinterpreters type-feature A feature request or enhancement
Projects
Status: Todo
Development

No branches or pull requests

4 participants