Skip to content

Commit eef1c5c

Browse files
feat(worker): make maximum consecutive failures configurable
1 parent abfd893 commit eef1c5c

10 files changed

Lines changed: 84 additions & 33 deletions

File tree

caddy/app.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ func (f *FrankenPHPApp) Start() error {
114114
frankenphp.WithMaxWaitTime(f.MaxWaitTime),
115115
}
116116
for _, w := range append(f.Workers) {
117-
opts = append(opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, w.Env, w.Watch))
117+
maxFailures := w.MaxConsecutiveFailures
118+
if maxFailures <= 0 {
119+
maxFailures = frankenphp.DefaultMaxConsecutiveFailures
120+
}
121+
opts = append(opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, w.Env, w.Watch, maxFailures))
118122
}
119123

120124
frankenphp.Shutdown()

caddy/workerconfig.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type workerConfig struct {
2929
Env map[string]string `json:"env,omitempty"`
3030
// Directories to watch for file changes
3131
Watch []string `json:"watch,omitempty"`
32+
// MaxConsecutiveFailures sets the maximum number of consecutive failures before panicking (defaults to 6)
33+
MaxConsecutiveFailures int `json:"max_consecutive_failures,omitempty"`
3234
}
3335

3436
func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
@@ -94,8 +96,19 @@ func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
9496
} else {
9597
wc.Watch = append(wc.Watch, d.Val())
9698
}
99+
case "max_consecutive_failures":
100+
if !d.NextArg() {
101+
return wc, d.ArgErr()
102+
}
103+
104+
v, err := strconv.ParseUint(d.Val(), 10, 32)
105+
if err != nil {
106+
return wc, err
107+
}
108+
109+
wc.MaxConsecutiveFailures = int(v)
97110
default:
98-
allowedDirectives := "name, file, num, env, watch"
111+
allowedDirectives := "name, file, num, env, watch, max_consecutive_failures"
99112
return wc, wrongSubDirectiveError("worker", allowedDirectives, v)
100113
}
101114
}

docs/config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ The `frankenphp` [global option](https://caddyserver.com/docs/caddyfile/concepts
7171
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
7272
watch <path> # Sets the path to watch for file changes. Can be specified more than once for multiple paths.
7373
name <name> # Sets the name of the worker, used in logs and metrics. Default: absolute path of worker file
74+
max_consecutive_failures <num> # Sets the maximum number of consecutive failures before the worker is considered unhealthy. Default: 6.
7475
}
7576
}
7677
}

docs/fr/worker.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@ Si le script worker reste en place plus longtemps que le dernier backoff \* 2, F
150150
Toutefois, si le script de worker continue d'échouer avec un code de sortie non nul dans un court laps de temps
151151
(par exemple, une faute de frappe dans un script), FrankenPHP plantera avec l'erreur : `too many consecutive failures` (trop d'échecs consécutifs).
152152

153+
Le nombre d'échecs consécutifs peut être configuré dans votre [Caddyfile](config.md#configuration-du-caddyfile) avec l'option `max_consecutive_failures` :
154+
155+
```caddyfile
156+
frankenphp {
157+
worker {
158+
# ...
159+
max_consecutive_failures 10
160+
}
161+
}
162+
```
163+
153164
## Comportement des superglobales
154165

