You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 73dad02
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/config.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -114,6 +114,7 @@ You can also explicitly configure FrankenPHP using the [global option](https://c
114
114
boot_timeout <duration> # EXPERIMENTAL: how long a background worker may take to reach its first tick() before the start fails, 0 waits forever. Default: 30s on PHP builds without Zend max execution timers, none elsewhere.
115
115
background # EXPERIMENTAL: marks this worker as a background (non-HTTP) worker, whose script ticks its FrankenPHP\WorkerHandle at least once
116
116
vars <namespace...> # EXPERIMENTAL: the vars namespaces a background worker publishes, read with frankenphp_get_vars()
117
+
tasks <namespace...> # EXPERIMENTAL: the task namespaces a background worker receives, sent with FrankenPHP\SentTaskHandle
117
118
}
118
119
}
119
120
}
@@ -205,6 +206,7 @@ php_server [<matcher>] {
205
206
boot_timeout <duration> # EXPERIMENTAL: how long a background worker may take to reach its first tick() before the start fails, 0 waits forever. Default: 30s on PHP builds without Zend max execution timers, none elsewhere.
206
207
background # EXPERIMENTAL: marks this worker as a background (non-HTTP) worker, whose script ticks its FrankenPHP\WorkerHandle at least once
207
208
vars <namespace...> # EXPERIMENTAL: the vars namespaces a background worker publishes, read with frankenphp_get_vars()
209
+
tasks <namespace...> # EXPERIMENTAL: the task namespaces a background worker receives, sent with FrankenPHP\SentTaskHandle
208
210
}
209
211
worker <other_file> <num> # Can also use the short form like in the global frankenphp block.
Copy file name to clipboardExpand all lines: docs/metrics.md
+6-4Lines changed: 6 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,13 +16,15 @@ When [Caddy metrics](https://caddyserver.com/docs/metrics) are enabled, FrankenP
16
16
-`frankenphp_busy_threads`: The number of PHP threads currently processing a request (running workers always consume a thread).
17
17
-`frankenphp_queue_depth`: The number of regular queued requests.
18
18
-`frankenphp_total_workers{worker="[worker_name]"}`: The total number of workers.
19
-
-`frankenphp_busy_workers{worker="[worker_name]"}`: The number of workers currently processing a request.
19
+
-`frankenphp_busy_workers{worker="[worker_name]"}`: The number of workers currently processing a request, or a task for a background worker.
20
20
-`frankenphp_worker_request_time{worker="[worker_name]"}`: The time spent processing requests by all workers.
21
21
-`frankenphp_worker_request_count{worker="[worker_name]"}`: The number of requests processed by all workers.
22
22
-`frankenphp_ready_workers{worker="[worker_name]"}`: The number of workers that have reached their ready point at least once: `frankenphp_handle_request()` for HTTP workers, `WorkerHandle::tick()` for background workers.
23
23
-`frankenphp_worker_crashes{worker="[worker_name]"}`: The number of times a worker has unexpectedly terminated.
24
24
-`frankenphp_worker_restarts{worker="[worker_name]"}`: The number of times a worker has been deliberately restarted.
25
-
-`frankenphp_worker_queue_depth{worker="[worker_name]"}`: The number of queued requests.
25
+
-`frankenphp_worker_queue_depth{worker="[worker_name]"}`: The number of queued requests, or of tasks waiting for a thread of a background worker.
26
+
-`frankenphp_worker_task_count{worker="[worker_name]",outcome="[outcome]"}`: The number of tasks sent to a background worker, by outcome: `completed`, `aborted` (the script ended with the task open), `abandoned` (the sender closed its stream first), `timeout` (no thread picked the task up in time) or `cancelled` (the wait for a thread ended on a restart or the shutdown).
27
+
-`frankenphp_worker_task_time{worker="[worker_name]"}`: The time spent on tasks by all threads of a background worker, from pickup to the close of the task's stream.
26
28
-`frankenphp_opcache_restarts{reason="[reason]"}`: (experimental) The number of restarts of opcache's shared memory scheduled, by reason (`oom`, `hash`, `manual`). PHP 8.4 and up.
27
29
28
30
For worker metrics, the `[worker_name]` placeholder is replaced by the worker name in the Caddyfile, otherwise the absolute path of the worker file will be used.
@@ -76,8 +78,8 @@ Each entry in `ThreadDebugStates` contains:
76
78
|`Index`| integer | The index of the thread. |
77
79
|`Name`| string | The name of the thread (e.g., the worker file path). |
78
80
|`State`| string | The internal state of the thread (e.g., `ready`, `shutting down`). |
79
-
|`IsWaiting`| boolean | Whether the thread is waiting for a request. |
80
-
|`IsBusy`| boolean | Whether the thread is currently processing a request. |
81
+
|`IsWaiting`| boolean | Whether the thread is waiting for a request, or for a task in a background worker. |
82
+
|`IsBusy`| boolean | Whether the thread is currently processing a request, or a task in a background worker. |
81
83
|`WaitingSinceMilliseconds`| integer | How long the thread has been idle, in milliseconds. `0` if the thread is busy. |
82
84
|`CurrentURI`| string | The URI currently being processed. Empty if the thread is idle. |
83
85
|`CurrentMethod`| string | The HTTP method of the current request (e.g., `GET`, `POST`). Empty if the thread is idle. |
Copy file name to clipboardExpand all lines: docs/worker.md
+56Lines changed: 56 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -310,6 +310,62 @@ Values must be null, scalars, arrays or enums. Each call replaces the whole snap
310
310
311
311
`frankenphp_get_vars()` can only block while workers boot, a background worker or the bootstrap of an HTTP worker waiting for the publisher of a namespace. A cycle between background workers throws instead of hanging, and so does a wait once the start failed. It also throws when no worker publishes the namespace.
312
312
313
+
### Sending tasks to background workers
314
+
315
+
A background worker receives the task namespaces declared with `tasks` on it, and a request, an HTTP worker or another background worker hands work to it by constructing a `FrankenPHP\SentTaskHandle` with one of those namespaces, resolved the way `frankenphp_get_vars()` resolves vars: within the `php_server` first, then among global workers.
316
+
317
+
```caddyfile
318
+
php_server {
319
+
worker {
320
+
file images.php
321
+
background
322
+
num 4
323
+
tasks image.resize image.thumbnail
324
+
}
325
+
}
326
+
```
327
+
328
+
A namespace has one receiving worker per scope, and the threads of that worker share one queue for all its namespaces, so a pool is sized for the work it takes on, and `getNamespace()` on a received task tells which namespace it was sent to. The payload follows the same rules as `setVars()`: null, scalars, arrays or enums. Constructing blocks until a thread of the worker picks the task up and throws if none did before the timeout, so a busy worker pushes back on its senders instead of queueing without bounds. `read()` then blocks for the next update and returns `null` once the worker completed the task, and `abandon()`, like dropping the handle, gives up on the task. Waiting on a task, alone or with others, goes through a context as it does for the worker's handle, or through `getStream()` for a library that takes a stream: reading that stream steals the signal `read()` needs, writing to it goes nowhere, and closing it ends the task on that side.
329
+
330
+
On the worker side, each task sent wakes one parked thread of the worker through its handle, in the loop of the previous section: `tick()` consumes the wake-up, which is not a count and in a pool may belong to a task a sibling thread took. `receive()` dequeues a task without blocking, a `FrankenPHP\ReceivedTaskHandle`, or `null` when another thread of the pool got there first, so the example below drains the queue on each wake-up and treats `null` as the normal outcome. A thread that ticks while tasks are queued finds its handle readable at once, whichever loop shape it uses. `update()` sends progress back, `complete()` ends the task with a last update when one is given, and a script that ends with a task still open, a `complete()` from a destructor or a shutdown function at that point included, makes the sender's next `read()` throw. When the sender gives up instead, the task's side of the worker becomes readable and its stream reaches EOF, so a context or a `feof()` tells a long task that nobody waits for its result, and `update()` throws.
331
+
332
+
```php
333
+
// background worker, in the loop of the previous section
// request, HTTP worker or another background worker
345
+
$task = new FrankenPHP\SentTaskHandle('image.resize', ['file' => 'photo.jpg']);
346
+
while (null !== $update = $task->read()) {
347
+
// ['progress' => 50], then ['result' => ...]
348
+
}
349
+
```
350
+
351
+
Both task handles are `Io\Poll\Handle` as well, so a context follows several tasks at once, and a script that never asks for a stream has none built for it:
352
+
353
+
```php
354
+
use Io\Poll\{Context, Event};
355
+
356
+
$poll = new Context();
357
+
$task = new FrankenPHP\SentTaskHandle('image.resize', ['file' => 'photo.jpg']);
358
+
$poll->add($task, [Event::Read], 'photo');
359
+
360
+
foreach ($poll->wait() as $watcher) {
361
+
$update = $task->read();
362
+
}
363
+
```
364
+
365
+
`getStream()` is there for the libraries that take a stream, and `stream_select()` or a blocking read work on it as they do on the worker's handle.
366
+
367
+
Sixteen updates are buffered per task; past that, `update()` waits for the sender to read, and it throws once the sender gave up. The channel of a task is a pair of eventfd descriptors on Linux and of kqueue descriptors on macOS, pooled between tasks, and a socket pair elsewhere.
0 commit comments