Skip to content

Commit b0572de

Browse files
committed
Implemented php-fpm graceful shutdown on sigterm
Added for k8s. Also shutdown signal delay added to prevent errors when nginx + fpm containers in the pod. In this case nginx must recive signal earlier.
1 parent c58510a commit b0572de

File tree

2 files changed

+107
-38
lines changed

2 files changed

+107
-38
lines changed

main.go

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55
"net/http"
66
"os"
7-
"os/exec"
87
"os/signal"
98
"syscall"
109
"time"
@@ -28,6 +27,7 @@ func init() {
2827
pflag.Duration("scrape-interval", time.Second, "fpm metrics scrape interval")
2928

3029
pflag.Uint("line-buffer-size", 16*1024, "Max log line size (in bytes)")
30+
pflag.Duration("shutdown-delay", 500*time.Millisecond, "Delay before process shutdown")
3131

3232
pflag.Parse()
3333

@@ -73,28 +73,28 @@ func main() {
7373
defer dataListener.Stop()
7474

7575
signalCh := make(chan os.Signal, 1)
76-
signal.Notify(signalCh, os.Interrupt)
77-
signal.Notify(signalCh, os.Kill)
78-
79-
cmd := exec.Command(viper.GetString("fpm"))
80-
cmd.Stdout = os.Stdout
81-
cmd.Stderr = stderr
82-
cmd.Env = os.Environ()
83-
cmd.Env = append(cmd.Env, fmt.Sprintf("FPM_WRAPPER_SOCK=unix://%s", viper.GetString("wrapper-socket")))
84-
cmd.Args = append(cmd.Args, "--nodaemonize")
85-
cmd.Args = append(cmd.Args, "--fpm-config", viper.GetString("fpm-config"))
86-
cmd.Args = append(cmd.Args, findFpmArgs()...)
87-
88-
if err = cmd.Start(); err != nil {
76+
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
77+
78+
fpmProcess := phpfpm.NewProcess(
79+
viper.GetString("fpm"),
80+
viper.GetString("fpm-config"),
81+
os.Stdout,
82+
stderr,
83+
viper.GetString("wrapper-socket"),
84+
viper.GetDuration("shutdown-delay"),
85+
findFpmArgs()...
86+
)
87+
88+
if err = fpmProcess.Start(); err != nil {
8989
fmt.Printf("exec.Command: %v", err)
9090
os.Exit(1)
9191
}
9292

93-
go handleSignals(cmd, signalCh)
94-
procErrCh := make(chan error, 1)
93+
go fpmProcess.HandleSignal(signalCh)
9594

95+
fpmExitCodeCh := make(chan int, 1)
9696
go func() {
97-
procErrCh <- cmd.Wait()
97+
fpmExitCodeCh <- fpmProcess.Wait(errCh)
9898
}()
9999

100100
http.Handle(viper.GetString("metrics-path"), promhttp.Handler())
@@ -112,27 +112,8 @@ func main() {
112112
if err != nil {
113113
panic(err)
114114
}
115-
case err := <-procErrCh:
116-
if err == nil {
117-
os.Exit(0)
118-
}
119-
if exitErr, ok := err.(*exec.ExitError); ok {
120-
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
121-
os.Exit(status.ExitStatus())
122-
}
123-
} else {
124-
panic(err)
125-
}
126-
}
127-
}
128-
}
129-
130-
func handleSignals(cmd *exec.Cmd, signalCh chan os.Signal) {
131-
for {
132-
err := cmd.Process.Signal(<-signalCh)
133-
if err != nil {
134-
fmt.Printf("cmd.Process.Signal: %v", err)
135-
os.Exit(1)
115+
case exitCode := <-fpmExitCodeCh:
116+
os.Exit(exitCode)
136117
}
137118
}
138119
}

pkg/phpfpm/process.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package phpfpm
2+
3+
import (
4+
"os"
5+
"fmt"
6+
"os/exec"
7+
"syscall"
8+
"time"
9+
"io"
10+
)
11+
12+
type Process struct {
13+
cmd *exec.Cmd
14+
shutdownDelay time.Duration
15+
}
16+
17+
func NewProcess(
18+
fpmPath string,
19+
fpmConfigPath string,
20+
stdout io.Writer,
21+
stderr io.Writer,
22+
wrapperSocket string,
23+
shutdownDelay time.Duration,
24+
extraArgs ...string,
25+
) *Process {
26+
cmd := exec.Command(fpmPath)
27+
cmd.Stdout = stdout
28+
cmd.Stderr = stderr
29+
30+
cmd.Env = os.Environ()
31+
cmd.Env = append(cmd.Env, fmt.Sprintf("FPM_WRAPPER_SOCK=unix://%s", wrapperSocket))
32+
33+
//cmd.Args = append(cmd.Args, "--nodaemonize")
34+
//cmd.Args = append(cmd.Args, "--fpm-config", fpmConfigPath)
35+
cmd.Args = append(cmd.Args, extraArgs...)
36+
37+
return &Process{cmd: cmd, shutdownDelay: shutdownDelay}
38+
}
39+
40+
func (p *Process) Start() error {
41+
return p.cmd.Start()
42+
}
43+
44+
func (p *Process) HandleSignal(signalCh chan os.Signal) {
45+
for {
46+
sig := <-signalCh
47+
48+
// k8s graceful shutdown impl
49+
if sig == syscall.SIGTERM {
50+
if p.shutdownDelay > 0 {
51+
<-time.After(p.shutdownDelay)
52+
}
53+
54+
sig = syscall.SIGQUIT
55+
}
56+
57+
p.cmd.Process.Signal(sig)
58+
}
59+
}
60+
61+
func (p *Process) Wait(errCh chan<- error) int {
62+
err := p.cmd.Wait()
63+
if err == nil {
64+
return 0
65+
}
66+
67+
exitErr, ok := err.(*exec.ExitError)
68+
if !ok {
69+
errCh <- err
70+
return -1
71+
}
72+
73+
status, ok := exitErr.Sys().(syscall.WaitStatus)
74+
if !ok {
75+
errCh <- err
76+
return -1
77+
}
78+
79+
if status.Exited() {
80+
return status.ExitStatus()
81+
}
82+
83+
if status.Signaled() {
84+
return 128 + int(status.Signal())
85+
}
86+
87+
return -1
88+
}

0 commit comments

Comments
 (0)