Skip to content

Commit 96400a8

Browse files
feat(worker): make maximum consecutive failures configurable (#1692)
1 parent 58fde42 commit 96400a8

14 files changed

Lines changed: 164 additions & 37 deletions

backoff.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (e *exponentialBackoff) recordFailure() bool {
3333
e.backoff = min(e.backoff*2, e.maxBackoff)
3434

3535
e.mu.Unlock()
36-
return e.failureCount >= e.maxConsecutiveFailures
36+
return e.maxConsecutiveFailures != -1 && e.failureCount >= e.maxConsecutiveFailures
3737
}
3838

3939
// wait sleeps for the backoff duration if failureCount is non-zero.

caddy/app.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,13 @@ 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+
workerOpts := []frankenphp.WorkerOption{
118+
frankenphp.WithWorkerEnv(w.Env),
119+
frankenphp.WithWorkerWatchMode(w.Watch),
120+
frankenphp.WithWorkerMaxFailures(w.MaxConsecutiveFailures),
121+
}
122+
123+
opts = append(opts, frankenphp.WithWorkers(w.Name, repl.ReplaceKnown(w.FileName, ""), w.Num, workerOpts...))
118124
}
119125

120126
frankenphp.Shutdown()

caddy/workerconfig.go

Lines changed: 17 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, set to -1 to never panick)
33+
MaxConsecutiveFailures int `json:"max_consecutive_failures,omitempty"`
3234
}
3335

3436
func parseWorkerConfig(d *caddyfile.Dispenser) (workerConfig, error) {
@@ -94,8 +96,22 @@ 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.Atoi(d.Val())
105+
if err != nil {
106+
return wc, err
107+
}
108+
if v < -1 {
109+
return wc, errors.New("max_consecutive_failures must be >= -1")
110+
}
111+
112+
wc.MaxConsecutiveFailures = int(v)
97113
default:
98-
allowedDirectives := "name, file, num, env, watch"
114+
allowedDirectives := "name, file, num, env, watch, max_consecutive_failures"
99115
return wc, wrongSubDirectiveError("worker", allowedDirectives, v)
100116
}
101117
}

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, -1 means the worker will always restart. Default: 6.
7475
}
7576
}
7677
}

