Handling timeouts and listening to channels

Issue

I have code that looks like this, where I’m listening to a channel up until a timeout interval. Let’s say this goroutine 1

select {
    case <-time.After(TimeoutInterval):
        mu.Lock()
        defer mu.Unlock()
        delete(msgChMap, index)
        return ""
    case msg := <-msgCh:
        return msg
}

Elsewhere, I have a goroutine 2 that runs something like this where it grabs the appropriate msgCh from a Map, deletes the entry in the map and then sends a message through the channel.

mu.Lock()
msgCh, ok := msgChMap[index]
delete(msgChMap, index)
mu.Unlock()
if ok {
    msgCh <- "yay"
}

It seems like it is possible for me to grab the message channel msgCh from the Map, try to send a message but because TimeoutInterval has already passed, there will be nothing listening to the channel, and my code will get stuck waiting for a listener. If I put the lock after sending yay to the msgCh, it seems possible that I could deadlock as 2 will be waiting for a listener to the channel and is not releasing the lock, but 1 is no longer listening but requires the lock.

What is a general pattern to avoid getting stuck waiting for a listener? Perhaps go is smart enough to not get stuck here.

Solution

You can prevent getting stuck when waiting for a listener by using select for the sender.

By using select you can use more case for sender in this situation

mu.Lock()
msgCh, ok := msgChMap[index]
delete(msgChMap, index)
mu.Unlock()
if ok {
    select {
    // listener is available
    case msgCh <- "yay":
        fmt.Println("sent")

    // if not avalable (execute immediately)
    default:
        fmt.Println("no available listener")
        // ...just ignore or do something else
    }
}

Or waiting for a short time

mu.Lock()
msgCh, ok := msgChMap[index]
delete(msgChMap, index)
mu.Unlock()
if ok {
    select {
    // listener is available
    case msgCh <- "yay":
        fmt.Println("sent")

    // if not available, waiting for listener
    case <-time.After(30 * time.Second):
        fmt.Println("after 30 seconds, still no available listener")
        // ...just ignore or do something else
    }
}

Answered By – Son Huynh

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.