You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add an @trio.not_in_async_task decorator, to error instead of stalling #2757
In a large codebase, you're going to have some functions which are slow, and so you don't want them to be called from an async task (without using trio.to_thread.run_sync(slow_fn), anyway). flake8-trio's TRIO200 error can help find direct calls, but indirect calls are really hard to find via static analysis.
As I mentioned in my PyCon talk, and work we've implemented a decorator to mark sync functions which can be called in a sync context, but must be delegated to a worker thread instead of being called in an async context - no matter how many sync functions are in the intermediate call chain. Simple implementation:
defnot_in_async_task(fn):
"""Decorator for sync functions which should not be called in an async context. <detailed docstring here> """@functools.wraps(fn)defdecorator(*args, **kwargs):
try:
# Identical logic to trio.lowlevel.current_task()GLOBAL_RUN_CONTEXT.taskexceptAttributeError:
passelse:
raiseRuntimeError(
f"{fn.__name__} should not be called in an async context. "f"Use an async alternative, or trio.to_thread.run_sync({fn.__name__})."
)
returnfn(*args, *kwargs)
returndecorator
Should we include this in trio itself? I've certainly found the pattern useful, and upstreaming it would encourage wider use of the pattern. On the other hand it's not entirely clear where this would fit, and it's neither hard to implement nor something which benefits much from standardization.
Perhaps the more fundamental primitive here is trio.in_trio_context(), which returns a bool. You can write it yourself using only public APIs, but it's weird:
The private implementation could check for the existence of GLOBAL_RUN_CONTEXT.runner.
(There is a subtle difference between checking for runner and checking for task: the former exists even in Trio context outside of a task, which you can observe in abort_fns and instrument calls. I think the runner version is more correct for this need.)
What's the expected behavior in guest runs? GLOBAL_RUN_CONTEXT.runner seems to exist there but... I don't think the host loop should be considered a trio context?
(maybe we should just tell people "use sniffio.current_async_library" because that has better semantics? I don't know what it returns for to_thread.run_sync calls though.)
In a guest run, as well as some other places like abort_fn calls and some instrument calls, you are in a Trio run (runner exists) but not in a Trio task (task does not exist). These concepts are separate but people tend to confuse them (and it doesn't help that Trio's error message is "must be run from async context" for both).
Activity
oremanj commentedon Aug 17, 2023
Perhaps the more fundamental primitive here is
trio.in_trio_context(), which returns a bool. You can write it yourself using only public APIs, but it's weird:The private implementation could check for the existence of
GLOBAL_RUN_CONTEXT.runner.(There is a subtle difference between checking for
runnerand checking fortask: the former exists even in Trio context outside of a task, which you can observe in abort_fns and instrument calls. I think therunnerversion is more correct for this need.)A5rocks commentedon Aug 2, 2024
I think this would be helpful. Given that debugging tools seem to be in
trio.lowleveltypically, should this go there too?CoolCat467 commentedon Aug 2, 2024
I would agree that
lowlevelwould be a good home forin_trio_contextA5rocks commentedon Jan 30, 2025
What's the expected behavior in guest runs?
GLOBAL_RUN_CONTEXT.runnerseems to exist there but... I don't think the host loop should be considered a trio context?(maybe we should just tell people "use
sniffio.current_async_library" because that has better semantics? I don't know what it returns forto_thread.run_synccalls though.)oremanj commentedon Jan 30, 2025
In a guest run, as well as some other places like abort_fn calls and some instrument calls, you are in a Trio run (
runnerexists) but not in a Trio task (taskdoes not exist). These concepts are separate but people tend to confuse them (and it doesn't help that Trio's error message is "must be run from async context" for both).I attempted to make sniffio explicit about this using python-trio/sniffio#38 and the linked python-trio/sniffio#39 but it was never reviewed.
I think Trio should probably provide both
lowlevel.in_trio_runandlowlevel.in_trio_task.