Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c77319c
feat: background workers and FrankenPHP\WorkerHandle
nicolas-grekas Sep 6, 2026
47da570
feat: a background worker no longer needs a name
nicolas-grekas Oct 27, 2026
24185e3
refactor: worker names, registries and metrics labels are main's again
nicolas-grekas Oct 27, 2026
8180d15
refactor: background threads count like the threads of any worker
nicolas-grekas Oct 27, 2026
d13828c
refactor: leave the boot timeout to a pull request of its own
nicolas-grekas Oct 27, 2026
ae431fa
chore: drop the leftovers of FRANKENPHP_WORKER holding the worker name
nicolas-grekas Oct 27, 2026
bcff211
feat: bound the wait for a background worker to become ready
nicolas-grekas Oct 27, 2026
732718d
test: expect the generated name as var_export() writes a Windows path
nicolas-grekas Oct 27, 2026
486940a
fix: a boot timeout stops its thread and never blocks Init()
nicolas-grekas Oct 27, 2026
f94e054
feat: frankenphp_get_vars() and WorkerHandle::setVars()
nicolas-grekas Sep 6, 2026
1a61637
feat: vars are addressed by namespace, declared on the worker that pu…
nicolas-grekas Oct 27, 2026
1d8594a
fix: a vars read survives a bailout and never hands out a shared string
nicolas-grekas Oct 27, 2026
f05fb05
test: bail out of a vars copy without relying on memory_limit
nicolas-grekas Oct 27, 2026
d6735cd
feat: SentTaskHandle and ReceivedTaskHandle
nicolas-grekas Sep 19, 2026
621a1b5
feat: tasks are sent to a namespace, declared on the worker that rece…
nicolas-grekas Oct 27, 2026
61d9559
fix: a task handle is constructed once, and cancelled tasks are counted
nicolas-grekas Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
746 changes: 746 additions & 0 deletions bgworker_test.go

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions caddy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,13 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if len(wc.MatchPath) != 0 {
return d.Errf(`"match" can only be used in a php_server worker block, not in a global one: %q`, wc.FileName)
}
// check for duplicate workers
for _, existingWorker := range f.Workers {
if existingWorker.FileName == wc.FileName {
return d.Errf("global workers must not have duplicate filenames: %q", wc.FileName)
// check for duplicate workers; background workers are not
// matched by path, several may share a script
if !wc.Background {
for _, existingWorker := range f.Workers {
if !existingWorker.Background && existingWorker.FileName == wc.FileName {
return d.Errf("global workers must not have duplicate filenames: %q", wc.FileName)
}
}
}

Expand Down
60 changes: 50 additions & 10 deletions caddy/caddy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,45 @@ func TestMetrics(t *testing.T) {
require.NoError(t, testutil.GatherAndCompare(ctx.GetMetricsRegistry(), strings.NewReader(expectedMetrics), "frankenphp_total_threads", "frankenphp_busy_threads"))
}

// TestBackgroundWorkerFromCaddyfile starts a background worker from a
// Caddyfile and checks it runs: the sentinel its script touches appears
func TestBackgroundWorkerFromCaddyfile(t *testing.T) {
sentinel := filepath.ToSlash(filepath.Join(t.TempDir(), "bg.sentinel"))
tester := caddytest.NewTester(t)
initServer(t, tester, `
{
skip_install_trust
admin localhost:2999
http_port `+testPort+`
https_port 9443

frankenphp {
worker {
file ../testdata/bgworker/basic.php
num 1
name bg-caddy
background
env BG_SENTINEL `+sentinel+`
}
}
}

localhost:`+testPort+` {
route {
php {
root ../testdata
}
}
}
`, "caddyfile")

require.Eventually(t, func() bool {
_, err := os.Stat(sentinel)

return err == nil
}, 5*time.Second, 25*time.Millisecond, "the background worker declared in the Caddyfile did not run")
}

func TestWorkerMetrics(t *testing.T) {
var wg sync.WaitGroup
tester := caddytest.NewTester(t)
Expand Down Expand Up @@ -858,7 +897,7 @@ func TestWorkerMetrics(t *testing.T) {
# TYPE frankenphp_busy_threads gauge
frankenphp_busy_threads 2

# HELP frankenphp_busy_workers Number of busy PHP workers for this worker
# HELP frankenphp_busy_workers Number of busy PHP workers for this worker: processing a request, or a task for a background worker
# TYPE frankenphp_busy_workers gauge
frankenphp_busy_workers{worker="` + workerName + `"} 0

Expand All @@ -870,7 +909,7 @@ func TestWorkerMetrics(t *testing.T) {
# TYPE frankenphp_worker_request_count counter
frankenphp_worker_request_count{worker="` + workerName + `"} 10

# HELP frankenphp_ready_workers Running workers that have successfully called frankenphp_handle_request at least once
# HELP frankenphp_ready_workers Running workers that have reached their ready point at least once: frankenphp_handle_request for HTTP workers, WorkerHandle::tick() for background workers
# TYPE frankenphp_ready_workers gauge
frankenphp_ready_workers{worker="` + workerName + `"} 2
`
Expand Down Expand Up @@ -1015,7 +1054,7 @@ func TestNamedWorkerMetrics(t *testing.T) {
# TYPE frankenphp_busy_threads gauge
frankenphp_busy_threads 2

# HELP frankenphp_busy_workers Number of busy PHP workers for this worker
# HELP frankenphp_busy_workers Number of busy PHP workers for this worker: processing a request, or a task for a background worker
# TYPE frankenphp_busy_workers gauge
frankenphp_busy_workers{worker="my_app"} 0

Expand All @@ -1027,7 +1066,7 @@ func TestNamedWorkerMetrics(t *testing.T) {
# TYPE frankenphp_worker_request_count counter
frankenphp_worker_request_count{worker="my_app"} 10

# HELP frankenphp_ready_workers Running workers that have successfully called frankenphp_handle_request at least once
# HELP frankenphp_ready_workers Running workers that have reached their ready point at least once: frankenphp_handle_request for HTTP workers, WorkerHandle::tick() for background workers
# TYPE frankenphp_ready_workers gauge
frankenphp_ready_workers{worker="my_app"} 2
`
Expand Down Expand Up @@ -1111,7 +1150,7 @@ func TestAutoWorkerConfig(t *testing.T) {
# TYPE frankenphp_busy_threads gauge
frankenphp_busy_threads ` + workers + `

# HELP frankenphp_busy_workers Number of busy PHP workers for this worker
# HELP frankenphp_busy_workers Number of busy PHP workers for this worker: processing a request, or a task for a background worker
# TYPE frankenphp_busy_workers gauge
frankenphp_busy_workers{worker="` + workerName + `"} 0

Expand All @@ -1123,7 +1162,7 @@ func TestAutoWorkerConfig(t *testing.T) {
# TYPE frankenphp_worker_request_count counter
frankenphp_worker_request_count{worker="` + workerName + `"} 10

# HELP frankenphp_ready_workers Running workers that have successfully called frankenphp_handle_request at least once
# HELP frankenphp_ready_workers Running workers that have reached their ready point at least once: frankenphp_handle_request for HTTP workers, WorkerHandle::tick() for background workers
# TYPE frankenphp_ready_workers gauge
frankenphp_ready_workers{worker="` + workerName + `"} ` + workers + `
`
Expand Down Expand Up @@ -1385,6 +1424,7 @@ func TestMaxWaitTimeWorker(t *testing.T) {
require.NoError(t, err)

expectedMetrics := `
# HELP frankenphp_worker_queue_depth Number of queued requests for this worker, or of tasks waiting for a thread of a background worker
# TYPE frankenphp_worker_queue_depth gauge
frankenphp_worker_queue_depth{worker="service"} 0
`
Expand Down Expand Up @@ -1485,7 +1525,7 @@ func TestMultiWorkersMetrics(t *testing.T) {
# TYPE frankenphp_busy_threads gauge
frankenphp_busy_threads 5

# HELP frankenphp_busy_workers Number of busy PHP workers for this worker
# HELP frankenphp_busy_workers Number of busy PHP workers for this worker: processing a request, or a task for a background worker
# TYPE frankenphp_busy_workers gauge
frankenphp_busy_workers{worker="service1"} 0

Expand All @@ -1498,7 +1538,7 @@ func TestMultiWorkersMetrics(t *testing.T) {
# TYPE frankenphp_worker_request_count counter
frankenphp_worker_request_count{worker="service1"} 10

# HELP frankenphp_ready_workers Running workers that have successfully called frankenphp_handle_request at least once
# HELP frankenphp_ready_workers Running workers that have reached their ready point at least once: frankenphp_handle_request for HTTP workers, WorkerHandle::tick() for background workers
# TYPE frankenphp_ready_workers gauge
frankenphp_ready_workers{worker="service1"} 2
frankenphp_ready_workers{worker="service2"} 3
Expand Down Expand Up @@ -1652,7 +1692,7 @@ func TestWorkerRestart(t *testing.T) {

// Check metrics
expectedMetrics := `
# HELP frankenphp_ready_workers Running workers that have successfully called frankenphp_handle_request at least once
# HELP frankenphp_ready_workers Running workers that have reached their ready point at least once: frankenphp_handle_request for HTTP workers, WorkerHandle::tick() for background workers
# TYPE frankenphp_ready_workers gauge
frankenphp_ready_workers{worker="service"} 1
# HELP frankenphp_total_workers Total number of PHP workers for this worker
Expand Down Expand Up @@ -1680,7 +1720,7 @@ func TestWorkerRestart(t *testing.T) {

// frankenphp_ready_workers should be back to 1 even after worker restarts
expectedMetrics = `
# HELP frankenphp_ready_workers Running workers that have successfully called frankenphp_handle_request at least once
# HELP frankenphp_ready_workers Running workers that have reached their ready point at least once: frankenphp_handle_request for HTTP workers, WorkerHandle::tick() for background workers
# TYPE frankenphp_ready_workers gauge
frankenphp_ready_workers{worker="service"} 1
# HELP frankenphp_total_workers Total number of PHP workers for this worker
Expand Down
Loading
Loading