@@ -8,7 +8,10 @@ import (
88 "os/exec"
99 "os/signal"
1010 "strconv"
11+ "strings"
12+ "sync"
1113 "syscall"
14+ "time"
1215)
1316
1417const (
@@ -20,9 +23,12 @@ var logger = NewCaddyRailsLogger()
2023
2124type UpstreamProcess struct {
2225 Started chan struct {}
23- cmd * exec.Cmd
26+ Cmd * exec.Cmd
2427 SyncMode bool
2528 PidFile string
29+ workers []int
30+ stopCh chan struct {}
31+ wg sync.WaitGroup
2632}
2733
2834func NewUpstreamProcess (command string , arg []string , syncMode bool , pidFile string ) (* UpstreamProcess , error ) {
@@ -34,97 +40,104 @@ func NewUpstreamProcess(command string, arg []string, syncMode bool, pidFile str
3440
3541 if command == "" && ! fileExists (pidFile ) {
3642 logger .Error ("For running an application, you must provide either an argument to the command serve-rails or ensure the presence of " + RailsExecutionFile )
37-
3843 return nil , fmt .Errorf ("for running an application, you must provide either an argument to the command serve-rails or ensure the presence of %s file" , RailsExecutionFile )
3944 }
4045
41- //logger.Info("Running a rails application by command: ", zap.String(command, strings.Join(arguments, " ")))
42-
4346 return & UpstreamProcess {
4447 Started : make (chan struct {}, 1 ),
45- cmd : exec .Command (command , arguments ... ),
48+ Cmd : exec .Command (command , arguments ... ),
4649 SyncMode : syncMode ,
4750 PidFile : pidFile ,
51+ stopCh : make (chan struct {}),
4852 }, nil
4953}
5054
51- func (p * UpstreamProcess ) Run () (int , error ) {
55+ func (p * UpstreamProcess ) Run (testMode bool ) (int , error ) {
56+ logger .Info ("Starting Run method" )
5257 if p .PidFile != "" {
5358 pid , err := p .readPidFile ()
54-
5559 if err == nil {
56- p .cmd = & exec.Cmd {Process : & os.Process {Pid : pid }}
60+ p .Cmd = & exec.Cmd {Process : & os.Process {Pid : pid }}
5761 p .Started <- struct {}{}
5862 if p .SyncMode {
59- return p . waitAndHandleExit ()
63+ select {}
6064 }
61- go p .waitAndHandleExit ()
6265 return 0 , nil
6366 }
6467 }
6568
66- p .cmd .Stdout = os .Stdout
67- p .cmd .Stderr = os .Stderr
69+ p .Cmd .Stdout = os .Stdout
70+ p .Cmd .Stderr = os .Stderr
6871
69- err := p .cmd .Start ()
72+ err := p .Cmd .Start ()
7073 if err != nil {
7174 return 0 , err
7275 }
7376
77+ p .wg .Add (1 )
7478 p .Started <- struct {}{}
7579
76- go p .handleSignals ()
80+ err = p .waitForPidFile ()
81+ if err != nil {
82+ return 0 , err
83+ }
84+
85+ p .checkForNewWorkers ()
86+
87+ // Signal handling
88+ sigs := make (chan os.Signal , 1 )
89+ signal .Notify (sigs , syscall .SIGINT , syscall .SIGTERM )
90+ go func () {
91+ sig := <- sigs
92+ logger .Info ("Received signal" , zap .String ("signal" , sig .String ()))
93+ p .terminateWorkers (sig )
94+ p .Shutdown ()
95+ close (p .stopCh )
96+ }()
7797
7898 if p .SyncMode {
79- return p .waitAndHandleExit ()
99+ <- p .stopCh
100+ p .wg .Wait ()
80101 }
81102
82- go p . waitAndHandleExit ( )
103+ logger . Info ( "Run method finished successfully" )
83104 return 0 , nil
84105}
85106
86- func (p * UpstreamProcess ) Stop () error {
87- if p .PidFile != "" {
88- pid , err := p .readPidFile ()
107+ func (p * UpstreamProcess ) Shutdown () error {
108+ logger .Info ("Starting Shutdown method" )
109+ if p .Cmd != nil && p .Cmd .Process != nil {
110+ err := p .Cmd .Process .Signal (syscall .SIGTERM )
89111 if err != nil {
90112 return err
91113 }
92114
93- logger .Info ("Stopping the rails application" , zap .Int ("pid" , pid ))
94-
95- return p .signalProcess (pid , syscall .SIGTERM )
96- }
115+ logger .Info ("Waiting for main process to finish" )
116+ err = p .Cmd .Wait ()
117+ if err != nil {
118+ logger .Error ("Failed to wait for process" , zap .Error (err ))
119+ return err
120+ }
97121
98- if p .cmd != nil && p .cmd .Process != nil {
99- return p .cmd .Process .Signal (syscall .SIGTERM )
122+ p .wg .Done ()
123+ logger .Info ("Main process finished" )
124+ return nil
100125 }
101126
102127 return errors .New ("process is not running or PID file is not specified" )
103128}
104129
105- func (p * UpstreamProcess ) PhasedRestart (serverType string ) error {
106- sig , err := determineSignal (serverType )
107- if err != nil {
108- return err
109- }
130+ func (p * UpstreamProcess ) Stop () error {
131+ if p .PidFile != "" {
132+ pid , err := p .readPidFile ()
133+ if err != nil {
134+ return err
135+ }
110136
111- pid , err := p .readPidFile ()
112- if err != nil {
113- return err
137+ return p .signalProcess (pid , syscall .SIGTERM )
114138 }
115139
116- logger .Info ("Hot restarting the rails application" , zap .Int ("pid" , pid ))
117-
118- return p .signalProcess (pid , sig )
119- }
120-
121- func (p * UpstreamProcess ) waitAndHandleExit () (int , error ) {
122- err := p .cmd .Wait ()
123- var exitErr * exec.ExitError
124- if errors .As (err , & exitErr ) {
125- return exitErr .ExitCode (), nil
126- }
127- return 0 , err
140+ return errors .New ("PidFile is not exists" )
128141}
129142
130143func (p * UpstreamProcess ) readPidFile () (int , error ) {
@@ -133,7 +146,7 @@ func (p *UpstreamProcess) readPidFile() (int, error) {
133146 return 0 , fmt .Errorf ("failed to read PID file: %v" , err )
134147 }
135148
136- pid , err := strconv .Atoi (string (pidData ))
149+ pid , err := strconv .Atoi (strings . TrimSpace ( string (pidData ) ))
137150 if err != nil {
138151 return 0 , fmt .Errorf ("failed to convert PID to integer: %v" , err )
139152 }
@@ -149,19 +162,60 @@ func (p *UpstreamProcess) signalProcess(pid int, sig os.Signal) error {
149162 return process .Signal (sig )
150163}
151164
152- func (p * UpstreamProcess ) signal (sig os.Signal ) error {
153- return p .cmd .Process .Signal (sig )
165+ func (p * UpstreamProcess ) waitForPidFile () error {
166+ timeout := time .After (20 * time .Second )
167+ tick := time .Tick (1 * time .Second )
168+
169+ for {
170+ select {
171+ case <- timeout :
172+ return fmt .Errorf ("timed out waiting for PID file" )
173+ case <- tick :
174+ if fileExists (p .PidFile ) {
175+ logger .Info ("PID file found" )
176+ return nil
177+ }
178+ }
179+ }
154180}
155181
156- func (p * UpstreamProcess ) handleSignals () {
157- ch := make (chan os.Signal , 1 )
158- signal .Notify (ch , syscall .SIGINT , syscall .SIGTERM )
182+ func (p * UpstreamProcess ) checkForNewWorkers () {
183+ output , err := exec .Command ("pgrep" , "-P" , strconv .Itoa (p .Cmd .Process .Pid )).Output ()
184+ if err != nil {
185+ logger .Warn ("Failed to check for worker processes" , zap .Error (err ))
186+ return
187+ }
159188
160- sig := <- ch
189+ pidStrings := strings .Fields (string (output ))
190+ for _ , pidStr := range pidStrings {
191+ pid , err := strconv .Atoi (pidStr )
192+ if err == nil {
193+ logger .Info ("Added worker with pid" , zap .Int ("pid" , pid ))
194+ p .workers = append (p .workers , pid )
195+ p .wg .Add (1 ) // Add worker to wait group
196+ } else {
197+ logger .Error ("Error with worker pid" , zap .Int ("pid" , pid ), zap .Error (err ))
198+ }
199+ }
200+ }
161201
162- logger .Info ("Relaying signal to upstream process" , zap .String ("signal" , sig .String ()))
202+ func (p * UpstreamProcess ) terminateWorkers (sig os.Signal ) {
203+ for _ , pid := range p .workers {
204+ process , err := os .FindProcess (pid )
205+ if err != nil {
206+ logger .Warn ("Failed to find worker process" , zap .Int ("pid" , pid ), zap .Error (err ))
207+ continue
208+ }
209+ err = process .Signal (sig )
210+ if err != nil {
211+ logger .Warn ("Failed to signal worker process" , zap .Int ("pid" , pid ), zap .Error (err ))
212+ } else {
213+ logger .Info ("Signaled worker process" , zap .Int ("pid" , pid ), zap .String ("signal" , sig .String ()))
214+ p .wg .Done () // Mark worker as done
215+ }
216+ }
163217
164- p .signal ( sig )
218+ p .workers = make ([] int , 0 )
165219}
166220
167221func determineCommand (command string , arg []string ) (string , []string ) {
@@ -173,17 +227,6 @@ func determineCommand(command string, arg []string) (string, []string) {
173227 return command , arg
174228}
175229
176- func determineSignal (serverType string ) (os.Signal , error ) {
177- switch serverType {
178- case "puma" :
179- return syscall .SIGUSR1 , nil
180- case "unicorn" :
181- return syscall .SIGUSR2 , nil
182- default :
183- return nil , fmt .Errorf ("unknown server type: %s" , serverType )
184- }
185- }
186-
187230func fileExists (filePath string ) bool {
188231 if _ , err := os .Stat (filePath ); err == nil {
189232 return true
0 commit comments