mirror of
https://github.com/caddyserver/caddy.git
synced 2025-06-23 15:31:40 -04:00
Much refactor; many fix; so wow
Fixed pidfile writing problem where a pidfile would be written even if child failed, also cleaned up restarts a bit and fixed a few bugs, it's more robust now in case of failures and with logging.
This commit is contained in:
parent
9e2cef38f6
commit
7d46a7d5f4
@ -59,7 +59,7 @@ var (
|
|||||||
|
|
||||||
// errIncompleteRestart occurs if this process is a fork
|
// errIncompleteRestart occurs if this process is a fork
|
||||||
// of the parent but no Caddyfile was piped in
|
// of the parent but no Caddyfile was piped in
|
||||||
errIncompleteRestart = errors.New("cannot finish restart successfully")
|
errIncompleteRestart = errors.New("incomplete restart")
|
||||||
|
|
||||||
// servers is a list of all the currently-listening servers
|
// servers is a list of all the currently-listening servers
|
||||||
servers []*server.Server
|
servers []*server.Server
|
||||||
@ -103,15 +103,16 @@ const (
|
|||||||
// In any case, an error is returned if Caddy could not be
|
// In any case, an error is returned if Caddy could not be
|
||||||
// started.
|
// started.
|
||||||
func Start(cdyfile Input) (err error) {
|
func Start(cdyfile Input) (err error) {
|
||||||
// When we return, tell the parent whether we started
|
// If we return with no errors, we must do two things: tell the
|
||||||
// successfully, and if so, write the pidfile (if enabled)
|
// parent that we succeeded and write to the pidfile.
|
||||||
defer func() {
|
defer func() {
|
||||||
success := err == nil
|
if err == nil {
|
||||||
signalParent(success)
|
signalSuccessToParent() // TODO: Is doing this more than once per process a bad idea? Start could get called more than once in other apps.
|
||||||
if success && PidFile != "" {
|
if PidFile != "" {
|
||||||
err := writePidFile()
|
err := writePidFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Could not write pidfile: %v", err)
|
log.Printf("[ERROR] Could not write pidfile: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -39,16 +40,16 @@ func checkFdlimit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// signalParent tells the parent our status using pipe at index 3.
|
// signalSuccessToParent tells the parent our status using pipe at index 3.
|
||||||
// If this process is not a restart, this function does nothing.
|
// If this process is not a restart, this function does nothing.
|
||||||
// Calling this is vital so that the parent process can unblock and
|
// Calling this function once this process has successfully initialized
|
||||||
// either continue running or kill itself.
|
// is vital so that the parent process can unblock and kill itself.
|
||||||
func signalParent(success bool) {
|
func signalSuccessToParent() {
|
||||||
if IsRestart() {
|
if IsRestart() {
|
||||||
ppipe := os.NewFile(3, "") // parent is listening on pipe at index 3
|
ppipe := os.NewFile(3, "") // parent is reading from pipe at index 3
|
||||||
if success {
|
_, err := ppipe.Write([]byte("success")) // we must send some bytes to the parent
|
||||||
// Tell parent process that we're OK so it can quit now
|
if err != nil {
|
||||||
ppipe.Write([]byte("success"))
|
log.Printf("[ERROR] Communicating successful init to parent: %v", err)
|
||||||
}
|
}
|
||||||
ppipe.Close()
|
ppipe.Close()
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -54,56 +53,56 @@ func Restart(newCaddyfile Input) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare a pipe that the child process will use to communicate
|
// Prepare a pipe that the child process will use to communicate
|
||||||
// its success or failure with us, the parent
|
// its success with us by sending > 0 bytes
|
||||||
sigrpipe, sigwpipe, err := os.Pipe()
|
sigrpipe, sigwpipe, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass along current environment and file descriptors to child.
|
// Pass along relevant file descriptors to child process; ordering
|
||||||
// Ordering here is very important: stdin, stdout, stderr, sigpipe,
|
// is very important since we rely on these being in certain positions.
|
||||||
// and then the listener file descriptors (in order).
|
extraFiles := []*os.File{sigwpipe}
|
||||||
fds := []uintptr{rpipe.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), sigwpipe.Fd()}
|
|
||||||
|
|
||||||
// Now add file descriptors of the sockets
|
// Add file descriptors of all the sockets
|
||||||
serversMu.Lock()
|
serversMu.Lock()
|
||||||
for i, s := range servers {
|
for i, s := range servers {
|
||||||
fds = append(fds, s.ListenerFd())
|
extraFiles = append(extraFiles, s.ListenerFd())
|
||||||
cdyfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
|
cdyfileGob.ListenerFds[s.Addr] = uintptr(4 + i) // 4 fds come before any of the listeners
|
||||||
}
|
}
|
||||||
serversMu.Unlock()
|
serversMu.Unlock()
|
||||||
|
|
||||||
// We're gonna need the proper path to the executable
|
// Set up the command
|
||||||
exepath, err := exec.LookPath(os.Args[0])
|
cmd := exec.Command(os.Args[0], os.Args[1:]...)
|
||||||
|
cmd.Stdin = rpipe // fd 0
|
||||||
|
cmd.Stdout = os.Stdout // fd 1
|
||||||
|
cmd.Stderr = os.Stderr // fd 2
|
||||||
|
cmd.ExtraFiles = extraFiles
|
||||||
|
|
||||||
|
// Spawn the child process
|
||||||
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fork the process with the current environment and file descriptors
|
// Feed Caddyfile to the child
|
||||||
execSpec := &syscall.ProcAttr{
|
|
||||||
Env: os.Environ(),
|
|
||||||
Files: fds,
|
|
||||||
}
|
|
||||||
_, err = syscall.ForkExec(exepath, os.Args, execSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feed it the Caddyfile
|
|
||||||
err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
|
err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
wpipe.Close()
|
wpipe.Close()
|
||||||
|
|
||||||
// Wait for child process to signal success or fail
|
// Determine whether child startup succeeded
|
||||||
sigwpipe.Close() // close our copy of the write end of the pipe or we might be stuck
|
sigwpipe.Close() // close our copy of the write end of the pipe -- TODO: why?
|
||||||
answer, err := ioutil.ReadAll(sigrpipe)
|
answer, readErr := ioutil.ReadAll(sigrpipe)
|
||||||
if err != nil || len(answer) == 0 {
|
if answer == nil || len(answer) == 0 {
|
||||||
log.Println("[ERROR] Restart: child failed to initialize; changes not applied")
|
cmdErr := cmd.Wait() // get exit status
|
||||||
|
log.Printf("[ERROR] Restart: child failed to initialize (%v) - changes not applied", cmdErr)
|
||||||
|
if readErr != nil {
|
||||||
|
log.Printf("[ERROR] Restart: additionally, error communicating with child process: %v", readErr)
|
||||||
|
}
|
||||||
return errIncompleteRestart
|
return errIncompleteRestart
|
||||||
}
|
}
|
||||||
|
|
||||||
// Child process is listening now; we can stop all our servers here.
|
// Looks like child was successful; we can exit gracefully.
|
||||||
return Stop()
|
return Stop()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ func init() {
|
|||||||
for {
|
for {
|
||||||
<-reload
|
<-reload
|
||||||
|
|
||||||
|
log.Println("[INFO] SIGUSR1: Reloading")
|
||||||
|
|
||||||
var updatedCaddyfile Input
|
var updatedCaddyfile Input
|
||||||
|
|
||||||
caddyfileMu.Lock()
|
caddyfileMu.Lock()
|
||||||
@ -42,7 +44,7 @@ func init() {
|
|||||||
|
|
||||||
err := Restart(updatedCaddyfile)
|
err := Restart(updatedCaddyfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] SIGUSR1: Restart returned: %v", err)
|
log.Printf("[ERROR] SIGUSR1: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
21
main.go
21
main.go
@ -30,7 +30,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&letsencrypt.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
|
flag.BoolVar(&letsencrypt.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
|
||||||
flag.StringVar(&letsencrypt.CAUrl, "ca", "https://acme-staging.api.letsencrypt.org", "Certificate authority ACME server")
|
flag.StringVar(&letsencrypt.CAUrl, "ca", "https://acme-staging.api.letsencrypt.org/directory", "Certificate authority ACME server")
|
||||||
flag.StringVar(&conf, "conf", "", "Configuration file to use (default="+caddy.DefaultConfigFile+")")
|
flag.StringVar(&conf, "conf", "", "Configuration file to use (default="+caddy.DefaultConfigFile+")")
|
||||||
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
|
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
|
||||||
flag.StringVar(&letsencrypt.DefaultEmail, "email", "", "Default Let's Encrypt account email address")
|
flag.StringVar(&letsencrypt.DefaultEmail, "email", "", "Default Let's Encrypt account email address")
|
||||||
@ -62,7 +62,7 @@ func main() {
|
|||||||
default:
|
default:
|
||||||
file, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
file, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error opening log file: %v", err)
|
log.Fatalf("Error opening process log file: %v", err)
|
||||||
}
|
}
|
||||||
log.SetOutput(file)
|
log.SetOutput(file)
|
||||||
}
|
}
|
||||||
@ -95,11 +95,7 @@ func main() {
|
|||||||
// Start your engines
|
// Start your engines
|
||||||
err = caddy.Start(caddyfile)
|
err = caddy.Start(caddyfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if caddy.IsRestart() {
|
mustLogFatal(err)
|
||||||
log.Printf("[ERROR] Upon starting %s: %v", appName, err)
|
|
||||||
} else {
|
|
||||||
mustLogFatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Twiddle your thumbs
|
// Twiddle your thumbs
|
||||||
@ -107,10 +103,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mustLogFatal just wraps log.Fatal() in a way that ensures the
|
// mustLogFatal just wraps log.Fatal() in a way that ensures the
|
||||||
// output is always printed to stderr so the user can see it,
|
// output is always printed to stderr so the user can see it
|
||||||
// even if the process log was not enabled.
|
// if the user is still there, even if the process log was not
|
||||||
|
// enabled. If this process is a restart, however, and the user
|
||||||
|
// might not be there anymore, this just logs to the process log
|
||||||
|
// and exits.
|
||||||
func mustLogFatal(args ...interface{}) {
|
func mustLogFatal(args ...interface{}) {
|
||||||
log.SetOutput(os.Stderr)
|
if !caddy.IsRestart() {
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
}
|
||||||
log.Fatal(args...)
|
log.Fatal(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,14 +280,11 @@ func (s *Server) WaitUntilStarted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListenerFd gets the file descriptor of the listener.
|
// ListenerFd gets the file descriptor of the listener.
|
||||||
func (s *Server) ListenerFd() uintptr {
|
func (s *Server) ListenerFd() *os.File {
|
||||||
s.listenerMu.Lock()
|
s.listenerMu.Lock()
|
||||||
defer s.listenerMu.Unlock()
|
defer s.listenerMu.Unlock()
|
||||||
file, err := s.listener.File()
|
file, _ := s.listener.File()
|
||||||
if err != nil {
|
return file
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return file.Fd()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP is the entry point for every request to the address that s
|
// ServeHTTP is the entry point for every request to the address that s
|
||||||
|
Loading…
x
Reference in New Issue
Block a user