55 "os"
66 "os/signal"
77 "strconv"
8+ "sync"
89 "syscall"
910
1011 "github.com/fsnotify/fsnotify"
@@ -13,24 +14,24 @@ import (
1314 "github.com/snorwin/haproxy-reload-wrapper/pkg/utils"
1415)
1516
17+ var (
18+ executable string
19+ cmds []* exec.Cmd
20+ l sync.Mutex
21+ terminated bool
22+ )
23+
1624func main () {
1725 // fetch the absolut path of the haproxy executable
18- executable , err := utils .LookupExecutablePathAbs ("haproxy" )
26+ var err error
27+ executable , err = utils .LookupExecutablePathAbs ("haproxy" )
1928 if err != nil {
2029 log .Emergency (err .Error ())
2130 os .Exit (1 )
2231 }
2332
24- // execute haproxy with the flags provided as a child process asynchronously
25- cmd := exec .Command (executable , os .Args [1 :]... )
26- cmd .Stdout = os .Stdout
27- cmd .Stderr = os .Stderr
28- cmd .Env = utils .LoadEnvFile ()
29- if err := cmd .AsyncRun (); err != nil {
30- log .Emergency (err .Error ())
31- os .Exit (1 )
32- }
33- log .Notice (fmt .Sprintf ("process %d started" , cmd .Process .Pid ))
33+ // execute haproxy with the flags provided as a child process
34+ runInstance ()
3435
3536 watchPath := utils .LookupWatchPath ()
3637 if watchPath == "" {
@@ -54,9 +55,6 @@ func main() {
5455 log .Notice (fmt .Sprintf ("watch : %s" , watchPath ))
5556 }
5657
57- // flag used for termination handling
58- var terminated bool
59-
6058 // initialize a signal handler for SIGINT, SIGTERM and SIGUSR1 (for OpenShift)
6159 sigs := make (chan os.Signal , 1 )
6260 signal .Notify (sigs , syscall .SIGINT , syscall .SIGTERM , syscall .SIGUSR1 )
@@ -81,62 +79,114 @@ func main() {
8179 }
8280 }
8381
84- // create a new haproxy process which will replace the old one after it was successfully started
85- tmp := exec .Command (executable , append ([]string {"-x" , utils .LookupHAProxySocketPath (), "-sf" , strconv .Itoa (cmd .Process .Pid )}, os .Args [1 :]... )... )
86- tmp .Stdout = os .Stdout
87- tmp .Stderr = os .Stderr
88- tmp .Env = utils .LoadEnvFile ()
89-
90- if err := tmp .AsyncRun (); err != nil {
91- log .Warning (err .Error ())
92- log .Warning ("reload failed" )
93- continue
94- }
82+ // create a new haproxy process which will take over listeners
83+ // from the previous ones after it was successfully started
84+ runInstance ()
9585
96- log .Notice (fmt .Sprintf ("process %d started" , tmp .Process .Pid ))
97- select {
98- case <- cmd .Terminated :
99- // old haproxy terminated - successfully started a new process replacing the old one
100- log .Notice (fmt .Sprintf ("process %d terminated : %s" , cmd .Process .Pid , cmd .Status ()))
101- log .Notice ("reload successful" )
102- cmd = tmp
103- case <- tmp .Terminated :
104- // new haproxy terminated without terminating the old process - this can happen if the modified configuration file was invalid
105- log .Warning (fmt .Sprintf ("process %d terminated unexpectedly : %s" , tmp .Process .Pid , tmp .Status ()))
106- log .Warning ("reload failed" )
107- }
10886 case err := <- fswatch .Errors :
10987 // handle errors of fsnotify.Watcher
11088 log .Alert (err .Error ())
11189 case sig := <- sigs :
11290 // handle SIGINT, SIGTERM, SIGUSR1 and propagate it to child process
11391 log .Notice (fmt .Sprintf ("received singal %d" , sig ))
11492
115- if cmd . Process == nil {
93+ if len ( cmds ) == 0 {
11694 // received termination suddenly before child process was even started
11795 os .Exit (0 )
11896 }
11997
12098 // set termination flag before propagating the signal in order to prevent race conditions
12199 terminated = true
122100
123- // propagate signal to child process
124- if err := cmd .Process .Signal (sig ); err != nil {
125- log .Warning (fmt .Sprintf ("propagating signal %d to process %d failed" , sig , cmd .Process .Pid ))
126- }
127- case <- cmd .Terminated :
128- // check for unexpected termination
129- if ! terminated {
130- log .Emergency (fmt .Sprintf ("process %d teminated unexpectedly : %s" , cmd .Process .Pid , cmd .Status ()))
131- if cmd .ProcessState != nil && cmd .ProcessState .ExitCode () != 0 {
132- os .Exit (cmd .ProcessState .ExitCode ())
133- } else {
134- os .Exit (1 )
101+ // propagate signal to child processes
102+ for i := range cmds {
103+ if cmds [i ].Process != nil {
104+ if err := cmds [i ].Process .Signal (sig ); err != nil {
105+ log .Warning (fmt .Sprintf ("propagating signal %d to process %d failed" , sig , cmds [i ].Process .Pid ))
106+ }
135107 }
136108 }
109+ }
110+ }
111+ }
112+
113+ func runInstance () {
114+
115+ // validate the config by using the "-c" flag
116+ argsValidate := append (os .Args [1 :], "-c" )
117+ cmdValidate := exec .Command (executable , argsValidate ... )
118+ cmdValidate .Stdout = os .Stdout
119+ cmdValidate .Stderr = os .Stderr
120+ cmdValidate .Env = utils .LoadEnvFile ()
121+
122+ if err := cmdValidate .Run (); err != nil {
123+ log .Warning ("validate failed: " + err .Error ())
124+ // exit if the config is invalid and no other process is running
125+ if len (cmds ) == 0 {
126+ os .Exit (1 )
127+ }
128+ return
129+ }
130+
131+ // launch the actual haproxy including the previous pids to terminate
132+ args := os .Args [1 :]
133+ if len (cmds ) > 0 {
134+ args = append (args , []string {"-x" , utils .LookupHAProxySocketPath (), "-sf" , pids ()}... )
135+ }
136+
137+ cmd := exec .Command (executable , args ... )
138+ cmd .Stdout = os .Stdout
139+ cmd .Stderr = os .Stderr
140+ cmd .Env = utils .LoadEnvFile ()
141+
142+ if err := cmd .AsyncRun (); err != nil {
143+ log .Warning ("process starting failed: " + err .Error ())
144+ }
145+ go func (cmd * exec.Cmd ) {
146+ <- cmd .Terminated
147+ log .Notice (fmt .Sprintf ("process %d terminated : %s" , cmd .Process .Pid , cmd .Status ()))
137148
138- log .Notice (fmt .Sprintf ("process %d terminated : %s" , cmd .Process .Pid , cmd .Status ()))
149+ // exit if termination signal was received and the last process terminated abnormally
150+ if terminated && cmd .ProcessState .ExitCode () != 0 {
139151 os .Exit (cmd .ProcessState .ExitCode ())
140152 }
153+
154+ // remove the process from tracking
155+ l .Lock ()
156+ defer l .Unlock ()
157+ for i := range cmds {
158+ if cmds [i ].Process .Pid == cmd .Process .Pid {
159+ cmds = append (cmds [:i ], cmds [i + 1 :]... )
160+ break
161+ }
162+ }
163+
164+ // exit if there are no more processes running
165+ if len (cmds ) == 0 {
166+ if cmd .ProcessState != nil && cmd .ProcessState .ExitCode () != 0 {
167+ os .Exit (cmd .ProcessState .ExitCode ())
168+ } else {
169+ os .Exit (0 )
170+ }
171+ }
172+ }(cmd )
173+
174+ log .Notice (fmt .Sprintf ("process started with pid %d and status %s" , cmd .Process .Pid , cmd .Status ()))
175+
176+ l .Lock ()
177+ defer l .Unlock ()
178+ cmds = append (cmds , cmd )
179+ }
180+
181+ func pids () string {
182+ var str string
183+ if len (cmds ) == 0 {
184+ return str
141185 }
186+
187+ for i := range cmds {
188+ str = strconv .Itoa (cmds [i ].Process .Pid ) + " " + str
189+ }
190+
191+ return str
142192}
0 commit comments