GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-114271: Make `_thread.ThreadHandle` thread-safe in free-threaded builds by mpage · Pull Request #115190 · python/cpython

Conversation

@mpage

@mpage mpage commented Feb 8, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

We protect the mutable state of ThreadHandle using a _PyOnceFlag. Concurrent operations (i.e. join) on ThreadHandle block until it is their turn to execute or an earlier operation succeeds. Once an operation has been applied successfully all future operations complete immediately.

The join() method is now idempotent. It may be called multiple times but the underlying OS thread will only be joined once. After join() succeeds, any future calls to join() will succeed immediately.

The detach() method has been removed; nothing was using it.

  • Issue: Make _threadmodule.c thread-safe in --disable-gil builds #114271

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mpage
Make _thread.ThreadHandle thread-safe in free-threaded builds
cf6491c
We protect the mutable state of `ThreadHandle` using a `_PyOnceFlag`.
Concurrent operations (i.e. `join` or `detach`) on `ThreadHandle` block
until it is their turn to execute or an earlier operation succeeds.
Once an operation has been applied successfully all future operations
complete immediately.

The `join()` method is now idempotent. It may be called multiple times
but the underlying OS thread will only be joined once. After `join()`
succeeds, any future calls to `join()` will succeed immediately.

The `detach()` method is also idempotent. It may be called multiple times
but the underlying OS thread will only be detached once. After `detach()`
succeeds, any future calls to `detach()` will succeed immediately.

If the handle is being joined, `detach()` blocks until the join completes.
@bedevere-app bedevere-app Bot mentioned this pull request Feb 8, 2024
Make _threadmodule.c thread-safe in --disable-gil builds #114271
Closed
@mpage
mpage marked this pull request as ready for review February 9, 2024 00:55
@bedevere-app bedevere-app Bot added the awaiting review label Feb 9, 2024
@mpage

mpage commented Feb 9, 2024

Copy link
Copy Markdown
Contributor Author

@colesbury - Would you please have a look and add the "skip news" label to this? In order to limit the size of the changes I'd like to get this merged first and then stack the tstate_lock stuff on top.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury
colesbury self-requested a review February 9, 2024 00:57
@colesbury colesbury added the skip news label Feb 9, 2024
colesbury
colesbury reviewed Feb 9, 2024
View reviewed changes
Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

mpage added 4 commits February 9, 2024 11:50
@mpage
Don't use PyOnceFlag post fork
e86dde8
@mpage
Check for self-join outside of the once flag
9a61e20
@mpage
Fix grammar and formatting
bcf66ce
@mpage
Update comment.
7f9720d
@mpage

mpage commented Feb 9, 2024

Copy link
Copy Markdown
Contributor Author

@colesbury - This should be good to go. Would you please have another look?

colesbury reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mpage mpage mentioned this pull request Feb 9, 2024
Add _PyOnceFlag_CallOnceTimed #115229
Closed
colesbury
colesbury reviewed Feb 9, 2024
View reviewed changes

@colesbury colesbury left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, the thread id reuse is tricky.

I'd like to avoid adding _PyEventRc if possible. The event doesn't seem like a great fit for ThreadHandleObject because we are never waiting on the event -- we're just using it like an atomic flag.

