Skip to content

Commit 4e5683c

Browse files
committed
in progress
1 parent 545a022 commit 4e5683c

File tree

2 files changed

+110
-67
lines changed

2 files changed

+110
-67
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ var logger = utils.NewCaddyRailsLogger()
2828

2929
func init() {
3030
caddycmd.RegisterCommand(caddycmd.Command{
31-
Name: "serve-rails",
31+
Name: "serve",
3232
Short: "Runs an external server and sets up a reverse proxy to it",
3333
Long: `
34-
The serve-rails command runs an external server specified as its argument and
34+
The serve command runs an external server specified as its argument and
3535
sets up a reverse proxy to forward requests to it.`,
3636
CobraFunc: func(cmd *cobra.Command) {
3737
cmd.Flags().String("pid-file", "", "Path to the PID file to control an existing process")

internal/utils/upstream_process.go

Lines changed: 108 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import (
88
"os/exec"
99
"os/signal"
1010
"strconv"
11+
"strings"
12+
"sync"
1113
"syscall"
14+
"time"
1215
)
1316

1417
const (
@@ -20,9 +23,12 @@ var logger = NewCaddyRailsLogger()
2023

2124
type 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

2834
func 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

130143
func (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

167221
func 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-
187230
func fileExists(filePath string) bool {
188231
if _, err := os.Stat(filePath); err == nil {
189232
return true

0 commit comments

Comments
 (0)