mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-24 11:15:49 +03:00
b5b31e398c
Lots of refinement still needed and runs only on POSIX systems. Windows will not get true graceful restarts (for now), but we will opt for very, very quick forceful restarts. Also, server configs are no longer put into a map; it is critical that they stay ordered so that they can be matched with their sockets in the child process after forking. This implementation of graceful restarts is probably not perfect, but it is a good start. Lots of details to attend to now.
172 lines
4.3 KiB
Go
172 lines
4.3 KiB
Go
// Package app holds application-global state to make it accessible
|
|
// by other packages in the application.
|
|
//
|
|
// This package differs from config in that the things in app aren't
|
|
// really related to server configuration.
|
|
package app
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/mholt/caddy/server"
|
|
)
|
|
|
|
const (
|
|
// Name is the program name
|
|
Name = "Caddy"
|
|
|
|
// Version is the program version
|
|
Version = "0.7.6"
|
|
)
|
|
|
|
var (
|
|
// Servers is a list of all the currently-listening servers
|
|
Servers []*server.Server
|
|
|
|
// ServersMutex protects the Servers slice during changes
|
|
ServersMutex sync.Mutex
|
|
|
|
// Wg is used to wait for all servers to shut down
|
|
Wg sync.WaitGroup
|
|
|
|
// HTTP2 indicates whether HTTP2 is enabled or not
|
|
HTTP2 bool // TODO: temporary flag until http2 is standard
|
|
|
|
// Quiet mode hides non-error initialization output
|
|
Quiet bool
|
|
)
|
|
|
|
func init() {
|
|
go func() {
|
|
// Wait for signal
|
|
interrupt := make(chan os.Signal, 1)
|
|
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGTERM? Or that should not run callbacks...
|
|
<-interrupt
|
|
|
|
// Run shutdown callbacks
|
|
var exitCode int
|
|
ServersMutex.Lock()
|
|
errs := server.ShutdownCallbacks(Servers)
|
|
ServersMutex.Unlock()
|
|
if len(errs) > 0 {
|
|
for _, err := range errs {
|
|
log.Println(err)
|
|
}
|
|
exitCode = 1
|
|
}
|
|
os.Exit(exitCode)
|
|
}()
|
|
}
|
|
|
|
// 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 caddyfile as its input
|
|
// configuration; it will not look elsewhere for the config
|
|
// to use.
|
|
func Restart(caddyfile []byte) error {
|
|
// TODO: This is POSIX-only right now; also, os.Args[0] is required!
|
|
// TODO: Pipe the Caddyfile to stdin of child!
|
|
// TODO: Before stopping this process, verify child started successfully (valid Caddyfile, etc)
|
|
|
|
// Tell the child that it's a restart
|
|
os.Setenv("CADDY_RESTART", "true")
|
|
|
|
// Pass along current environment and file descriptors to child.
|
|
// We pass along the file descriptors explicitly to ensure proper
|
|
// order, since losing the original order will break the child.
|
|
fds := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
|
|
|
|
// Now add file descriptors of the sockets
|
|
ServersMutex.Lock()
|
|
for _, s := range Servers {
|
|
fds = append(fds, s.ListenerFd())
|
|
}
|
|
ServersMutex.Unlock()
|
|
|
|
// Fork the process with the current environment and file descriptors
|
|
execSpec := &syscall.ProcAttr{
|
|
Env: os.Environ(),
|
|
Files: fds,
|
|
}
|
|
fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)
|
|
if err != nil {
|
|
log.Println("FORK ERR:", err, fork)
|
|
}
|
|
|
|
// Child process is listening now; we can stop all our servers here.
|
|
ServersMutex.Lock()
|
|
for _, s := range Servers {
|
|
go s.Stop() // TODO: error checking/reporting
|
|
}
|
|
ServersMutex.Unlock()
|
|
|
|
return err
|
|
}
|
|
|
|
// SetCPU parses string cpu and sets GOMAXPROCS
|
|
// according to its value. It accepts either
|
|
// a number (e.g. 3) or a percent (e.g. 50%).
|
|
func SetCPU(cpu string) error {
|
|
var numCPU int
|
|
|
|
availCPU := runtime.NumCPU()
|
|
|
|
if strings.HasSuffix(cpu, "%") {
|
|
// Percent
|
|
var percent float32
|
|
pctStr := cpu[:len(cpu)-1]
|
|
pctInt, err := strconv.Atoi(pctStr)
|
|
if err != nil || pctInt < 1 || pctInt > 100 {
|
|
return errors.New("invalid CPU value: percentage must be between 1-100")
|
|
}
|
|
percent = float32(pctInt) / 100
|
|
numCPU = int(float32(availCPU) * percent)
|
|
} else {
|
|
// Number
|
|
num, err := strconv.Atoi(cpu)
|
|
if err != nil || num < 1 {
|
|
return errors.New("invalid CPU value: provide a number or percent greater than 0")
|
|
}
|
|
numCPU = num
|
|
}
|
|
|
|
if numCPU > availCPU {
|
|
numCPU = availCPU
|
|
}
|
|
|
|
runtime.GOMAXPROCS(numCPU)
|
|
return nil
|
|
}
|
|
|
|
// DataFolder returns the path to the folder
|
|
// where the application may store data. This
|
|
// currently resolves to ~/.caddy
|
|
func DataFolder() string {
|
|
return filepath.Join(userHomeDir(), ".caddy")
|
|
}
|
|
|
|
// userHomeDir returns the user's home directory according to
|
|
// environment variables.
|
|
//
|
|
// Credit: http://stackoverflow.com/a/7922977/1048862
|
|
func userHomeDir() string {
|
|
if runtime.GOOS == "windows" {
|
|
home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
|
if home == "" {
|
|
home = os.Getenv("USERPROFILE")
|
|
}
|
|
return home
|
|
}
|
|
return os.Getenv("HOME")
|
|
}
|