lilos
lilos copied to clipboard
Idea about strong cancellation safety for handoffs
As far as I understand, the cancel safety issues around push and pop arise from temporarily storing the object inside the future.
A B
| |
(1) (2)
| |
+ -- make offer --> o
| |
o <-- accept it --- +
| |
... ...
Above, I have included a small diagram for two tasks A and B. In (1), task A transfers ownership of what it wants to offers to what I like to call the "shelf". Now, the object can be pushed, but the future does not own it. It keeps a reference to the shelf instead. In (2), task B sets up its own empty shelf with which it would like to attempt the offer, then tries to accept.
If A puts an object on the shelf, and makes an offer, it is cancellation safe, because the shelf is only emptied if the offer is accepted. Thus, any canceled offers can simply not be taken up anymore and the object can be offered once again.
If B sets up an empty shelf, waits for task A to offer an object, task A puts an object onto B's shelf and then B is dropped, so is its non-empty shelf, which could signal to A that it was non-empty when dropped.
In fact, this is not a big idea. It's mostly implemented already, except that the shelves (or guards as they're called in the code) are part of the futures. Simply lifting them out into the surrounding scope should make this API cancellation safe, I think.
// API sketch for producer task
let shelf = pusher.push(x);
shelf.offer().await;
// API sketch for consumer task
let shelf = popper.pop(x);
let x = shelf.accept().await;
EDIT: This is actually conceptually very similar to #9 on both sides. However, I did not see that issue until after I came up with my proposed solution.
If it would be fine to increase the sizes of the Pusher and Popper, the shelves could also be their struct members. This might allow better safety with the same API at cost of the extra shelf members.