Fix deadlock related to _stash_obj_in_tls#180700
Closed
rtimpe wants to merge 1 commit into
Closed
Conversation
rtimpe
added a commit
that referenced
this pull request
Apr 17, 2026
There's potential deadlock related to _stash_obj_in_tls. As of #173568 the context object is stored in the calling thread TLS during _engine_run_backward. Normally this object deallocated after the backward pass completes, but there's a race where the autograd device thread can end up holding the last reference. If this happens the context object gets deallocated by the device thread, which needs to acquire the GIL. If another thread happens to be holding the GIL while blocked on the device thread the result is a deadlock. See #173568 for more detail The fix here avoids aquiring the GIL in the device thread by clearing the GraphTask's shared_ptr to the context object during cleanup. Since the TLS in the calling thread also has a shared_ptr, this avoids calling the SafePyObject destructor (and therefore acquiring the GIL). I'm not a huge fan of this approach since it introduces a manual cleanup step, but I wasn't able to come up with something better. Another option would be to use SafePyHandle instead of SafePyObject and introduce a `_remove_obj_from_tls` python API. But that makes the python side responsible for refcounting, which feels wrong. I'm also not sure exactly how we end up with a thread holding the GIL and also blocked on an autograd device thread. That feels like a bug, but it seems to be triggered by Meta-internal code, so I'm not able to debug it. All of which is to say that this is the best approach I can come up with, but I'm very open to other suggestions. ghstack-source-id: 81c9703 Pull-Request: #180700
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/180700
Note: Links to docs will display an error until the docs builds have been completed. This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
Collaborator
Author
|
Duplicate of #180701 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Stack from ghstack (oldest at bottom):
There's potential deadlock related to _stash_obj_in_tls. As of #173568 the
context object is stored in the calling thread TLS during _engine_run_backward. Normally this object deallocated after
the backward pass completes, but there's a race where the autograd device thread can end up holding the last reference.
If this happens the context object gets deallocated by the device thread, which needs to acquire the GIL. If another
thread happens to be holding the GIL while blocked on the device thread the result is a deadlock. See
#173568 for more detail
The fix here avoids aquiring the GIL in the device thread by clearing the GraphTask's shared_ptr to the context object
during cleanup. Since the TLS in the calling thread also has a shared_ptr, this avoids calling the SafePyObject
destructor (and therefore acquiring the GIL). I'm not a huge fan of this approach since it introduces a manual cleanup
step, but I wasn't able to come up with something better. Another option would be to use SafePyHandle instead of
SafePyObject and introduce a
_remove_obj_from_tlspython API. But that makes the python side responsible forrefcounting, which feels wrong.
I'm also not sure exactly how we end up with a thread holding the GIL and also blocked on an autograd device thread.
That feels like a bug, but it seems to be triggered by Meta-internal code, so I'm not able to debug it. All of which is
to say that this is the best approach I can come up with, but I'm very open to other suggestions.
Authored with Claude.