How to tell if a channel's sender still has a valid receiver?

I have a worker that has to do a lot of work to create a value. Once the value is created it sends that value using a channel.

The problem is that the work takes such a long time that sometimes the receiver on the channel will decide that it doesn't need the value and will hang up. When that happens the worker is wasting CPU because it keeps working and doesn't discover that the channel is closed until it tries tries to send the fully computed value.

I would like some way for the worker to periodically check to see if the channel is still valid. If it's not, then the worker can just quit without sending any value. But as far as I can see the only way to see if a channel is valid, is by sending. Is there some other way? Or a standard design to work around the problem?

Best that I can think of is that I send an enum through the channel with two values:

  1. AreYouStillAlive
  2. FinalValue(value)

Then my worker could periodically send the "AreYouStillAlive" value to see if it should continue work. And send "FinalValue" when done... but this is pretty messy because it means the receiver needs to filter and ignore all the AreYouStillAlive values. Must be better way?

1 Like

One way to solve this is to create a separate channel just for the purpose of checking if the receiver is alive.

The receiver will hold the send part of the channel, and, when it finishes, it'll drop the sender, causing the other side of the channel to close.

The other half can then try to receive with zero timeout and check if the error is "channel is closed" or "no message yet".

An neat bit here is that you never actually send anything through the channel, you only care about its drop impl.

I use a similar pattern in rls

2 Likes

Thanks, I'll give that approach a try.

Another option is to send a Arc<AtomicBool> that the receiver polls to see if the sender is not interested in the result anymore.

1 Like

Yep, atomic flag would work as well and will be a simpler solution. The two not-obvious benefits of the channel solution are that the "flag" is automatically set on drop, and that you can select! on this event.

In that pattern they use recv with the arguably awkward select macro:

fn is_closed(chan: &Receiver<Never>) -> bool {
    select! {
        recv(chan, msg) => match msg {
            None => true,
            Some(never) => match never {}
        }
        default => false,
    }
}

rather than just using try_recv, for example:

fn is_closed(chan: &Receiver<Never>) -> bool {
    match chan.try_recv() {
        Ok(never) => match never {},
        Err(TryRecvError::Disconnected) => true,
        Err(TryRecvError::Empty) => false,
    }
}

Especially since try_recv seems simpler than recv with all its blocking code. Is there a reason I'm missing to use recv over try_recv for this case?