Skip to content

Commit 5b84690

Browse files
docs+test: clarify non-worker mode cost, add edge-case tests
Docs: "same behavior every request" wording was misleading. In non-worker mode, only the first request to a given name pays the lazy-start cost; subsequent calls see the worker already reserved and return almost immediately. Tests: add coverage for three edge cases of frankenphp_require_background_worker(): - Empty name -> ValueError - Negative timeout -> ValueError - timeout=0 -> must not hang (returns promptly, any error flavor)
1 parent f5babb0 commit 5b84690

3 files changed

Lines changed: 61 additions & 1 deletion

File tree

docs/background-workers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Declares a dependency on a background worker. Behavior depends on the caller con
4848

4949
- **In an HTTP worker script, before `frankenphp_handle_request()` (bootstrap)**: lazy-starts the worker (at-most-once) if not already running, blocks until it has called `set_vars()` at least once. Fails fast on boot failure (no exponential-backoff tolerance): if the worker's first boot attempts fail, the exception is thrown right away with the captured details. Use this to declare dependencies up front.
5050
- **In an HTTP worker script, inside `frankenphp_handle_request()` (runtime)**: assert-only. The worker must already be running (declared with `num 1` in Caddyfile or previously required during bootstrap). Never lazy-starts. Throws immediately if the name isn't known. Use this to assert a dependency without starting anything.
51-
- **In non-worker mode (classic request-per-process)**: lazy-starts the worker and waits up to `$timeout`, tolerating transient boot failures via exponential backoff. Same behavior every request.
51+
- **In non-worker mode (classic request-per-process)**: lazy-starts the worker and waits up to `$timeout`, tolerating transient boot failures via exponential backoff. The first request to a given name pays the startup cost; subsequent requests in the same FrankenPHP process see the worker already reserved and return almost immediately.
5252

5353
```php
5454
// HTTP worker (bootstrap)

frankenphp_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,28 @@ func TestBackgroundWorkerBootFailureError(t *testing.T) {
10091009
})
10101010
}
10111011

1012+
func TestBackgroundWorkerRequireEdgeCases(t *testing.T) {
1013+
cwd, _ := os.Getwd()
1014+
entrypoint := cwd + "/testdata/background-worker-with-argv.php"
1015+
1016+
runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) {
1017+
body, _ := testGet("http://example.com/background-worker-require-edge-cases.php", handler, t)
1018+
// Empty name and negative timeout are argument validation errors.
1019+
assert.Contains(t, body, "EMPTY_NAME:value_error")
1020+
assert.Contains(t, body, "NEG_TIMEOUT:value_error")
1021+
// timeout=0 must not hang; we accept any error, we just want quick return.
1022+
assert.Contains(t, body, "ZERO_TIMEOUT:threw_in_under_half_sec")
1023+
}, &testOptions{
1024+
workerScript: "background-worker-require-edge-cases.php",
1025+
nbWorkers: 1,
1026+
nbParallelRequests: 1,
1027+
initOpts: []frankenphp.Option{
1028+
frankenphp.WithMaxThreads(50),
1029+
frankenphp.WithWorkers("", entrypoint, 0, frankenphp.WithWorkerBackground()),
1030+
},
1031+
})
1032+
}
1033+
10121034
func TestBackgroundWorkerRuntimeRequireAssertsOnly(t *testing.T) {
10131035
cwd, _ := os.Getwd()
10141036
entrypoint := cwd + "/testdata/background-worker-with-argv.php"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
frankenphp_handle_request(function () {
4+
$results = [];
5+
6+
// Empty name rejected with ValueError.
7+
try {
8+
frankenphp_require_background_worker('');
9+
$results[] = 'EMPTY_NAME:no_error';
10+
} catch (\ValueError $e) {
11+
$results[] = 'EMPTY_NAME:value_error';
12+
} catch (\Throwable $e) {
13+
$results[] = 'EMPTY_NAME:other:' . get_class($e);
14+
}
15+
16+
// Negative timeout rejected with ValueError.
17+
try {
18+
frankenphp_require_background_worker('some-worker', -1.0);
19+
$results[] = 'NEG_TIMEOUT:no_error';
20+
} catch (\ValueError $e) {
21+
$results[] = 'NEG_TIMEOUT:value_error';
22+
} catch (\Throwable $e) {
23+
$results[] = 'NEG_TIMEOUT:other:' . get_class($e);
24+
}
25+
26+
// timeout=0 must not hang. In runtime mode this throws 'not running' for
27+
// an unknown worker; the key invariant is that the call returns promptly.
28+
$start = microtime(true);
29+
try {
30+
frankenphp_require_background_worker('never-required-zero', 0.0);
31+
$results[] = 'ZERO_TIMEOUT:no_error';
32+
} catch (\Throwable $e) {
33+
$elapsed = microtime(true) - $start;
34+
$results[] = 'ZERO_TIMEOUT:threw_in_' . ($elapsed < 0.5 ? 'under_half_sec' : 'over_half_sec');
35+
}
36+
37+
echo implode("\n", $results);
38+
});

0 commit comments

Comments
 (0)