使用兩個context實現CLOSE包的超時等待

winlin發表於2017-05-18

在UDP中,一般傳送者傳送包後,如果一定的時間對方沒有收到,就需要重傳。例如UDP實現握手的過程,如果握手的包,比如RTMFP協議的IHELLO,傳送給對方後,如果一定1秒沒有收到,就應該重發一次,然後等3秒、6秒、9秒,如果最後沒有收到就是超時了。

最後一個Close包,傳送者不能等待這麼長的時間,所以需要設定一個較短的時間做超時退出。一般收發都是一個context,在最後這個Close包時,收到ctx.Done也不能立刻退出,因為還需要稍微等待,譬如600毫秒如果沒有收到響應才能退出。

一個可能的實現是這樣:

in := make(chan []byte)

func Close(ctx context.Context) (err error) {
    timeous := ... // 1s,3s,6s,9s...
    for _, to := range timeouts {
        // 傳送給對方WriteToUDP("CLOSE", peer)
        // 另外一個goroutine讀取UDP包到in

        select {
        case <- time.After(to):
        case <- in:
            fmt.Println("Close ok")
            return
        case <- ctx.Done():
            fmt.Println("Program quit")
            return
        }
    }
    return
}

但是這個問題在於,在程式退出時,一般都會cancel ctx然後呼叫Close方法,這個地方就不會等待任何的超時,就列印"Program quit"然後返回了。解決方案是用另外一個context。但是如何處理之前的ctx的done呢?可以再起一個goroutine做同步:

in := make(chan []byte)

func Close(ctx context.Context) (err error) {
    ctxRead,cancelRead := context.WithCancel(context.Background())
    go func(){ // sync ctx with ctxRead
        select {
            case <-ctxRead.Done():
            case <-ctx.Done():
                select {
                    case <-ctxRead.Done():
                    case <-time.After(600*time.Milliseconds):
                        cancelRead()
                }
        }
    }()

    ctx = ctxRead // 下面直接用ctxRead。
    timeous := ... // 1s,3s,6s,9s...
    for _, to := range timeouts {
        // 傳送給對方WriteToUDP("CLOSE", peer)
        // 另外一個goroutine讀取UDP包到in

        select {
        case <- time.After(to):
        case <- in:
            fmt.Println("Close ok")
            return
        case <- ctx.Done():
            fmt.Println("Program quit")
            return
        }
    }
    return
}

這樣在主要的邏輯中,還是隻需要處理ctx,但是這個ctx已經是新的context了。不過在實際的過程中,這個sync的goroutine需要確定起來後,才能繼續,否則會造成執行順序不確定:

sc := make(chan bool, 1)
go func(){ // sync ctx with ctxRead
    sc <- true
    select {
    ......
}
<- sc

使用context,來控制多個goroutine的執行和取消,是非常好用的,關鍵可以完全關注業務的邏輯,而不會引入因為ctx取消或者超時機制而造成的特殊邏輯。

相關文章