Golang Clean shutdown on Signal Interrupt

Issue

I am trying to find an efficient way to shutdown all my go routines, once i get my OS interrupt signals. Here i am polling events (say from some queue) and processing it in a goroutine. But when i receive OS interrupt, i want to make sure the jobs that are on fly are completed before they are terminated. I also need some additional thing to do only after all the goroutines are completed. The below code seems to work fine for me, but is there any better/efficient way to do this?

package main

import (
    "fmt"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time" // or "runtime"
)

func something(wg *sync.WaitGroup){
    defer wg.Done()
    fmt.Println("something is happening here...")
    time.Sleep(10 * time.Second)
    fmt.Println("job done...")
}

func main() {

    c := make(chan os.Signal)
    mutex := sync.Mutex{}
    stop := make(chan int, 1)
    signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
    wg := sync.WaitGroup{}
    count := 0
    go func() {
        <-c
        currentTime := time.Now()
        fmt.Println("Interrupt signal got at: ", currentTime.String())
        // do not let the code shutdown without running everything we needed to do
        mutex.Lock()
        stop <- 1
        fmt.Println("Done .. try shutting down")
        wg.Wait()

        // do cleanup
        time.Sleep(3*time.Second)

        fmt.Println("All cleanup completed .. shut down")
        currentTime = time.Now()
        fmt.Println("Kill at : ", currentTime.String())
        mutex.Unlock()
    }()

    // This for loop is for reading messages from queue like sqs, and it has to be infinite loop because there might be scenarios where there are no events for period of time.
    for {            
        // read off of queue
        select {
        case stop <- 1:
            fmt.Println("Not stopped yet")
            wg.Add(1)
            go something(&wg)
            <- stop
            count ++
        default:
            // try getting the lock before exiting (so that other cleanups are done)
            mutex.Lock()
            fmt.Println("Done! All jobs completed: Jobs count",count)
            return
        }
        fmt.Println("Processing job -", count)
        time.Sleep(1 * time.Second)
    }
}

Solution

I believe your solution is unnecessarily complicated. It might be correct, but this is much simpler:

func goroutine(wg *sync.WaitGroup,stop chan struct{}) {
  defer wg.Done()
   for {
     select {
       case <-stop:
          return
       default:
     }
    // do stuff
  }
}

func main() {
 stop:=make(chan struct{})

 // Setup signal handlers
 go func() {
        <-c
        // This will send the stop signal to all goroutines
        close(stop)
 }()
 // Start goroutines
 ...
 // wait for them to finish
wg.Wait()

Answered By – Burak Serdar

Answer Checked By – Terry (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.