-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Proposal: Typed suspend
#7291
Comments
Small nitpick, but the check for |
That's meant to be an atomic lock -- so if the continuation and cancellation resolve at the same time, they won't both try to resume at once. Double resume is UB in unsafe modes (or should be, if I understand correctly). I did notice some obvious holes in that impl though, so I've patched them (there might be more, but it's only for demonstration anyway, so who cares). |
I think this can already (somewhat) be implemented in status-quo by communication via pointers. Untested: const action = enum{.go, .stop};
fn asynchronous_task(action_ptr_loc: **action) result_type {
var resumer_requested_action: action = .go;
action_ptr_loc.* = &requested_action;
// ...
suspend;
switch(resumer_requested_action){
// decide what to do...
}
// ...
}
fn caller(){
var request_ptr: *action = undefined;
var aframe = async asynchronous_task(&request_ptr);
// ...
if() { //the same atomic-exchange you did
request_ptr.* = if(should_stop) .stop else .go;
resume aframe;
}
} The downside is that you have to transport both I think the ability to upcast from
It can still be useful to have a type-erased handle for both, until you decide to split it up at a later point in the code - I assume that would be Also, small nitpick, the syntax |
De-lurking for a second. Zig already has extended the meaning of const result = suspend {
event_loop.registerContinuation(
@frame(),
...
);
} else {
// do cancellation actions.
} This may be the wrong place to put that, but my understanding of the whole idea is that there are really only two things that can happen to a continuation from outside: resume and cancel. And if cancel can only be caught/happen at suspend points, then it seems like Just a random thought... Re-lurking. |
One problem: where do you store that value?
As per discussion on #5277, no, this is not generally useful, and should not be encouraged. There is
The first argument is the frame, the second is the suspend value. This is the case at every
There are other use cases for suspend values as well -- read the proposal.
|
Inside the async function's stack frame, like it is in the status-quo code I posted.
I meant ambiguity from a reader's perspective who's never encountered that syntax before, potentially mistaking it for a multi-resume (compare |
No good -- the resumer can't know whether such a value is stored, and allocating space for it on the off-chance someone might want to do that is just wasteful. Upon further consideration, this is a bad idea. #5277 already covers differentiating |
Inspired by discussion on #5913. Conceived first by @kprotty, although he never wrote it down 😉
Due to technical issues (pointed out by King) involving nested frames and the stubborn nature of some functions, cancelling a function from the awaiter side is sadly impractical. Something resembling cancellation is possible from the resumer side, however I believe that implementing this at the language level is a folly -- our job as language designers is not to provide every feature, but to provide only the features necessary to build whatever abstractions are suitable.
To that end, I propose one change to suspension semantics: rather than being valueless,
suspend
and suspend blocks will return a value to the function in which they appear, passed to them by the correspondingresume
. An event loop may use this to inform a function how to proceed:The invoker may specify a cancellation condition as argument (e.g. a timeout for a web request), and then the function will register the appropriate callbacks with the event loop. The event loop may even provide a method to generate an awaiter-triggerable cancel token, one end of which could be passed to a generically cancelable function. If a function is not cancelable (some file operations on some kernels), it can simply not take such a condition, and the user will not mistakenly try to cancel it.
Since
@frame()
may be called anywhere within the function, and the resumer needs to know the type before analysing the frame, the suspend type (T
inanyframe<-T
) must be part of the function's signature. I propose we reusewhile
loop continuation syntax:Any function that uses the
suspend
keyword must have a suspend type. This is not function colouring, as any function with explicitsuspend
is necessarily asynchronous anyway (functions that onlyawait
cannot be keyword-resume
d, so do not need a suspend type). The suspend type may bevoid
orerror!void
(no error set inference, since such errors originate outside the function), in which case the handle type isanyframe<-void
oranyframe<-error!void
(notanyframe
-- we require strongly typed handles for type checking, which is one drawback), andresume
does not necessarily take a second argument, as in status quo.This not only permits flexible evented userspace cancellation, but also more specialised continuation conditions: a function waiting for multiple files to become available could receive a handle to the first one that does, and combined with a mechanism to check whether a frame has completed, #5263 could be implemented in userspace in the same manner.
At first blush, this may appear to be hostile to inlining async functions -- however, allowing that would already require semantic changes (#5277) that actually complement this quite nicely:
@frame()
would returnanyframe<-T
of the syntactically enclosing function's suspend type, regardless of the suspend type of the underlying frame, and there is now a strict delineation between resumable and awaitable handles.The text was updated successfully, but these errors were encountered: