mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-10 04:48:50 +03:00
57f1d3c205
pass all tests respond to maintainer comments reinstate assignment of t correct typo correct typo pass linter some more
102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
// +build !windows
|
|
|
|
package caddy
|
|
|
|
import (
|
|
"encoding/gob"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
func init() {
|
|
gob.Register(CaddyfileInput{})
|
|
}
|
|
|
|
// Restart restarts the entire application; gracefully with zero
|
|
// downtime if on a POSIX-compatible system, or forcefully if on
|
|
// Windows but with imperceptibly-short downtime.
|
|
//
|
|
// The restarted application will use newCaddyfile as its input
|
|
// configuration. If newCaddyfile is nil, the current (existing)
|
|
// Caddyfile configuration will be used.
|
|
//
|
|
// Note: The process must exist in the same place on the disk in
|
|
// order for this to work. Thus, multiple graceful restarts don't
|
|
// work if executing with `go run`, since the binary is cleaned up
|
|
// when `go run` sees the initial parent process exit.
|
|
func Restart(newCaddyfile Input) error {
|
|
if newCaddyfile == nil {
|
|
caddyfileMu.Lock()
|
|
newCaddyfile = caddyfile
|
|
caddyfileMu.Unlock()
|
|
}
|
|
|
|
if len(os.Args) == 0 { // this should never happen...
|
|
os.Args = []string{""}
|
|
}
|
|
|
|
// Tell the child that it's a restart
|
|
os.Setenv("CADDY_RESTART", "true")
|
|
|
|
// Prepare our payload to the child process
|
|
cdyfileGob := caddyfileGob{
|
|
ListenerFds: make(map[string]uintptr),
|
|
Caddyfile: newCaddyfile,
|
|
}
|
|
|
|
// Prepare a pipe to the fork's stdin so it can get the Caddyfile
|
|
rpipe, wpipe, err := os.Pipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Prepare a pipe that the child process will use to communicate
|
|
// its success or failure with us, the parent
|
|
sigrpipe, sigwpipe, err := os.Pipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Pass along current environment and file descriptors to child.
|
|
// Ordering here is very important: stdin, stdout, stderr, sigpipe,
|
|
// and then the listener file descriptors (in order).
|
|
fds := []uintptr{rpipe.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), sigwpipe.Fd()}
|
|
|
|
// Now add file descriptors of the sockets
|
|
serversMu.Lock()
|
|
for i, s := range servers {
|
|
fds = append(fds, s.ListenerFd())
|
|
cdyfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
|
|
}
|
|
serversMu.Unlock()
|
|
|
|
// Fork the process with the current environment and file descriptors
|
|
execSpec := &syscall.ProcAttr{
|
|
Env: os.Environ(),
|
|
Files: fds,
|
|
}
|
|
_, err = syscall.ForkExec(os.Args[0], os.Args, execSpec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Feed it the Caddyfile
|
|
err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
wpipe.Close()
|
|
|
|
// Wait for child process to signal success or fail
|
|
sigwpipe.Close() // close our copy of the write end of the pipe or we might be stuck
|
|
answer, err := ioutil.ReadAll(sigrpipe)
|
|
if err != nil || len(answer) == 0 {
|
|
log.Println("restart: child failed to initialize; changes not applied")
|
|
return errIncompleteRestart
|
|
}
|
|
|
|
// Child process is listening now; we can stop all our servers here.
|
|
return Stop()
|
|
}
|