harm
harm

Reputation: 10385

Idiomatic way of handling interrupts in Go using ZeroMQ

I'm learning both Go and ZeroMQ at the moment and in that spirit I'm trying to contribute Go examples for the Zguide. I'm struggling a bit with the interrupt example. I'm unsure what the idiomatic way of handling the problem would be.

The solution I currently have is the following: I create a channel which receives the SIGINT signal. When it does I write a bool on an other channel which is used in the main loop to break. The problem is, is that Recv is blocking, the loop never gets to check the loop condition. I circumvent the problem by passing the NOBLOCK constant to Recv. But I feel there is a better way as Recv should return a EINTR when interrupted (which it doesn't as far as I can tell). You readers are much better equipped to answer this question then I am, what do you think?

For your convenience the code I have so far:

package main

import (
    "os/signal"
    "os"
    "fmt"
    zmq "github.com/alecthomas/gozmq"
)

func listenForSignals(exit_channel chan bool) {
    signal_channel := make(chan os.Signal)
    signal.Notify(signal_channel)
    <- signal_channel
    fmt.Println("stopping")
    exit_channel <- true
}

func main() {
    exit := make(chan bool)
    exit_signal := false
    go listenForSignals(exit)

    context, _ := zmq.NewContext()
    defer context.Close()

    socket, _ := context.NewSocket(zmq.REP)
    defer socket.Close()
    socket.Bind("tcp://*:5555")

    for exit_signal == false {
      select {
      case exit_signal = <- exit:
        fmt.Println("W: interrupt received, killing server...")
      default:
        msgbytes, err := socket.Recv(zmq.NOBLOCK)
        fmt.Printf("%s.\n", string(msgbytes))
      }
    }

}

Edit simplified the code somewhat based on feedback

Upvotes: 2

Views: 1698

Answers (2)

Denys S&#233;guret
Denys S&#233;guret

Reputation: 382264

This looks a little too complex. In the main you can start a goroutine waiting for the interrupt signal :

go func() {
    sigchan := make(chan os.Signal, 10)
    signal.Notify(sigchan, os.Interrupt)
    <-sigchan
    log.Println("Application killed !")
    // do things like writing your last will or cursing the killer before you really die        
    os.Exit(2)
}()
// starts the things your application has to do

Upvotes: 0

Matt Balkam
Matt Balkam

Reputation: 198

If you are going to use a select statement to switch over channels, you should return the result of socket.Recv on a channel as well. This also lets you run socket.Recv on a goroutine so the blocking nature isn't an issue.

Realistically, you should probably also handle errors that you get as well. That you can do by adding another channel to the whole shebang.

func main() {
    ...

    data := make(chan []byte)
    errors := make(chan error)
    go function() {
      for {
        msgbytes, err := socket.Recv(0)
        if err != nil {
          errors <- err
        } else {
          data <- msgbytes
        }
      }
    }()

    for exit_signal == false {
      select {
      case exit_signal = <- exit:
        fmt.Println("W: interrupt received, killing server...")
      case err := <- errors:
        fmt.Println("Receive Error:", err.Error())
      case msgbytes := <- data:
        fmt.Printf("%s.\n", string(msgbytes))
      }
    }

}

Upvotes: 2

Related Questions