mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05: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,17 +103,18 @@ 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)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Input must never be nil; try to load something
 | 
						// Input must never be nil; try to load something
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								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,22 +95,23 @@ 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() {
 | 
					 | 
				
			||||||
			log.Printf("[ERROR] Upon starting %s: %v", appName, err)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
		mustLogFatal(err)
 | 
							mustLogFatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Twiddle your thumbs
 | 
						// Twiddle your thumbs
 | 
				
			||||||
	caddy.Wait()
 | 
						caddy.Wait()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 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{}) {
 | 
				
			||||||
 | 
						if !caddy.IsRestart() {
 | 
				
			||||||
		log.SetOutput(os.Stderr)
 | 
							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