mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	cmd: Improve stop command by trying API before signaling process
This allows graceful shutdown on all platforms
This commit is contained in:
		
							parent
							
								
									0ca109db4a
								
							
						
					
					
						commit
						6cdb2392d7
					
				@ -35,6 +35,7 @@ import (
 | 
				
			|||||||
	"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
						"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
				
			||||||
	"github.com/keybase/go-ps"
 | 
						"github.com/keybase/go-ps"
 | 
				
			||||||
	"github.com/mholt/certmagic"
 | 
						"github.com/mholt/certmagic"
 | 
				
			||||||
 | 
						"go.uber.org/zap"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func cmdStart(fl Flags) (int, error) {
 | 
					func cmdStart(fl Flags) (int, error) {
 | 
				
			||||||
@ -193,19 +194,43 @@ func cmdRun(fl Flags) (int, error) {
 | 
				
			|||||||
	select {}
 | 
						select {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func cmdStop(_ Flags) (int, error) {
 | 
					func cmdStop(fl Flags) (int, error) {
 | 
				
			||||||
 | 
						stopCmdAddrFlag := fl.String("address")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						adminAddr := caddy.DefaultAdminListen
 | 
				
			||||||
 | 
						if stopCmdAddrFlag != "" {
 | 
				
			||||||
 | 
							adminAddr = stopCmdAddrFlag
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						stopEndpoint := fmt.Sprintf("http://%s/stop", adminAddr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req, err := http.NewRequest(http.MethodPost, stopEndpoint, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.Header.Set("Origin", adminAddr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = apiRequest(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// if the caddy instance doesn't have an API listener set up,
 | 
				
			||||||
 | 
							// or we are unable to reach it for some reason, try signaling it
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							caddy.Log().Warn("unable to use API to stop instance; will try to signal the process",
 | 
				
			||||||
 | 
								zap.String("endpoint", stopEndpoint),
 | 
				
			||||||
 | 
								zap.Error(err),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		processList, err := ps.Processes()
 | 
							processList, err := ps.Processes()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err)
 | 
								return caddy.ExitCodeFailedStartup, fmt.Errorf("listing processes: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		thisProcName := getProcessName()
 | 
							thisProcName := getProcessName()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var found bool
 | 
							var found bool
 | 
				
			||||||
		for _, p := range processList {
 | 
							for _, p := range processList {
 | 
				
			||||||
		// the process we're looking for should have the same name but different PID
 | 
								// the process we're looking for should have the same name as us but different PID
 | 
				
			||||||
			if p.Executable() == thisProcName && p.Pid() != os.Getpid() {
 | 
								if p.Executable() == thisProcName && p.Pid() != os.Getpid() {
 | 
				
			||||||
				found = true
 | 
									found = true
 | 
				
			||||||
				fmt.Printf("pid=%d\n", p.Pid())
 | 
									fmt.Printf("pid=%d\n", p.Pid())
 | 
				
			||||||
 | 
					 | 
				
			||||||
				if err := gracefullyStopProcess(p.Pid()); err != nil {
 | 
									if err := gracefullyStopProcess(p.Pid()); err != nil {
 | 
				
			||||||
					return caddy.ExitCodeFailedStartup, err
 | 
										return caddy.ExitCodeFailedStartup, err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -214,7 +239,10 @@ func cmdStop(_ Flags) (int, error) {
 | 
				
			|||||||
		if !found {
 | 
							if !found {
 | 
				
			||||||
			return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running")
 | 
								return caddy.ExitCodeFailedStartup, fmt.Errorf("Caddy is not running")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fmt.Println(" success")
 | 
							fmt.Println(" success")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return caddy.ExitCodeSuccess, nil
 | 
						return caddy.ExitCodeSuccess, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -251,25 +279,19 @@ func cmdReload(fl Flags) (int, error) {
 | 
				
			|||||||
	if adminAddr == "" {
 | 
						if adminAddr == "" {
 | 
				
			||||||
		adminAddr = caddy.DefaultAdminListen
 | 
							adminAddr = caddy.DefaultAdminListen
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	adminEndpoint := fmt.Sprintf("http://%s/load", adminAddr)
 | 
						loadEndpoint := fmt.Sprintf("http://%s/load", adminAddr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// send the configuration to the instance
 | 
						// prepare the request to update the configuration
 | 
				
			||||||
	resp, err := http.Post(adminEndpoint, "application/json", bytes.NewReader(config))
 | 
						req, err := http.NewRequest(http.MethodPost, loadEndpoint, bytes.NewReader(config))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return caddy.ExitCodeFailedStartup,
 | 
							return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err)
 | 
				
			||||||
			fmt.Errorf("sending configuration to instance: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer resp.Body.Close()
 | 
						req.Header.Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						req.Header.Set("Origin", adminAddr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// if it didn't work, let the user know
 | 
						err = apiRequest(req)
 | 
				
			||||||
	if resp.StatusCode >= 400 {
 | 
					 | 
				
			||||||
		respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
			return caddy.ExitCodeFailedStartup,
 | 
							return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
 | 
				
			||||||
				fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return caddy.ExitCodeFailedStartup,
 | 
					 | 
				
			||||||
			fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return caddy.ExitCodeSuccess, nil
 | 
						return caddy.ExitCodeSuccess, nil
 | 
				
			||||||
@ -522,3 +544,22 @@ commands:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return caddy.ExitCodeSuccess, nil
 | 
						return caddy.ExitCodeSuccess, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func apiRequest(req *http.Request) error {
 | 
				
			||||||
 | 
						resp, err := http.DefaultClient.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("performing request: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if it didn't work, let the user know
 | 
				
			||||||
 | 
						if resp.StatusCode >= 400 {
 | 
				
			||||||
 | 
							respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*10))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -134,11 +134,18 @@ not quit after printing, and can be useful for troubleshooting.`,
 | 
				
			|||||||
		Long: `
 | 
							Long: `
 | 
				
			||||||
Stops the background Caddy process as gracefully as possible.
 | 
					Stops the background Caddy process as gracefully as possible.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
On Windows, this stop is forceful and Caddy will not have an opportunity to
 | 
					It will first try to use the admin API's /stop endpoint; the address of
 | 
				
			||||||
clean up any active locks; for a graceful shutdown on Windows, use Ctrl+C
 | 
					this request can be customized using the --address flag if it is not the
 | 
				
			||||||
or the /stop API endpoint.
 | 
					default.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note: this will stop any process named the same as the executable (os.Args[0]).`,
 | 
					If that fails for any reason, it will attempt to signal the first process
 | 
				
			||||||
 | 
					it can find named the same as this one (os.Args[0]). On Windows, such
 | 
				
			||||||
 | 
					a stop is forceful because Windows does not have signals.`,
 | 
				
			||||||
 | 
							Flags: func() *flag.FlagSet {
 | 
				
			||||||
 | 
								fs := flag.NewFlagSet("stop", flag.ExitOnError)
 | 
				
			||||||
 | 
								fs.String("address", "", "The address to use to reach the admin API endpoint, if not the default")
 | 
				
			||||||
 | 
								return fs
 | 
				
			||||||
 | 
							}(),
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RegisterCommand(Command{
 | 
						RegisterCommand(Command{
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func gracefullyStopProcess(pid int) error {
 | 
					func gracefullyStopProcess(pid int) error {
 | 
				
			||||||
	fmt.Printf("Graceful stop...\n")
 | 
						fmt.Print("Graceful stop... ")
 | 
				
			||||||
	err := syscall.Kill(pid, syscall.SIGINT)
 | 
						err := syscall.Kill(pid, syscall.SIGINT)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("kill: %v", err)
 | 
							return fmt.Errorf("kill: %v", err)
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func gracefullyStopProcess(pid int) error {
 | 
					func gracefullyStopProcess(pid int) error {
 | 
				
			||||||
	fmt.Printf("Forceful Stop...\n")
 | 
						fmt.Print("Forceful stop... ")
 | 
				
			||||||
	// process on windows will not stop unless forced with /f
 | 
						// process on windows will not stop unless forced with /f
 | 
				
			||||||
	cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/f")
 | 
						cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/f")
 | 
				
			||||||
	if err := cmd.Run(); err != nil {
 | 
						if err := cmd.Run(); err != nil {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user