Skip to content
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

Implement stop-the-world functionality (for --disable-gil builds) #111964

Closed
Tracked by #108219
colesbury opened this issue Nov 10, 2023 · 3 comments
Closed
Tracked by #108219

Implement stop-the-world functionality (for --disable-gil builds) #111964

colesbury opened this issue Nov 10, 2023 · 3 comments
Assignees
Labels
3.13 bugs and security fixes topic-free-threading type-feature A feature request or enhancement

Comments

@colesbury
Copy link
Contributor

colesbury commented Nov 10, 2023

Feature or enhancement

The --disable-gil builds occasionally need to pause all but one thread. Some examples include:

  • Cyclic garbage collection, where this is often called a "stop the world event"
  • Before calling fork(), to ensure a consistent state for internal data structures
  • During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

In the nogil-3.12 fork, a stop-the-world call paused all threads in all interpreters. In CPython 3.13, we probably want to provide two levels of "stop-the-world": per-interpreter and global. In general, per-interpreter pauses are preferable to global pauses, but there are some cases (like before fork()) where want all threads to pause, so that we don't fork() while a thread is modifying some global runtime structure.

In the default build, stop-the-world calls should generally be no-ops, but global pauses may be useful in a few cases, such as before fork() when using multiple interpreters each with their own "GIL".

In the nogil-3.12 fork, this was implemented together with other GC modifications: colesbury/nogil-3.12@2864b6b36e. I'd like to implement it separately in 3.13 to keep the PRs smaller.

Linked PRs

@Hels15
Copy link
Contributor

Hels15 commented Nov 21, 2023

Is someone already working on this? @colesbury

@colesbury
Copy link
Contributor Author

@Hels15 I've been working on this

colesbury added a commit to colesbury/cpython that referenced this issue Nov 27, 2023
The `--disable-gil` builds occasionally need to pause all but one thread. Some
examples include:

* Cyclic garbage collection, where this is often called a "stop the world event"
* Before calling `fork()`, to ensure a consistent state for internal data structures
* During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

This adds the following functions to implement global and per-interpreter pauses:

* `_PyRuntimeState_StopTheWorld` and `_PyRuntimeState_StartTheWorld`
* `_PyInterpreterState_StopTheWorld` and `_PyInterpreterState_StartTheWorld`

These functions are no-ops outside of the `--disable-gil` build.

This also adds `_PyRWMutex`, a "readers-writer" lock, which is used to
serialize global stop-the-world pauses with per-interpreter pauses.
colesbury added a commit to colesbury/cpython that referenced this issue Dec 7, 2023
This adds `_PyRWMutex`, a "readers-writer" lock, which wil be used to
serialize global stop-the-world pauses with per-interpreter pauses.
colesbury added a commit to colesbury/cpython that referenced this issue Dec 7, 2023
The `--disable-gil` builds occasionally need to pause all but one thread. Some
examples include:

* Cyclic garbage collection, where this is often called a "stop the world event"
* Before calling `fork()`, to ensure a consistent state for internal data structures
* During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

This adds the following functions to implement global and per-interpreter pauses:

* `_PyRuntimeState_StopTheWorld` and `_PyRuntimeState_StartTheWorld`
* `_PyInterpreterState_StopTheWorld` and `_PyInterpreterState_StartTheWorld`

These functions are no-ops outside of the `--disable-gil` build.

This also adds `_PyRWMutex`, a "readers-writer" lock, which is used to
serialize global stop-the-world pauses with per-interpreter pauses.
ericsnowcurrently pushed a commit that referenced this issue Dec 16, 2023
This adds `_PyRWMutex`, a "readers-writer" lock, which wil be used to
serialize global stop-the-world pauses with per-interpreter pauses.
colesbury added a commit to colesbury/cpython that referenced this issue Dec 18, 2023
The `--disable-gil` builds occasionally need to pause all but one thread. Some
examples include:

* Cyclic garbage collection, where this is often called a "stop the world event"
* Before calling `fork()`, to ensure a consistent state for internal data structures
* During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

This adds the following functions to implement global and per-interpreter pauses:

* `_PyRuntimeState_StopTheWorld` and `_PyRuntimeState_StartTheWorld`
* `_PyInterpreterState_StopTheWorld` and `_PyInterpreterState_StartTheWorld`

These functions are no-ops outside of the `--disable-gil` build.

This also adds `_PyRWMutex`, a "readers-writer" lock, which is used to
serialize global stop-the-world pauses with per-interpreter pauses.
@colesbury
Copy link
Contributor Author

@markshannon: Carl mentioned you had a question about the stop-the-world implementation and reentrancy. The current implementation in the linked PR does not support reentrancy. Prior implementations in the nogil fork supported reentrancy and behaved like recursive mutexes: only the "outer most" calls stopped or resumed threads; "inner" calls only increment/decrement a counter.

The only place reentrancy was useful was during runtime finalization (i.e., interpreter shutdown). The stop-the-world mechanism is useful for forcing daemon threads 1 to a consistent state right before we delete their thread states, but we also trigger the GC later during shutdown. Making the stop-the-world calls reentrant was a convenient "hack", but I think there are other ways to achieve the same effect.

I don't think reentrant stop-the-world calls are generally useful because you are quite restricted on what's safe to call during a stop-the-world pause or you risk deadlock. Other paused threads may be holding the locks you need, so you can't call arbitrary python code or destructors. The GC is (or will be) careful to only call finalizers and weakref callbacks after resuming other threads.

Footnotes

  1. and potentially non-daemon threads depending on when you press ctrl-c

ericsnowcurrently pushed a commit that referenced this issue Jan 23, 2024
The `--disable-gil` builds occasionally need to pause all but one thread.  Some
examples include:

* Cyclic garbage collection, where this is often called a "stop the world event"
* Before calling `fork()`, to ensure a consistent state for internal data structures
* During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

This adds the following functions to implement global and per-interpreter pauses:

* `_PyEval_StopTheWorldAll()` and `_PyEval_StartTheWorldAll()` (for the global runtime)
* `_PyEval_StopTheWorld()` and `_PyEval_StartTheWorld()` (per-interpreter)

(The function names may change.)

These functions are no-ops outside of the `--disable-gil` build.
aisk pushed a commit to aisk/cpython that referenced this issue Feb 11, 2024
)

This adds `_PyRWMutex`, a "readers-writer" lock, which wil be used to
serialize global stop-the-world pauses with per-interpreter pauses.
aisk pushed a commit to aisk/cpython that referenced this issue Feb 11, 2024
The `--disable-gil` builds occasionally need to pause all but one thread.  Some
examples include:

* Cyclic garbage collection, where this is often called a "stop the world event"
* Before calling `fork()`, to ensure a consistent state for internal data structures
* During interpreter shutdown, to ensure that daemon threads aren't accessing Python objects

This adds the following functions to implement global and per-interpreter pauses:

* `_PyEval_StopTheWorldAll()` and `_PyEval_StartTheWorldAll()` (for the global runtime)
* `_PyEval_StopTheWorld()` and `_PyEval_StartTheWorld()` (per-interpreter)

(The function names may change.)

These functions are no-ops outside of the `--disable-gil` build.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.13 bugs and security fixes topic-free-threading type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

2 participants