On the user level, it follows the semantics of std.Io, so any blocking operation can return error.Canceled if the parent that spawned the task calls future.cancel() or group.cancel(). Plus you can use cancel protection (to avoid getting error.Canceled), and re-cancel (if you can't propagate error.Canceled from the function).
If you are asking on the implementation level, when you call cancel() it will set a flag on the task that it's been canceled, and then it will wake up the task. When the task resumes, it will be inside the scheduler code (in yield), so it will consume the flag and and return error.Canceled. That will get propagated to the function that called yield(), which will most likely be the function that submitted the operation to the event loop (still zio's internals), so in errdefer, that will cancel the operation on the event loop, and then forward error.Canceled to its called. That would be the std.Io vtable implementation function, so that again just forwards the error and that's it, now you are in the user code where the blocking function returned error.Canceled.
On the event loop level, it depends on the backend, io_uring and IOCP are completion based and can cancel operations directly, while epoll/kqueue/poll backends can only cancel operations while they are waiting for readiness, in which case we simply abort the "poll" (however it's done in the particular backend). Cancelation is typically asynchronous, so the task that wants to cancel an I/O operation actually has to wait for the cancel to complete before returning the error.Canceled error to the caller.
If you are using zio natively, outside of std.Io, you have more options for canceling tasks.
This is my library. Ping me if you have any question about how it works, how to use it, etc.
How does it handle cancellation?
On the user level, it follows the semantics of
std.Io, so any blocking operation can returnerror.Canceledif the parent that spawned the task callsfuture.cancel()orgroup.cancel(). Plus you can use cancel protection (to avoid gettingerror.Canceled), and re-cancel (if you can't propagateerror.Canceledfrom the function).If you are asking on the implementation level, when you call
cancel()it will set a flag on the task that it's been canceled, and then it will wake up the task. When the task resumes, it will be inside the scheduler code (inyield), so it will consume the flag and and returnerror.Canceled. That will get propagated to the function that calledyield(), which will most likely be the function that submitted the operation to the event loop (still zio's internals), so inerrdefer, that will cancel the operation on the event loop, and then forwarderror.Canceledto its called. That would be thestd.Iovtable implementation function, so that again just forwards the error and that's it, now you are in the user code where the blocking function returnederror.Canceled.On the event loop level, it depends on the backend, io_uring and IOCP are completion based and can cancel operations directly, while epoll/kqueue/poll backends can only cancel operations while they are waiting for readiness, in which case we simply abort the "poll" (however it's done in the particular backend). Cancelation is typically asynchronous, so the task that wants to cancel an I/O operation actually has to wait for the cancel to complete before returning the
error.Cancelederror to the caller.If you are using zio natively, outside of
std.Io, you have more options for canceling tasks.This is awesome. Thanks for sharing!