-
-
Notifications
You must be signed in to change notification settings - Fork 343
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
Fix from_thread.run(_sync)?
failing in any thread running Trio
#2535
base: main
Are you sure you want to change the base?
Fix from_thread.run(_sync)?
failing in any thread running Trio
#2535
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## master #2535 +/- ##
=======================================
Coverage 92.44% 92.44%
=======================================
Files 118 118
Lines 16336 16341 +5
Branches 3157 3157
=======================================
+ Hits 15101 15106 +5
- Misses 1105 1121 +16
+ Partials 130 114 -16
|
trio/_threads.py
Outdated
@@ -245,13 +245,17 @@ def _run_fn_as_system_task(cb, fn, *args, context, trio_token=None): | |||
"this thread wasn't created by Trio, pass kwarg trio_token=..." | |||
) | |||
|
|||
# Avoid deadlock by making sure we're not called from Trio thread | |||
# Avoid deadlock by making sure we're not called from the token's Trio thread |
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.
maybe "from the same thread we're trying to enter"
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.
that's definitely clearer, thanks. this should be addressed in https://github.com/python-trio/trio/compare/8b5cc1ed973f508cc7d81509077c1dc5fd603910..260c6167cc6243a87192f1810c5a0cc44318d552
newsfragments/2534.bugfix.rst
Outdated
@@ -0,0 +1 @@ | |||
`trio.from_thread.run` and `trio.from_thread.run_sync` no longer raise `RuntimeError` when called in a deadlock-free way from a thread running Trio. |
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.
instead of "from a thread running Trio", maybe elaborate "to enter one thread running Trio from another"? I also wonder if it would be good to add a disclaimer here that using them this way is still suboptimal (will block the originating thread's event loop) but we support it because it can be useful in some cases.
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.
thanks! this should be addressed in https://github.com/python-trio/trio/compare/8b5cc1ed973f508cc7d81509077c1dc5fd603910..260c6167cc6243a87192f1810c5a0cc44318d552
I also wonder if it would be good to add a disclaimer here that using them this way is still suboptimal (will block the originating thread's event loop) but we support it because it can be useful in some cases.
one one hand, i think the usual rule of "don't call a sync function that will block for a long time" covers that already. on the other hand, an extra reminder/disclaimer could be useful.
trio/_threads.py
Outdated
raise RuntimeError("this is a blocking function; call it from a thread") | ||
if trio_token is current_trio_token: | ||
raise RuntimeError( | ||
"this is a blocking function; using it to remotely enter the current " |
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.
s/remotely enter/re-enter/?
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.
Coverage is failing because the |
.....I'm not super keen. If you want to call between Trio threads, then it would be better to have an async way to do that? Intentionally doing blocking calls from a Trio thread can work under the right circumstances, but it's Sketchy. Also, while running multiple Trio threads simultaneously is definitely supported, it's a bit odd and I wonder what you're doing, and if there's a better way to accomplish it? |
i agree. however the use-case i'm after requires sync cross-calling; async cross-calling isn't suitable for it. see also #2534 (comment)
i agree. blocking cross-calls are only safe when the function being cross-called exits in a very short bounded time. so, if i think this is just the normal rule of "don't call a sync function that will block the event loop for a significant period of time", though.
see #2534 (comment) |
260c616
to
eb7c901
Compare
resolves #2191. resolves its duplicate #2534.
with this patch, trio would only prevent users from running
from_thread.run(_sync)?
when trio knows that such a call would deadlock. so, with this patch, trio still prevents "first-order"from_thread.run(_sync)?
deadlocks like https://github.com/python-trio/trio/pull/1574/files#r435039316. i call this first-order because it's the smallest possible cyclic graph:trio_threadA
is blocking waiting fortrio_threadA
.there is a side-effect of this, however. the deadlock heuristic's current aggressiveness means that trio also currently prevents higher-order
from_thread.run(_sync)?
deadlocks, such as a second-order deadlock where the graph cycle istrio_threadA -> trio_threadB -> trio_threadA
. higher-order deadlocks can be created through contrived examples:(click to expand) a higher order `trio.from_thread.run(_sync)?` cyclic deadlock
but while a higher-order deadlock can be created through a contrived example, i don't think they would ever arise naturally; they seem to require having:
multiple threads running trio
at least two trio threads sharing their tokens with another trio thread (in a manner that creates a graph cycle of length >= 2)
a trio thread (say it has token
tokenA
) callingtrio.from_thread.run(_sync)?(foo, trio_token=tokenB)
foo
being a function that will result in a trio thread callingtrio.from_thread.run(_sync)?(..., trio_token=tokenA)
.it seems like writing code that does all three of these things is not something that someone could actually do without realizing that they've probably written a deadlock. (1) is probably uncommon already, (2) requires pretty heavy sharing of
lowlevel.TrioToken
s in a way that raises red flags and screams "potential deadlock!", and (3) requires choosing to call a blocking function (that is not short-lived) in a trio thread.it seems to me that the motivation for the current deadlock heuristic was to prevent first-order
from_thread.run(_sync)?
deadlocks (their implementation is only one line of code, after all), but i don't think thatfrom_thread.run(_sync)?
should be artificially restricted to prevent users from shooting themselves with a footgun that (unless i have missed something important...) appears to be rather difficult to manage to shoot oneself with.