What about something like:

  1. Make thread_is_exiting a plain variable that we use atomic operations to access, like int thread_is_exiting (because we don't have atomic ops on bool currently)
  2. Pass the ThreadHandleObject to do_start_new_thread instead of having another object with a separate lifetime.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@mpage

mpage commented Feb 9, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

I'd like to avoid adding _PyEventRc if possible.

Me too! I thought pretty hard about this and couldn't come up with a better solution. Plus, I think we're going to need it later anyway if we want to be able to implement join with a timeout, at which point we will be waiting on it.

What about something like: ...

I considered this, but I think we need to set the flag after the PyThreadState has been destroyed. Otherwise, you could end up deadlocking in the following situation:

  1. T0 joins T1.
  2. T1 finishes. It has a thread-local with a finalizer that joins T1. The flag is set at this point, so we skip the self-join check and deadlock.

Since we have to set the flag after the PyThreadState has been destroyed, it's no longer safe to call Py_DECREF, so we can't use a ThreadHandleObject (or any Python object). We could create a ref-counted flag (or re-use one if it exists), but since we're already going to need the _PyEventRc anyway, I figured we'd just use it here.

colesbury reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury
colesbury requested a review from pitrou February 9, 2024 23:32
colesbury
colesbury approved these changes Feb 9, 2024
View reviewed changes

@colesbury colesbury left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

@pitrou, would you please look this over? The motivation is to make thread joining thread-safe in the freethreaded build, as well as fix a race condition that can affect both the default and freethreaded build (described by @mpage in #114839)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting review labels Feb 9, 2024
@mpage
Account for handles being cleared after fork
092c2eb
pitrou
pitrou reviewed Feb 12, 2024
View reviewed changes

@pitrou pitrou left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this looks ok but I think this can be simplified, see below.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Include/internal/pycore_lock.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

mpage added 3 commits February 12, 2024 10:20
@mpage
Move implementations of _PyEventRc functions out of header
77da31d
@mpage
Track all states of the handle in the enum
321a4e0
@mpage
Remove detach() method
cd0372c
@mpage

mpage commented Feb 13, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

@pitrou - I think I've addressed all of your comments except for the question about using bool. A quick grep of the codebase reveals quite a few uses of bool (in central places like obmalloc.c and dictobject.c, among others). Even if there isn't explicit guidance, there's certainly precedent. I've also removed the detach() method. Would you please have another look?

@colesbury - This has changed a bit since you reviewed it. Would you please have another look? In particular, I'd like a sanity check that the use of relaxed atomics for accessing handle state is OK.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mpage

mpage commented Feb 13, 2024

Copy link
Copy Markdown
Contributor Author

The failing macos-13 builds appear unrelated to this PR. They're failing with errors like:

error: call to undeclared function 'is_pad'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    if (py_is_pad(self->win)) {
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

colesbury
colesbury reviewed Feb 13, 2024
View reviewed changes

@colesbury colesbury left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments below

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Edgarruiz856 reacted with heart emoji
All reactions
  • ❤️ 1 reaction
Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@colesbury colesbury mentioned this pull request Feb 13, 2024
Undeclared function 'is_pad' on macos-13 GitHub runners #115383
Closed
@mpage
Don't need to atomically initialize state in ThreadHandle ctor
7badb2d
@mpage
Use sequential consistency for operations on state
1fe27ce
@mpage

mpage commented Feb 14, 2024

Copy link
Copy Markdown
Contributor Author

@pitrou - I think I've addressed everything now. Would you please have a look?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@colesbury
colesbury requested a review from pitrou February 14, 2024 14:32
@pitrou
pitrou requested a review from gpshead February 14, 2024 16:47
@mpage
Merge branch 'main' into pythongh-114271-_thread-ThreadHandle
9ec6d23
@mpage

mpage commented Feb 26, 2024

Copy link
Copy Markdown
Contributor Author

@pitrou @gpshead - Did you want to take a look at this?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gpshead
gpshead requested changes Feb 26, 2024
View reviewed changes

@gpshead gpshead left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(review in progress, more to come)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_thread.py
with threading_helper.wait_threads_exit():
handle = thread.start_joinable_thread(task)
handle.join()
with self.assertRaisesRegex(ValueError, "not joinable"):

@gpshead gpshead Feb 26, 2024

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting... our docs already claim that threads can be joined multiple times. So I wonder why this existing test logic was previously explicitly checking for an error here.

In this sense this change aligns with what our docs claim so a behavior change here could be seen as a bugfix. I do not expect anyone to be depending on subsequent join()s of a thread raising regardless.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_thread.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_thread.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/threading.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot added awaiting changes and removed awaiting merge labels Feb 26, 2024
@bedevere-app

bedevere-app Bot commented Feb 26, 2024

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mpage added 2 commits February 26, 2024 15:18
@mpage
Remove _join_lock attribute
a46b727
@mpage
Add reference to more details for self-join test
61d9fa9
gpshead
gpshead reviewed Feb 26, 2024
View reviewed changes
Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_thread.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@gpshead gpshead self-assigned this Feb 26, 2024
mpage added 4 commits February 26, 2024 16:13
@mpage
Assign explicit values for ThreadHandle states
f6bfa08
@mpage
Detail why non-atomic state access is safe in the destructor
1cc0f2e
@mpage
Document the purpose of bootstate
3fbf119
@mpage
Mark handles running before decrefing args
9d21444
@mpage

mpage commented Feb 27, 2024

Copy link
Copy Markdown
Contributor Author

I have made the requested changes; please review again

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mpage
mpage requested a review from gpshead February 27, 2024 23:54
@mpage

mpage commented Feb 29, 2024

Copy link
Copy Markdown
Contributor Author

@gpshead - Would you please have another look at this?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gpshead
gpshead reviewed Mar 1, 2024
View reviewed changes
Comment thread Modules/_threadmodule.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

gpshead
gpshead approved these changes Mar 1, 2024
View reviewed changes
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting changes labels Mar 1, 2024
@gpshead
gpshead merged commit 9e88173 into python:main Mar 1, 2024
@bedevere-app bedevere-app Bot removed the awaiting merge label Mar 1, 2024
woodruffw pushed a commit to woodruffw-forks/cpython that referenced this pull request Mar 4, 2024
@mpage @woodruffw
pythongh-114271: Make _thread.ThreadHandle thread-safe in free-thre…
94bdef5
…aded builds (pythonGH-115190)

Make `_thread.ThreadHandle` thread-safe in free-threaded builds

We protect the mutable state of `ThreadHandle` using a `_PyOnceFlag`.
Concurrent operations (i.e. `join` or `detach`) on `ThreadHandle` block
until it is their turn to execute or an earlier operation succeeds.
Once an operation has been applied successfully all future operations
complete immediately.

The `join()` method is now idempotent. It may be called multiple times
but the underlying OS thread will only be joined once. After `join()`
succeeds, any future calls to `join()` will succeed immediately.

The internal thread handle `detach()` method has been removed.
adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 25, 2024
@mpage @adorilson
pythongh-114271: Make _thread.ThreadHandle thread-safe in free-thre…
d69e884
…aded builds (pythonGH-115190)

Make `_thread.ThreadHandle` thread-safe in free-threaded builds

We protect the mutable state of `ThreadHandle` using a `_PyOnceFlag`.
Concurrent operations (i.e. `join` or `detach`) on `ThreadHandle` block
until it is their turn to execute or an earlier operation succeeds.
Once an operation has been applied successfully all future operations
complete immediately.

The `join()` method is now idempotent. It may be called multiple times
but the underlying OS thread will only be joined once. After `join()`
succeeds, any future calls to `join()` will succeed immediately.

The internal thread handle `detach()` method has been removed.
diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 17, 2024
@mpage @diegorusso
pythongh-114271: Make _thread.ThreadHandle thread-safe in free-thre…
bf25f87
…aded builds (pythonGH-115190)

Make `_thread.ThreadHandle` thread-safe in free-threaded builds

We protect the mutable state of `ThreadHandle` using a `_PyOnceFlag`.
Concurrent operations (i.e. `join` or `detach`) on `ThreadHandle` block
until it is their turn to execute or an earlier operation succeeds.
Once an operation has been applied successfully all future operations
complete immediately.

The `join()` method is now idempotent. It may be called multiple times
but the underlying OS thread will only be joined once. After `join()`
succeeds, any future calls to `join()` will succeed immediately.

The internal thread handle `detach()` method has been removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@colesbury colesbury colesbury approved these changes
@gpshead gpshead gpshead approved these changes
@pitrou pitrou Awaiting requested review from pitrou

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@gpshead gpshead

Labels

skip news topic-free-threading

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@mpage @gpshead @colesbury @pitrou
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.