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

variant of wait() that guarantees that predicate matches when it returns #19

Open
girtsf opened this issue Oct 29, 2021 · 4 comments
Open

Comments

@girtsf
Copy link

girtsf commented Oct 29, 2021

Would there be interest in adding a variant of wait that only returns if the predicate is still True? I find that I sometimes need to write stuff like:

foo = trio_util.AsyncValue("start")
# ... start up some other task that can modify foo
while True:
  await foo.wait_value("bar")
  if foo.value != "bar":
    continue
  # ... now we now that "foo" is bar, at least until we do something that yields control

Maybe something like:

await foo.wait_value("bar", recheck=True)
# ... now we now that "foo" is bar, at least until we do something that yields control
@belm0
Copy link
Contributor

belm0 commented Oct 31, 2021

Would there be interest in adding a variant of wait that only returns if the predicate is still True?

It isn't possible to implement, based on the way coroutines and the Trio scheduler work. Given await foo(), there is an arbitrary amount of time and task executions between the async foo() returning and the caller receiving control.

Note that the AsyncValue API always returns the value that triggered the predicate. Many use cases rely on this, so that they don't miss fast-moving values. E.g.:

val = await foo.wait_value(lambda x: x > 10)
handle_high_foo(val)  # guaranteed to be > 10

@belm0
Copy link
Contributor

belm0 commented Oct 31, 2021

... now we know that "foo" is bar, at least until we do something that yields control

I'd argue that this is an uphill battle, because in general code tends to need async calls for something, and then your invariant is violated.

If you can share more details on your use case, I may be able to suggest something.

@girtsf
Copy link
Author

girtsf commented Nov 1, 2021

Hi @belm0!

It isn't possible to implement, based on the way coroutines and the Trio scheduler work. Given await foo(), there is an arbitrary amount of time and task executions between the async foo() returning and the caller receiving control.

From my reading of https://trio.readthedocs.io/en/stable/reference-core.html#checkpoints, I understand that the Trio scheduler only gets to reschedule tasks during checkpoints. Checkpoints happen in async functions that are built into Trio, e.g., trio.sleep, trio.lowlevel.checkpoint. Internally these functions will call into Trio's scheduler to pause these tasks, and typically will have some kind of callback that will tell Trio to wake these tasks up. My understanding is that there isn't any magic in the awaiting on an arbitrary async function that would allow Trio to create a scheduling point.

So it's my understanding that in this:

async def foo():
  print("a")
  await trio.sleep(0)
  print("b")

async def bar():
  await foo()
  print("c")

...
await bar()

we might get paused between "a" and "b", but Trio will never have the opportunity to interrupt us between printing "b" and "c".

Please let me know if I'm misunderstanding how async/Trio works, I'm relatively new to async & Trio. :)

@belm0
Copy link
Contributor

belm0 commented Nov 2, 2021

That's correct. I see, so the wait function is checking the value, and either returning directly to the caller or going back to sleep.

Along the lines of my other comment about code tending to need async calls, I guess in practice I've learned not to rely on that property. For example, events are often layered or aggregated, say by passing several wait_value() calls to wait_any(), which will introduce intermediate scheduling.

The use case is a little odd: we're saying that as long as time is frozen, you'd want to act on the event. But if you advance time by even 1 ms (i.e. let another task run), the value may be changed, and then you don't want to act on it. At least in our application (soft real-time robot) I don't think that's a common pattern-- but I'd like to keep an eye out for it over some time, and think about the use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants