Commit 7d46a7d5 authored by Matthew Holt's avatar Matthew Holt

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.
parent 9e2cef38
...@@ -59,7 +59,7 @@ var ( ...@@ -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 ( ...@@ -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 ( ...@@ -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() { ...@@ -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 ( ...@@ -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 { ...@@ -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:]...)
if err != nil { cmd.Stdin = rpipe // fd 0
return err cmd.Stdout = os.Stdout // fd 1
} cmd.Stderr = os.Stderr // fd 2
cmd.ExtraFiles = extraFiles
// Fork the process with the current environment and file descriptors // Spawn the child process
execSpec := &syscall.ProcAttr{ err = cmd.Start()
Env: os.Environ(),
Files: fds,
}
_, err = syscall.ForkExec(exepath, os.Args, execSpec)
if err != nil { if err != nil {
return err return err
} }
// Feed it the Caddyfile // Feed Caddyfile to the child
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() { ...@@ -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() { ...@@ -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)
} }
} }
}() }()
......
...@@ -30,7 +30,7 @@ const ( ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment