Discussion: "Timeouts and cancellation for humans"

You can use this thread to discuss the blog post Timeouts and cancellation for humans.

So requests doesn’t have to do anything to pass this through – when it eventually sends and receives data over the network, those primitive calls will automatically have the deadline applied.

Just to be clear – this refers to a hypothetical requests library with support for async and trio in particular (as implemented e.g. by asks), not the actual requests library, right? I tried running requests.get on a slow responding server inside a with trio.move_on_after(...) block, and trio’s timeout was not applied.

Not complaining, just making sure I understand this correctly – if it worked without any effort required from third-party library developers, it would be downright magical :slight_smile:

As an aside, thank you so much for all your posts related to async, as well as the trio docs! Your writing is top-notch, you manage to keep it accessible yet technically detailed, a rare ability :slight_smile:

Never mind, I should have finished reading the article before starting to experiment and asking questions :slight_smile: The Summary section makes it pretty clear that requests indeed needs to be ported over to async + trio before the sample timeout code can work.

I’m guessing the timing on the Cancel tokens described here won’t be perfectly exact - it’ll take some (small) amount of time to call after, such that time.monotonic is slightly late.

Assuming that’s the case, would this timing error compound with repeated calls to after?
If not, why?, and if so, could the Cancel token structure be changed to fix this?

I’m guessing this isn’t super important for most of trio’s applications, but I was curious about it anyway - I’ve seen a similar construct in Ada where non-compounding timing errors matter a lot.

Trio lets you set timeouts as either relative or absolute times, and it always stores them internally as absolute times. For example, move_on_after(10) is just a convenient shorthand for move_on_at(trio.current_time() + 10), and sleep(10) is a shorthand for sleep_until(trio.current_time() + 10). So if you’re in a situation where you want to e.g. set a timer for every 10 seconds, you can do that by using the absolute time API: sleep_until(start_time + i*10).

I just now stumbled upon this post and absolutely loved it (as well as Trio’s overall nursery idea). I was curious of you had ever heard of GLib’s GCancellable, which is sort of like cancel tokens, except a lot more primitive with less sugar (because C).

I’ve read this article a few times over the years, and will try to sum up the high-level implications:

  • njs lays out a better programming mechanism for timeouts and cancellation (arguably, setting a new state-of-the-art)
  • for the case of concurrent programs, the solution relies on structured concurrency
  • in the domain of “making concurrency manageable”, the solution likely represents the first must-have feature built on top of structured concurrency
  • it’s reiterating the strength of structured concurrency: that the paradigm allows programming solutions (in the form of API, language control structure, etc.) that work for non-concurrent programs to be applied equally to concurrent programs

What I’m still curious about: was the article and associated Trio implementation the first time that cancellation was done this way (on top of structured concurrency)? I’m not familiar with Kotlin’s cancellation API, and wonder if @elizarov could comment on whether it was based on Nathaniel’s work.

Reading this article for a second time, and I have a remark:

def send_websocket_messages(url, messages, cancel_token):
    open_websocket_connection(url, cancel_token=cancel_token)
    try:
        for message in messages:
            ws.send_message(message, cancel_token=cancel_token)
    finally:
        ws.close(cancel_token=cancel_token)

Once the cancel token is triggered, then all future operations on that token are cancelled, so the call to ws.close doesn’t get stuck. It’s a less error-prone paradigm.

Except that this only works well in case of network error, but if the user clicks “Abort” button, we still want to gracefully close the socket, i.e. to run ws.close() normally. Here the cancel token works like an emergency fire suppression system - it sucks air from the building and doesn’t care for normal procedures. Even when some cancellations should perform normal procedures. Like the author said, it’s difficult.