smol-rs / async-broadcast

Async broadcast channels

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add a `Sender::poll_ready` method

yshui opened this issue · comments

It is difficult right now to use the broadcast channel in a poll_? style function. Add a poll_ready method, which waits for space in the channel, would make it easier.

The receiver type implements Stream, which can be polled using the poll_next function.

That's fine, but how do I broadcast from a poll_? function?

That's fine, but how do I broadcast from a poll_? function?

The Send type returned by Sender::broadcast implemented Future so you can poll it.

hmm.. looking at the docs of Sender::broadcast now, I see it's a bit confusing:

If the channel is full, this method waits until there is space for a message unless:

It's not this method that waits (that would be blocking) but rather the Future impl it returns.

The Send type returned by Sender::broadcast implemented Future so you can poll it.

But I'd have to store this Send somewhere. (IIUC, if I drop this Send my task won't get woken up). And since Send borrows from the channel, this often ended up needing a self-referential type.

Is it really not possible to provide a poll_ style method?

The Send type returned by Sender::broadcast implemented Future so you can poll it.

But I'd have to store this Send somewhere. (IIUC, if I drop this Send my task won't get woken up). And since Send borrows from the channel, this often ended up needing a self-referential type.

Right, that's true.

Is it really not possible to provide a poll_ style method?

How about we have broadcast return a Send<'static> instead? (we could also remove the lifetime param of Send but that'd be a breaking change). I've the patch ready locally already.

How about we have broadcast return a Send<'static> instead? (we could also remove the lifetime param of Send but that'd be a breaking change). I've the patch ready locally already.

On second thought, you can always make use of async move to create a Future impl that keeps the Sender while calling the broadcast for you:

    struct SenderFuture<F>(F);

    impl<F> Future for SenderFuture<F>
    where
        F: Future<Output = ()> + Unpin,
    {
        type Output = ();

        fn poll(
            mut self: std::pin::Pin<&mut Self>,
            cx: &mut std::task::Context<'_>,
        ) -> std::task::Poll<Self::Output> {
            Future::poll(std::pin::Pin::new(&mut self.as_mut().0), cx)
        }
    }

    let (s, mut r) = broadcast(1);

    // Using Box here but you can pin on the stack too.
    let f = SenderFuture(Box::pin(async move {
        s.broadcast(()).await.unwrap();
    }));

If this doesn't work for you and you'd want #40 to land, please provide more detail on your specific use case.

@zeenix ah, that indeed works, and it is basically the workaround that I am currently using. except there is more complications. for example, ideally you would want the sender back once you completed the broadcast; and you would want it back if you cancelled the broadcast too. so I need to put another channel into the async move so I can tell it to cancel the broadcast and return the sender.

also, doing it this way adds an extra allocation every time I need to broadcast, so there is some unsafe code so I can reuse the same Box. overall it's not very nice or clean.

for example, ideally you would want the sender back once you completed the broadcast;

That's not at all a problem, your async block can return it after completing.

and you would want it back if you cancelled the broadcast too.

back to where? Why can't the caller/canceller keep a clone of Sender instead if it's going to need it anyway?

also, doing it this way adds an extra allocation every time I need to broadcast, so there is some unsafe code so I can reuse the same Box. overall it's not very nice or clean.

I'm really not sure what exactly are you doing but adding Sender::poll_ready will not be easy (if even possible) if we want to do it right because it will be racy (since we use interior mutability).

If you think it's impractical to implement poll_ready, then I can close this issue. It was meant to be a suggestion after all.

Since I opened this issue, I managed to build my code around the lack of a poll_ready (although it did make my code convoluted), so I can live without it.

If you think it's impractical to implement poll_ready, then I can close this issue.

If you won't contest my reasons behind that conclusion, I guess we have to accept it as a fact. :)

It was meant to be a suggestion after all.

Sure, no hard feelings from my side at least. Sorry if my direct tone sounded aggressive. I was trying to understand your exact use case.

Since I opened this issue, I managed to build my code around the lack of a poll_ready (although it did make my code convoluted), so I can live without it.

👍