vendredi 19 mars 2021

Channels and Graceful shutdown deadlock

Run the below program and run CTRL + C, the handle routine gets blocked as it is trying to send to a channel but the receiver routine has shutdown. What is a better concurrency design to solve this?

Making process exit after handle will makes thing work. But in my use case the problem is process should be returned due to many other reasons like ctx or due to some issue in processing etc...

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
)

func process(ctx context.Context, pwg *sync.WaitGroup, c chan string) {
    defer func() {
        fmt.Println("process: wg done(), processor exited")
        pwg.Done()
    }()

    fmt.Println("process: processing (select)")
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("process: ctx done bye\n")
            return
        case msg := <-c:
            fmt.Printf("process: got msg: %v\n", msg)
        }
    }
}

func handle(ctx context.Context, pwg *sync.WaitGroup) {
    defer func() {
        fmt.Println("handle: wg done(), handler exited")
        pwg.Done()
    }()
    c := make(chan string, 3)
    wg := &sync.WaitGroup{}
    go process(ctx, wg, c)
    wg.Add(1)
    defer func() {
        fmt.Println("handle: wg wait() for processing to return")
        wg.Wait()
        fmt.Println("handle: wg wait() done. processing returned")
    }()

    for i := 0; ; i++ {
        time.Sleep(5 * time.Second)
        fmt.Printf("handle: sending to channel: %v\n", i)
        c <- fmt.Sprintf("%d", i)
        fmt.Printf("handle: sent to channel: %v\n", i)
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    wg := &sync.WaitGroup{}

    go handle(ctx, wg)
    wg.Add(1)

    sigterm := make(chan os.Signal, 1)
    signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)
    ready := 0
    for ready >= 0 {
        select {
        default:
        case <-sigterm:
            fmt.Printf("SIGTERM signal received\n")
            ready = -1
        }
    }
    fmt.Println("cancelling context")
    cancel()
    fmt.Println("waiting for all routines to exit")
    wg.Wait()
}

Output

$ go run chan-shared.go
process: processing (select)
handle: sending to channel: 0
handle: sent to channel: 0
process: got msg: 0
handle: sending to channel: 1
handle: sent to channel: 1
process: got msg: 1
handle: sending to channel: 2
handle: sent to channel: 2
process: got msg: 2
handle: sending to channel: 3
handle: sent to channel: 3
process: got msg: 3
handle: sending to channel: 4
handle: sent to channel: 4
process: got msg: 4

^C
SIGTERM signal received

cancelling context
waiting for all routines to exit
process: ctx done bye
process: wg done(), processor exited

handle: sending to channel: 5
handle: sent to channel: 5
handle: sending to channel: 6
handle: sent to channel: 6
handle: sending to channel: 7
handle: sent to channel: 7
handle: sending to channel: 8

Killed: 9

Aucun commentaire:

Enregistrer un commentaire