Golang : Daemonizing a simple web server process example
Problem:
You use the previous tutorial on how to put your Golang program into background but somehow found that using the -d=true
parameter is troublesome.
Not only that, you want to make your Golang program similar to launching a daemon process on Unix/Linux OS. Such as having different behavior with start
, install
, reload
or stop
parameters.
You also want to store the process ID into a file in /tmp
folder.
How to do that?
Solution:
We will use os/exec.Command()
function to launch a child process of the parent program but with a different parameter. Typical Unix/Linux daemon process supports start
and stop
parameters. In our program start
block, it will launch a daemon process but with main
parameter instead. It can be kinda mysterious operation for some readers initially.
To get a better understanding, run the code below.
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"os/signal"
"syscall"
)
var PIDFile = "/tmp/daemonize.pid"
func savePID(pid int) {
file, err := os.Create(PIDFile)
if err != nil {
log.Printf("Unable to create pid file : %v\n", err)
os.Exit(1)
}
defer file.Close()
_, err = file.WriteString(strconv.Itoa(pid))
if err != nil {
log.Printf("Unable to create pid file : %v\n", err)
os.Exit(1)
}
file.Sync() // flush to disk
}
func SayHelloWorld(w http.ResponseWriter, r *http.Request) {
html := "Hello World"
w.Write([]byte(html))
}
func main() {
if len(os.Args) != 2 {
fmt.Printf("Usage : %s [start|stop] \n ", os.Args[0]) // return the program name back to %s
os.Exit(0) // graceful exit
}
if strings.ToLower(os.Args[1]) == "main" {
// Make arrangement to remove PID file upon receiving the SIGTERM from kill command
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, os.Kill, syscall.SIGTERM)
go func() {
signalType := <-ch
signal.Stop(ch)
fmt.Println("Exit command received. Exiting...")
// this is a good place to flush everything to disk
// before terminating.
fmt.Println("Received signal type : ", signalType)
// remove PID file
os.Remove(PIDFile)
os.Exit(0)
}()
mux := http.NewServeMux()
mux.HandleFunc("/", SayHelloWorld)
log.Fatalln(http.ListenAndServe(":8080", mux))
}
if strings.ToLower(os.Args[1]) == "start" {
// check if daemon already running.
if _, err := os.Stat(PIDFile); err == nil {
fmt.Println("Already running or /tmp/daemonize.pid file exist.")
os.Exit(1)
}
cmd := exec.Command(os.Args[0], "main")
cmd.Start()
fmt.Println("Daemon process ID is : ", cmd.Process.Pid)
savePID(cmd.Process.Pid)
os.Exit(0)
}
// upon receiving the stop command
// read the Process ID stored in PIDfile
// kill the process using the Process ID
// and exit. If Process ID does not exist, prompt error and quit
if strings.ToLower(os.Args[1]) == "stop" {
if _, err := os.Stat(PIDFile); err == nil {
data, err := ioutil.ReadFile(PIDFile)
if err != nil {
fmt.Println("Not running")
os.Exit(1)
}
ProcessID, err := strconv.Atoi(string(data))
if err != nil {
fmt.Println("Unable to read and parse process id found in ", PIDFile)
os.Exit(1)
}
process, err := os.FindProcess(ProcessID)
if err != nil {
fmt.Printf("Unable to find process ID [%v] with error %v \n", ProcessID, err)
os.Exit(1)
}
// remove PID file
os.Remove(PIDFile)
fmt.Printf("Killing process ID [%v] now.\n", ProcessID)
// kill process and exit immediately
err = process.Kill()
if err != nil {
fmt.Printf("Unable to kill process ID [%v] with error %v \n", ProcessID, err)
os.Exit(1)
} else {
fmt.Printf("Killed process ID [%v]\n", ProcessID)
os.Exit(0)
}
} else {
fmt.Println("Not running.")
os.Exit(1)
}
} else {
fmt.Printf("Unknown command : %v\n", os.Args[1])
fmt.Printf("Usage : %s [start|stop]\n", os.Args[0]) // return the program name back to %s
os.Exit(1)
}
}
First, use go build
instead of go run
go build daemonize.go
./daemonize start
Sample output
Daemon process ID is : 2050
at this stage, the program exited and running in the background.
ps -ef | grep daemon
will show the process running at the background.
now, point your browser to localhost:8080
and you will see the expected output of "Hello World"
finally, execute
./daemonize stop
Killing process ID [2050] now.
Killed process ID [2050]
and this will stop the background process.
In case the process is killed by kill
command instead of ./daemonize stop
. It will still be able to remove the /tmp/daemonize.pid
file
NOTE:
Golang does not have support for forking and even though I've managed to daemonize
a Golang program via the traditional
method... it is kinda useless because nothing else can be executed after os.Exit(0)
. Why? See the explanation in https://habrahabr.ru/post/187668/ (in Russian language)
Below is the code example for daemonizing Golang program in traditional
way that you can try out just for fun and perhaps learn something. Happy coding!
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"syscall"
)
var PIDFile = "/tmp/daemonize.pid"
func savePID(pid string) {
file, err := os.Create(PIDFile)
if err != nil {
log.Printf("Unable to create pid file : %v\n", err)
os.Exit(1)
}
defer file.Close()
_, err = file.WriteString(pid)
if err != nil {
log.Printf("Unable to create pid file : %v\n", err)
os.Exit(1)
}
file.Sync() // flush to disk
}
func daemonize(stdin, stdout, stderr string) {
var ret, ret2 uintptr
var syserr syscall.Errno
darwin := runtime.GOOS == "darwin"
// already a daemon
if syscall.Getppid() == 1 {
fmt.Println("Already a daemon.")
os.Exit(1)
}
// detaches from the parent process
// Golang does not have os.Fork()...
// so we will use syscall.SYS_FORK
// ret is the child process ID
ret, ret2, syserr = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if syserr != 0 {
fmt.Println("syscall.SYS_FORK error number: %v", syserr)
os.Exit(1)
}
// failure
if ret2 < 0 {
fmt.Println("syscall.SYS_FORK failed")
os.Exit(-1)
}
// handle exception for darwin
if darwin && ret2 == 1 {
ret = 0
}
// if we got a good PID, then we save the child process ID
// to /tmp/daemonize.pid
// and exit the parent process.
if ret > 0 {
log.Println("Detached process from parent. Child process ID is : ", ret)
// convert uintptr(pointer) value to string
childPID := fmt.Sprint(ret)
savePID(childPID)
os.Chdir("/")
// replace file descriptors for stdin, stdout and stderr
// default value is /dev/null
infile, err := os.OpenFile(stdin, os.O_RDWR, 0)
if err == nil {
infileDescriptor := infile.Fd()
syscall.Dup2(int(infileDescriptor), int(os.Stdin.Fd()))
}
// remove the output files
os.Remove(stdout)
os.Remove(stderr)
// with correct permissions
outfile, err := os.OpenFile(stdout, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if err == nil {
outfileDescriptor := outfile.Fd()
syscall.Dup2(int(outfileDescriptor), int(os.Stdout.Fd()))
}
errfile, err := os.OpenFile(stderr, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
if err == nil {
errfileDescriptor := errfile.Fd()
syscall.Dup2(int(errfileDescriptor), int(os.Stderr.Fd()))
}
// debug
//syscall.Write(int(outfile.Fd()), []byte("test output to outfile.\n"))
//syscall.Write(int(os.Stdout.Fd()), []byte("test output to os.Stdout.\n"))
os.Exit(0)
// debug
// will not work after os.Exit(0)
//syscall.Write(int(os.Stdout.Fd()), []byte("test output to os.Stdout after os.Exit.\n"))
}
// Change the file mode mask
_ = syscall.Umask(0)
// create a new SID for the child process(relinquish the session leadership)
// i.e we do not want the child process to die after the parent process is killed
// see http://unix.stackexchange.com/questions/240646/why-we-use-setsid-while-daemonizing-a-process
// just for fun, try commenting out the Setsid() lines and run this program. The daemonized child will die
// together with the parent process.
s_ret, s_errno := syscall.Setsid()
if s_errno.Error() != strconv.Itoa(0) {
log.Printf("Error: syscall.Setsid errno: %d", s_errno)
// we already exit the program....
}
if s_ret < 0 {
log.Printf("Error: Unable to set new SID")
// we already exit the program....
}
}
func main() {
if len(os.Args) != 2 {
fmt.Printf("Usage : %s [start|stop] \n ", os.Args[0]) // return the program name back to %s
os.Exit(0) // graceful exit
}
if strings.ToLower(os.Args[1]) == "start" {
// check if daemon already running.
if _, err := os.Stat(PIDFile); err == nil {
fmt.Println("Already running or /tmp/daemonize.pid file exist.")
os.Exit(1)
}
daemonize("/dev/null", "/tmp/daemonize.log", "/tmp/daemonize.log")
}
// upon receiving the stop command
// read the Process ID stored in PIDfile
// kill the process using the Process ID
// and exit. If Process ID does not exist, prompt error and quit
if strings.ToLower(os.Args[1]) == "stop" {
if _, err := os.Stat(PIDFile); err == nil {
data, err := ioutil.ReadFile(PIDFile)
if err != nil {
fmt.Println("Not running")
os.Exit(1)
}
ProcessID, err := strconv.Atoi(string(data))
if err != nil {
fmt.Println("Unable to read and parse process id found in ", PIDFile)
os.Exit(1)
}
process, err := os.FindProcess(ProcessID)
if err != nil {
fmt.Printf("Unable to find process ID [%v] with error %v \n", ProcessID, err)
os.Exit(1)
}
// remove PID file
os.Remove(PIDFile)
fmt.Printf("Killing process ID [%v] now.\n", ProcessID)
// kill process and exist immediately
err = process.Kill()
if err != nil {
fmt.Printf("Unable to kill process ID [%v] with error %v \n", ProcessID, err)
os.Exit(1)
} else {
fmt.Printf("Killed process ID [%v]\n", ProcessID)
os.Exit(0)
}
} else {
fmt.Println("Not running.")
os.Exit(1)
}
} else {
fmt.Printf("Unknown command : %v\n", os.Args[1])
fmt.Printf("Usage : %s [start|stop]\n", os.Args[0]) // return the program name back to %s
os.Exit(1)
}
}
References:
https://habrahabr.ru/post/187668/
https://gist.github.com/wofeiwo/3634357
https://www.socketloop.com/tutorials/golang-intercept-and-process-unix-signals-example
https://www.socketloop.com/tutorials/golang-get-command-line-arguments
https://www.socketloop.com/tutorials/golang-check-if-a-file-exist-or-not
See also : Golang : Terminate-stay-resident or daemonize your program?
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+14.3k Golang : Rename directory
+18.3k Golang : Find IP address from string
+5.2k Golang : What is StructTag and how to get StructTag's value?
+9k Golang : Apply Histogram Equalization to color images
+5.1k JavaScript/JQuery : Redirect page examples
+13.5k Golang : How to determine if a year is leap year?
+5.1k Javascript : Change page title to get viewer attention
+24.9k Golang : Storing cookies in http.CookieJar example
+7.5k Golang : Error reading timestamp with GORM or SQL driver
+9.2k Golang : Accessing content anonymously with Tor
+12.3k Golang : Exit, terminating or aborting a program
+6.5k Golang : Find the longest line of text example