155166
[Les superglobales PHP](https://www.php.net/manual/fr/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...)

docs/worker.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@ it will not penalize the worker script and restart it again.
146146
However, if the worker script continues to fail with a non-zero exit code in a short period of time
147147
(for example, having a typo in a script), FrankenPHP will crash with the error: `too many consecutive failures`.
148148

149+
The number of consecutive failures can be configured in your [Caddyfile](config.md#caddyfile-config) with the `max_consecutive_failures` option:
150+
151+
```caddyfile
152+
frankenphp {
153+
worker {
154+
# ...
155+
max_consecutive_failures 10
156+
}
157+
}
158+
```
159+
160+
```caddyfile
161+
149162
## Superglobals Behavior
150163
151164
[PHP superglobals](https://www.php.net/manual/en/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...)

options.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"time"
66
)
77

8+
// DefaultMaxConsecutiveFailures is the default maximum number of consecutive failures before panicking
9+
const DefaultMaxConsecutiveFailures = 6
10+
811
// Option instances allow to configure FrankenPHP.
912
type Option func(h *opt) error
1013

@@ -22,11 +25,12 @@ type opt struct {
2225
}
2326

2427
type workerOpt struct {
25-
name string
26-
fileName string
27-
num int
28-
env PreparedEnv
29-
watch []string
28+
name string
29+
fileName string
30+
num int
31+
env PreparedEnv
32+
watch []string
33+
maxConsecutiveFailures int
3034
}
3135

3236
// WithNumThreads configures the number of PHP threads to start.
@@ -55,9 +59,12 @@ func WithMetrics(m Metrics) Option {
5559
}
5660

5761
// WithWorkers configures the PHP workers to start
58-
func WithWorkers(name string, fileName string, num int, env map[string]string, watch []string) Option {
62+
func WithWorkers(name string, fileName string, num int, env map[string]string, watch []string, maxConsecutiveFailures int) Option {
5963
return func(o *opt) error {
60-
o.workers = append(o.workers, workerOpt{name, fileName, num, PrepareEnv(env), watch})
64+
if maxConsecutiveFailures <= 0 {
65+
maxConsecutiveFailures = DefaultMaxConsecutiveFailures
66+
}
67+
o.workers = append(o.workers, workerOpt{name, fileName, num, PrepareEnv(env), watch, maxConsecutiveFailures})
6168

6269
return nil
6370
}

phpmainthread_test.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) {
9696

9797
assert.NoError(t, Init(
9898
WithNumThreads(numThreads),
99-
WithWorkers(worker1Name, worker1Path, 1, map[string]string{"ENV1": "foo"}, []string{}),
100-
WithWorkers(worker2Name, worker2Path, 1, map[string]string{"ENV1": "foo"}, []string{}),
99+
WithWorkers(worker1Name, worker1Path, 1, map[string]string{"ENV1": "foo"}, []string{}, 0),
100+
WithWorkers(worker2Name, worker2Path, 1, map[string]string{"ENV1": "foo"}, []string{}, 0),
101101
WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil))),
102102
))
103103

@@ -182,17 +182,17 @@ func TestFinishBootingAWorkerScript(t *testing.T) {
182182

183183
func TestReturnAnErrorIf2WorkersHaveTheSameFileName(t *testing.T) {
184184
workers = make(map[string]*worker)
185-
_, err1 := newWorker(workerOpt{fileName: "filename.php"})
186-
_, err2 := newWorker(workerOpt{fileName: "filename.php"})
185+
_, err1 := newWorker(workerOpt{fileName: "filename.php", maxConsecutiveFailures: 0})
186+
_, err2 := newWorker(workerOpt{fileName: "filename.php", maxConsecutiveFailures: 0})
187187

188188
assert.NoError(t, err1)
189189
assert.Error(t, err2, "two workers cannot have the same filename")
190190
}
191191

192192
func TestReturnAnErrorIf2ModuleWorkersHaveTheSameName(t *testing.T) {
193193
workers = make(map[string]*worker)
194-
_, err1 := newWorker(workerOpt{fileName: "filename.php", name: "workername"})
195-
_, err2 := newWorker(workerOpt{fileName: "filename2.php", name: "workername"})
194+
_, err1 := newWorker(workerOpt{fileName: "filename.php", name: "workername", maxConsecutiveFailures: 0})
195+
_, err2 := newWorker(workerOpt{fileName: "filename2.php", name: "workername", maxConsecutiveFailures: 0})
196196

197197
assert.NoError(t, err1)
198198
assert.Error(t, err2, "two workers cannot have the same name")
@@ -203,8 +203,9 @@ func getDummyWorker(fileName string) *worker {
203203
workers = make(map[string]*worker)
204204
}
205205
worker, _ := newWorker(workerOpt{
206-
fileName: testDataPath + "/" + fileName,
207-
num: 1,
206+
fileName: testDataPath + "/" + fileName,
207+
num: 1,
208+
maxConsecutiveFailures: 0,
208209
})
209210
return worker
210211
}

scaling_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestScaleAWorkerThreadUpAndDown(t *testing.T) {
3737
assert.NoError(t, Init(
3838
WithNumThreads(2),
3939
WithMaxThreads(3),
40-
WithWorkers(workerName, workerPath, 1, map[string]string{}, []string{}),
40+
WithWorkers(workerName, workerPath, 1, map[string]string{}, []string{}, 0),
4141
WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil))),
4242
))
4343

threadworker.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func convertToWorkerThread(thread *phpThread, worker *worker) {
3030
backoff: &exponentialBackoff{
3131
maxBackoff: 1 * time.Second,
3232
minBackoff: 100 * time.Millisecond,
33-
maxConsecutiveFailures: 6,
33+
maxConsecutiveFailures: worker.maxConsecutiveFailures,
3434
},
3535
})
3636
worker.attachThread(thread)
@@ -116,7 +116,6 @@ func tearDownWorkerScript(handler *workerThread, exitStatus int) {
116116

117117
// on exit status 0 we just run the worker script again
118118
if exitStatus == 0 && !handler.isBootingScript {
119-
// TODO: make the max restart configurable
120119
metrics.StopWorker(worker.name, StopReasonRestart)
121120
handler.backoff.recordSuccess()
122121
logger.LogAttrs(ctx, slog.LevelDebug, "restarting", slog.String("worker", worker.name), slog.Int("thread", handler.thread.threadIndex), slog.Int("exit_status", exitStatus))

worker.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import (
1414

1515
// represents a worker script and can have many threads assigned to it
1616
type worker struct {
17-
name string
18-
fileName string
19-
num int
20-
env PreparedEnv
21-
requestChan chan *frankenPHPContext
22-
threads []*phpThread
23-
threadMutex sync.RWMutex
17+
name string
18+
fileName string
19+
num int
20+
env PreparedEnv
21+
requestChan chan *frankenPHPContext
22+
threads []*phpThread
23+
threadMutex sync.RWMutex
24+
maxConsecutiveFailures int
2425
}
2526

2627
var (
@@ -99,12 +100,13 @@ func newWorker(o workerOpt) (*worker, error) {
99100

100101
o.env["FRANKENPHP_WORKER\x00"] = "1"
101102
w := &worker{
102-
name: o.name,
103-
fileName: absFileName,
104-
num: o.num,
105-
env: o.env,
106-
requestChan: make(chan *frankenPHPContext),
107-
threads: make([]*phpThread, 0, o.num),
103+
name: o.name,
104+
fileName: absFileName,
105+
num: o.num,
106+
env: o.env,
107+
requestChan: make(chan *frankenPHPContext),
108+
threads: make([]*phpThread, 0, o.num),
109+
maxConsecutiveFailures: o.maxConsecutiveFailures,
108110
}
109111
workers[key] = w
110112

0 commit comments

Comments
 (0)