ihsan
ihsan

Reputation: 621

Go signal handling

I'm quite new to Go and in a quite a rut trying to figure out how to handle events that need to return back to the main loop.

In C, I can just return the function back but it seems I can't do that here, at least with that method.

I can handle signals that need to be processed within sigHandler() i.e. SIGINT or SIGTERM but I need to return calls back to main when handling SIGHUP or SIGUSR. In my code below, the process just stuck in wait mode once I sent the hangup signal.

I appreciate if someone can help me to guide on how I can properly design the signal handling to handle calls that need to return to main code which is in the first goroutine.

Edit

I'm now handling channel messages in the main goroutine within select{} but the code below quits when I'm sending the HUP signal. The objective here is to reload the config and continue execution as normal.

In the main line code, I have this:

go sigHandler()
cs := make(chan bool, 1)
go sigHandler(cs)

// setup the http server
err := setupServer(addr, port)
if err != nil {
    fatal("Error setting up listening sockets")
    os.Exit(1)
}

    select {
    case quit := <-cs:
        if quit {
            logEvent(loginfo, sys, "Terminating..")
            closeLog()
            os.Exit(0)
        } else {
            logEvent(loginfo, sys, "Reloading configuration..")
            }
    }

The function sigHandler()

func sigHandler(cs chan bool) {
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

    signal := <-c
    logEvent(lognotice, sys, "Signal received: "+signal.String())

    switch signal {
    case syscall.SIGINT, syscall.SIGTERM:
        cs <- true
    case syscall.SIGHUP:
            cs <- false
    }
}

Upvotes: 2

Views: 5610

Answers (2)

creack
creack

Reputation: 121492

You could do something like this:

package main

import (
    "os"
    "os/signal"
    "syscall"
)

// We make sigHandler receive a channel on which we will report the value of var quit
func sigHandler(q chan bool) {
    var quit bool

    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

    // foreach signal received
    for signal := range c {
        // logEvent(lognotice, sys, "Signal received: "+signal.String())

        switch signal {
        case syscall.SIGINT, syscall.SIGTERM:
            quit = true
        case syscall.SIGHUP:
            quit = false
        }

        if quit {
            quit = false
            // closeDb()
            // logEvent(loginfo, sys, "Terminating..")
            // closeLog()
            os.Exit(0)
        }
        // report the value of quit via the channel
        q <- quit
    }
}

func main() {
    // init two channels, one for the signals, one for the main loop
    sig := make(chan bool)
    loop := make(chan error)

    // start the signal monitoring routine
    go sigHandler(sig)

    // while vat quit is false, we keep going
    for quit := false; !quit; {
        // we start the main loop code in a goroutine
        go func() {
            // Main loop code here
            // we can report the error via the chan (here, nil)
            loop <- nil
        }()

        // We block until either a signal is received or the main code finished
        select {
        // if signal, we affect quit and continue with the loop
        case quit = <-sig:
        // if no signal, we simply continue with the loop
        case <-loop:
        }
    }
}

However, note that the signal will cause the main loop to continue, but it will not stop the execution on the first goroutine.

Upvotes: 2

rog
rog

Reputation: 6670

Here's one way of structuring things to do what you want, separating concerns so that the signal-handling code and the main code are separate and easily tested independently.

How you implement Quit and ReloadConfig is entirely up to your program - ReloadConfig may send a "please reload" value on a channel to a running goroutine; it may lock a mutex and change some shared configuration data; or some other possibility.

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    obj := &myObject{}
    go handleSignals(obj)
    select {}
}

type myObject struct {
}

func (obj *myObject) Quit() {
    log.Printf("quitting")
    os.Exit(0)
}

func (obj *myObject) ReloadConfig() {
    log.Printf("reloading configuration")
}

type MainObject interface {
    ReloadConfig()
    Quit()
}

func handleSignals(main MainObject) {
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
    for sig := range c {
        switch sig {
        case syscall.SIGINT, syscall.SIGTERM:
            main.Quit()
            return
        case syscall.SIGHUP:
            main.ReloadConfig()
        }
    }
}

Upvotes: 2

Related Questions