docs/fr/config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ L'[option globale](https://caddyserver.com/docs/caddyfile/concepts#global-option
7070
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour régler plusieurs variables d'environnement.
7171
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
7272
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Défaut : chemin absolu du fichier du worker
73+
max_consecutive_failures <num> # Définit le nombre maximum d'échecs consécutifs avant que le worker ne soit considéré comme défaillant, -1 signifie que le worker redémarre toujours. Par défaut : 6.
7374
}
7475
}
7576
}

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`...)

frankenphp_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), *
6666

6767
initOpts := []frankenphp.Option{frankenphp.WithLogger(opts.logger)}
6868
if opts.workerScript != "" {
69-
initOpts = append(initOpts, frankenphp.WithWorkers("workerName", testDataDir+opts.workerScript, opts.nbWorkers, opts.env, opts.watch))
69+
workerOpts := []frankenphp.WorkerOption{
70+
frankenphp.WithWorkerEnv(opts.env),
71+
frankenphp.WithWorkerWatchMode(opts.watch),
72+
}
73+
initOpts = append(initOpts, frankenphp.WithWorkers("workerName", testDataDir+opts.workerScript, opts.nbWorkers, workerOpts...))
7074
}
7175
initOpts = append(initOpts, opts.initOpts...)
7276
if opts.phpIni != nil {

options.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package frankenphp
22

33
import (
4+
"fmt"
45
"log/slog"
56
"time"
67
)
78

9+
// defaultMaxConsecutiveFailures is the default maximum number of consecutive failures before panicking
10+
const defaultMaxConsecutiveFailures = 6
11+
812
// Option instances allow to configure FrankenPHP.
913
type Option func(h *opt) error
1014

15+
// WorkerOption instances allow configuring FrankenPHP worker.
16+
type WorkerOption func(*workerOpt) error
17+
1118
// opt contains the available options.
1219
//
1320
// If you change this, also update the Caddy module and the documentation.
@@ -22,11 +29,12 @@ type opt struct {
2229
}
2330

2431
type workerOpt struct {
25-
name string
26-
fileName string
27-
num int
28-
env PreparedEnv
29-
watch []string
32+
name string
33+
fileName string
34+
num int
35+
env PreparedEnv
36+
watch []string
37+
maxConsecutiveFailures int
3038
}
3139

3240
// WithNumThreads configures the number of PHP threads to start.
@@ -55,9 +63,54 @@ func WithMetrics(m Metrics) Option {
5563
}
5664

5765
// WithWorkers configures the PHP workers to start
58-
func WithWorkers(name string, fileName string, num int, env map[string]string, watch []string) Option {
66+
func WithWorkers(name string, fileName string, num int, options ...WorkerOption) Option {
5967
return func(o *opt) error {
60-
o.workers = append(o.workers, workerOpt{name, fileName, num, PrepareEnv(env), watch})
68+
worker := workerOpt{
69+
name: name,
70+
fileName: fileName,
71+
num: num,
72+
env: PrepareEnv(nil),
73+
watch: []string{},
74+
maxConsecutiveFailures: defaultMaxConsecutiveFailures,
75+
}
76+
77+
for _, option := range options {
78+
if err := option(&worker); err != nil {
79+
return err
80+
}
81+
}
82+
83+
o.workers = append(o.workers, worker)
84+
85+
return nil
86+
}
87+
}
88+
89+
// WithWorkerEnv sets environment variables for the worker
90+
func WithWorkerEnv(env map[string]string) WorkerOption {
91+
return func(w *workerOpt) error {
92+
w.env = PrepareEnv(env)
93+
94+
return nil
95+
}
96+
}
97+
98+
// WithWorkerWatchMode sets directories to watch for file changes
99+
func WithWorkerWatchMode(watch []string) WorkerOption {
100+
return func(w *workerOpt) error {
101+
w.watch = watch
102+
103+
return nil
104+
}
105+
}
106+
107+
// WithWorkerMaxFailures sets the maximum number of consecutive failures before panicking
108+
func WithWorkerMaxFailures(maxFailures int) WorkerOption {
109+
return func(w *workerOpt) error {
110+
if maxFailures < -1 {
111+
return fmt.Errorf("max consecutive failures must be >= -1, got %d", maxFailures)
112+
}
113+
w.maxConsecutiveFailures = maxFailures
61114

62115
return nil
63116
}

phpmainthread_test.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,16 @@ 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,
100+
WithWorkerEnv(map[string]string{"ENV1": "foo"}),
101+
WithWorkerWatchMode([]string{}),
102+
WithWorkerMaxFailures(0),
103+
),
104+
WithWorkers(worker2Name, worker2Path, 1,
105+
WithWorkerEnv(map[string]string{"ENV1": "foo"}),
106+
WithWorkerWatchMode([]string{}),
107+
WithWorkerMaxFailures(0),
108+
),
101109
WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil))),
102110
))
103111

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

183191
func TestReturnAnErrorIf2WorkersHaveTheSameFileName(t *testing.T) {
184192
workers = make(map[string]*worker)
185-
_, err1 := newWorker(workerOpt{fileName: "filename.php"})
186-
_, err2 := newWorker(workerOpt{fileName: "filename.php"})
193+
_, err1 := newWorker(workerOpt{fileName: "filename.php", maxConsecutiveFailures: defaultMaxConsecutiveFailures})
194+
_, err2 := newWorker(workerOpt{fileName: "filename.php", maxConsecutiveFailures: defaultMaxConsecutiveFailures})
187195

188196
assert.NoError(t, err1)
189197
assert.Error(t, err2, "two workers cannot have the same filename")
190198
}
191199

192200
func TestReturnAnErrorIf2ModuleWorkersHaveTheSameName(t *testing.T) {
193201
workers = make(map[string]*worker)
194-
_, err1 := newWorker(workerOpt{fileName: "filename.php", name: "workername"})
195-
_, err2 := newWorker(workerOpt{fileName: "filename2.php", name: "workername"})
202+
_, err1 := newWorker(workerOpt{fileName: "filename.php", name: "workername", maxConsecutiveFailures: defaultMaxConsecutiveFailures})
203+
_, err2 := newWorker(workerOpt{fileName: "filename2.php", name: "workername", maxConsecutiveFailures: defaultMaxConsecutiveFailures})
196204

197205
assert.NoError(t, err1)
198206
assert.Error(t, err2, "two workers cannot have the same name")
@@ -203,8 +211,9 @@ func getDummyWorker(fileName string) *worker {
203211
workers = make(map[string]*worker)
204212
}
205213
worker, _ := newWorker(workerOpt{
206-
fileName: testDataPath + "/" + fileName,
207-
num: 1,
214+
fileName: testDataPath + "/" + fileName,
215+
num: 1,
216+
maxConsecutiveFailures: defaultMaxConsecutiveFailures,
208217
})
209218
return worker
210219
}

0 commit comments

Comments
 (0)