What is a hot restart:
The new and old programs (processes) are seamlessly replaced, while maintaining the service to the client. Let the client side not feel your service hangs off. such as reloading the configuration file, need to reboot, replace the old program need to reboot, you need to use a hot restart. However, the use of golang scenes, in fact, directly at the HTTP proxy level to make cut traffic more convenient. principle
interacts with processes by sending signal (signals). The signal can be defined by itself, specifying the signal of the interception system, changing the system default behavior to customize the operation.
If you receive a restart signal, fork the new version of the process to hand the socket handle to the new process, the new process starts accepting the new connection request the old version server to stop accepting the connection, to maintain the existing connection, stop the old version of the server immediately after processing, turn off the monitoring (OS. Exit (1)). a simplified version of the restart process
My code here does not deal with the original connection, easy to understand the key points, you can refer to other articles with sync. Waitgroup.wait () to deal with this problem. generate a server to listen on 8888 ports, then intercept system signals, get listeners
Get the listener, here with a function to get, because the parent process and the child process gets to the listener is not the same, the parent process gets the original TCP socket file, and the child process gets only the copy of the socket file. Well, if you get a TCP socket file twice, which is equivalent to listening to the same port 2 times, this error will be reported listen tcp:8888:bind:address already in use. Therefore, the second acquisition of a copy is equivalent to inheriting the parent listener. This is the key point for the thermal restart.
Func Main () {
Server = http. server{
Addr: ": 8888",
Handler: &myhandle{},
Readtimeout:6 * time. Second,
}
go handlesignals ()
log. Printf ("Actual pid is%d\n", Syscall. Getpid ())
var err2 error
Listener, ERR2 = Getlistener (server. ADDR)
if err2! = nil {
log. Println (ERR2)
}
log. Printf ("IsChild:%v, Listener:%v\n", IsChild, listener)
ERR: = Server. Serve (listener)
if err! = Nil {
log. PRINTLN (Err)
}
}
getting the socket listener and inheriting the socket listener
IsChild is the flag of the child process, if it is a child process f: = OS. NewFile (3, ""), why is 3, instead of 1 0 or other numbers. This is because the parent process has given a FD to the child process and the child process 0,1,2 is reserved for standard input, output, and error, so the parent process gives the first FD in the sub-process sequence is starting from 3, if the fork when the CMD. Extrafiles gave two file handle, then the child process can also start with 4, it depends on how many children you have to increase the number of processes from the line. Because I have a sub-process here, so I wrote 3 dead. L, err = net. Filelistener (f) This step simply wraps the FD descriptor into the tcplistener structure.
Func Getlistener (Laddr string) (l net. Listener, err Error) {
If ischild {
runningserverreg.rlock ()
defer runningserverreg.runlock ()
f: = OS. NewFile (3, "")
l, err = net. Filelistener (f)
if err! = Nil {
log. Printf ("net. Filelistener error: ", err)
return
}
log. Printf ("LADDR:%v, listener:%v \ n", Laddr, L)
Syscall. Kill (Syscall. Getppid (), Syscall. SIGTSTP)//Kill parent Process
} else {
L, err = net. Listen ("TCP", LADDR)
if err! = Nil {
log. Printf ("net. Listen error:%v ", err)
return}}
return
}
Signal Processing
Signal with a signal bag. Notify filters out the signals that they want to define and releases them. Sighup is used to fork the child process, SIGTSTP to kill the parent process. SIGINT is used for its own ctr+c, convenient to interrupt the test.
Func handlesignals () {
var sig os. Signal
Signal. Notify (
Sigchan,
hookablesignals ...,
)
pid: = Syscall. Getpid () for
{
sig = <-sigchan
log. PRINTLN (PID, "Received sig.", SIG)
switch sig {case
syscall. SIGHUP:
log. PRINTLN (PID, "Received SIGHUP. Forking. ")
ERR: = Fork ()
if err! = Nil {
log. Println ("Fork err:", err)
} case
Syscall. SIGTSTP:
log. PRINTLN (PID, "Received sigtstp.")
Shutdown ()
default:
log. Printf ("Received%v \ n", Sig)}}
Fork Sub-process
Cmd. Extrafiles = []*os. FILE{FL} is the key to implementing the underlying socket service
Func fork () (err error) {
runningserverreg.lock ()
defer runningserverreg.unlock ()
if runningserversforked {
return errors. New ("another process already forked. Ignoring this one. ")
}
runningserversforked = True
log. Println ("restart:forked Start ...")
TL: = Listener. ( *net. TcpListener)
fl, _: = TL. File ()
path: = OS. Args[0]
cmd: = Exec.command (path, []string{"-continue"})
cmd. Stdout = OS. Stdout
cmd. Stderr = OS. Stderr
cmd. Extrafiles = []*os. FILE{FL}//Inherited listener file
err = cmd. Start ()//fork, not immediately execute
if err! = Nil {
log. Printf ("Restart:failed to launch, error:%v", err)
}
return
}
There is also a way to fork, as in the essence of the above:
Execspec: = &syscall. procattr{
ENV: os. Environ (),
Files: []uintptr{os. STDIN.FD (), OS. STDOUT.FD (), OS. STDERR.FD (), lFd},
}
pid, err: = Syscall. Forkexec (OS. ARGS[0], OS. Args, Execspec)
Close Parent Process
In fact, a first check there is no active connection, the service must be completed before switching off, with sync. Waitgroup.wait () to block is a good way, I do not write these in order to demonstrate the fork section.
Func shutdown () {
log. Printf ("Shutdown Listener:%v\n", Listener)
err: = Listener. Close ()
if err! = Nil {
log. Println (Syscall. Getpid (), "Listener.close () Error:", err)
} else {
log. Println (Syscall. Getpid (), server. ADDR, "Listener closed.")
}
Os. Exit (1)
}
The magical magic of flag package
Flag package can automatically parse various command line parameters, more simple than the analysis of the string itself, must flag. Parse () to begin parsing.
Func init () {
flag. Boolvar (&ischild, "continue", false, "Listen on open fd (after forking)")
flag. Parse ()
}
source code has been uploaded
Https://github.com/jeffdeng/gracefullDemo
If you want to open the box, you can use this Https://github.com/tim1020/godaemon or this https://github.com/fvbock/endless, of course, understand the principle is the most important. Reference:
http://my.oschina.net/tim8670/blog/643966
http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/