Trying to learn golang network programming. Implemented simple server with unix domain sockets. The server has:
Then starts signalHandler(), three taskWorker(), acceptClientReqs() in goroutines. On receiving the signal (send the signal to this process with kill -SIGINT ), the signal handler sets global variable serverShutdown. My intention is - all the goroutines to read serverShutdown and exit cleanly. However, some times all the worker threads exiting and other times they don't. Do you see any bug in my code? Or am I missing some thing?
package main
import "encoding/json"
import "fmt"
import "net"
import "os"
import "os/signal"
import "syscall"
import "time"
var srvShutdown = false
type netServer struct {
listener *net.UnixListener // unix local sock
taskChan chan net.Conn // unbuf channel for client connections
sigChan chan os.Signal // buffered channel
srvDone chan bool
numOfWorkers int
}
// initialize signals.
func initSignals() {
// block all async signals to this server except SIGINT/SIGHUP
signal.Ignore()
}
// initializes server:
// 1) creates listener.
// 2) creates task channel - client connections accepted on this channel.
// 3) initializes few other things such as signal channel, num of workers etc.
func initServer() (*netServer, error) {
sockAddr, err := net.ResolveUnixAddr("unix", "/tmp/unix.socket")
if err != nil {
fmt.Println("initServer() failed on ResolveUnixAddr(): ", err)
return nil, err
}
srvlistener, err := net.ListenUnix("unix", sockAddr)
if err != nil {
fmt.Println("initServer() failed on ListenUnix(): ", err)
return nil, err
}
netSrv := &netServer{
listener: srvlistener,
taskChan: make(chan net.Conn),
sigChan: make(chan os.Signal, 1),
srvDone: make(chan bool),
numOfWorkers: 3,
}
initSignals()
return netSrv, nil
}
// Accept client connections. Each connection is directed onto
// a channel. Worker threads pickup and work on these connections.
func (netSrv *netServer) acceptClientReqs() {
fmt.Println("Listening on /tmp/unix.socket")
for srvShutdown != true {
netSrv.listener.SetDeadline(time.Now().Add(time.Second))
conn, err := netSrv.listener.Accept()
if err != nil {
fmt.Println("Accept failed : ", err)
continue
}
netSrv.taskChan <- conn
}
netSrv.srvDone <- true
fmt.Println("Exiting server")
}
// Pickup a client connection, work on it and close connection.
func (netSrv *netServer) taskWorker(workerId int) {
fmt.Println("taskWorker(): starting worker-", workerId)
// for conn := range netSrv.taskChan {
for srvShutdown != true {
conn := <-netSrv.taskChan
if conn == nil {
fmt.Println("taskWorker(): worker-", workerId, " continue")
continue
}
fmt.Println("taskWorker(): worker-", workerId, " processing client connection: ", conn)
dec := json.NewDecoder(conn)
var Cmd string
dec.Decode(&Cmd)
fmt.Println(" cmd: ", Cmd)
// reply to client
_, err := conn.Write([]byte("AUTH SUCCESS"))
if err != nil {
return
}
conn.Close()
fmt.Println("taskWorker(): worker-", workerId, " processed client connection: ", conn)
}
fmt.Println("taskWorker(): exiting worker-", workerId)
}
// Handle SIGINT and SIGHUP for now.
func (netSrv *netServer) signalHandler() {
signal.Notify(netSrv.sigChan, syscall.SIGINT, syscall.SIGHUP)
sig := <-netSrv.sigChan
fmt.Println("signalHandler() received signal", sig)
srvShutdown = true
}
func (netSrv *netServer) cleanUp() {
close(netSrv.taskChan)
netSrv.listener.Close()
close(netSrv.srvDone)
}
func main() {
srv, err := initServer()
if err != nil {
fmt.Println("initServer() failed - ", err)
return
}
go srv.signalHandler()
for i := 1; i <= srv.numOfWorkers; i++ {
go srv.taskWorker(i)
}
go srv.acceptClientReqs()
// wait server to complete
<-srv.srvDone
srv.cleanUp()
}