hawkw / thingbuf

in-place allocation-reusing queues for Rust
MIT License
292 stars 24 forks source link

Cancelling existing `SendRef` #70

Open aldanor opened 2 years ago

aldanor commented 2 years ago

Wonder if allowing to cancel existing SendRef would be possible? E.g. something along the lines of

impl<'a, ...> SendRef<'a, ...> {
    /// Drops this send ref *without* sending the data. The slot is returned to the pool.
    pub fn cancel(self) {
        ...
    }
}

Here's a particular sample use case:

Thanks!

hawkw commented 2 years ago

Hmm, something like this would definitely be nice to have, however, I'm not sure if it's really possible with the concurrent ring buffer algorithm. When a SendRef is created, the tail index is advanced: https://github.com/hawkw/thingbuf/blob/0cf1ea24b7f1aba7cf4f3932cd3f02f5846a5f54/src/lib.rs#L218-L221 so the next attempt to push/send a slot will claim the next index.

However, the head (readable) index is not advanced until the SendRef is dropped. I think the naive solution (just mem::forgetting the SendRef) would result in the queue being in an inconsistent state, where the head index is perpetually one slot behind where it "should" be.

I think if we wanted to implement a cancellation API like you're describing, we could, but the only way we could do it would be to essentially mark the slot as having been skipped --- sort of like the approach you proposed of sending an empty message, but with the benefit of not waking up the receiver when a slot is skipped. This would still "waste" the slot on this lap (it would be usable again when the ring buffer wraps around), but it would avoid spurious wakeups.

There might be a better solution. I'll keep thinking about it.

aldanor commented 2 years ago

Thanks for the prompt reply - yea, it sounds like the only obvious way for this to work would be implementing a way of marking slots as skipped (as if you kept a SendRef for an entire lap).

For the memory usage perspective, as long as you don't abuse the cancellation procedure and it's more of a rare occasion, it should be negligible in most cases. IIUC, it may introduce some extra branching on the tx side (to handle the 'skipped' status), but with mpsc you'd be typically trying to avoid branching and wakeups on the rx side as much as possible.

sgasse commented 8 months ago

Hi! We have a somewhat similar situation where we obtain a SendRef but have loop iterations in which we have nothing to send and thus do not want to send it out. You probably found a workaround (maybe the same) by now @aldanor but I still want to share our workaround here, which works if you are working in a loop:

We created an Option<SendRef<'a, T>> outside of the loop. In every iteration of the loop, we first check if there is already a SendRef in the option and either use that or try to get a new one. If at the end of the loop iteration it turns out that we do not want to send the SendRef, we hand it back to the Option<SendRef<'a, T>>. This avoids empty sends without introducing much complexity or runtime cost.

hawkw commented 7 months ago

PR #81 added the capability for senders to skip slots which are still in use by the receiver on the current lap. We could probably implement slot cancelling using similar code. We would need to use an additional bit of state on the slot to mark it as "hey, this slot doesn't actually contain any data, don't try to read from it" from the receiver's perspective, to the HAS_READER bit that's currently used to tell senders to skip a slot that's currently in use by the receiver.