* Adds filesystem watcher with tests. * Refactoring. * Formatting. * Formatting. * Switches to absolute path in tests. * Fixes race condition from merge conflict. * Fixes race condition. * Fixes tests. * Fixes markdown lint errors. * Switches back to absolute paths. * Reverts back to relative file paths. * Fixes golangci-lint issues. * Uses github.com/dunglas/go-fswatch instead. * Stops watcher before stopping workers. * Updates docs. * Avoids segfault in tests. * Fixes watcher segmentation violations on shutdown. * Adjusts watcher latencies and tests. * Adds fswatch to dockerfiles * Fixes fswatch in alpine. * Fixes segfault (this time for real). * Allows queueing new reload if file changes while workers are reloading. * Makes tests more consistent. * Prevents the watcher from getting stuck if there is an error in the worker file itself. * Reverts changing the image. * Puts fswatch version into docker-bake.hcl. * Asserts instead of panicking. * Adds notice Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update dev.Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update alpine.Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update alpine.Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update dev-alpine.Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update dev-alpine.Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update dev.Dockerfile Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update docs/config.md Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Runs fswatch version. * Removes .json. * Replaces ms with s. * Resets the channel after closing it. * Update watcher_options.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update watcher_test.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Asserts no error instead. * Fixes a race condition where events are fired after frankenphp has stopped. * Updates docs. * Update watcher_options_test.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Allows queuing events while watchers are reloading. * go fmt * Refactors stopping and draining logic. * Allows extended watcher configuration with dirs, recursion, symlinks, case-sensitivity, latency, monitor types and regex. * Updates docs. * Adds TODOS. * go fmt. * Fixes linting errors. * Also allows wildcards in the longform and adjusts docs. * Adds debug log. * Fixes the watcher short form. * Refactors sessions and options into a struct. * Fixes an overflow in the 'workersReadyWG' on unexpected terminations. * Properly logs errors coming from session.Start(). * go fmt. * Adds --nocache. * Fixes lint issue. * Refactors and resolves race condition on worker reload. * Implements debouncing with a timer as suggested by @withinboredom. * Starts watcher even if no workers are defined. * Updates docs with file limit warning. * Adds watch config unit tests. * Adjusts debounce timings. * go fmt. * Adds fswatch to static builder (test). * Adds a short grace period between stopping and destroying the watcher sessions. * Adds caddy test. * Adjusts sleep time. * Swap to edant/watcher. * Fixes watch options and tests. * go fmt. * Adds TODO. * Installs edant/watcher in the bookworm image. * Fixes linting. * Refactors the watcher into its own module. * Adjusts naming. * ADocker image adjustments and refactoring. * Testing installation methods. * Installs via gcc instead. * Fixes pointer formats. * Fixes lint issues. * Fixes arm alpine and updates docs. * Clang format. * Fixes dirs. * Adds watcher version arg. * Uses static lib version. * Adds watcher to tests and sanitizers. * Uses sudo for copying the shared lib. * Removes unnused func. * Refactoring. * Update .github/workflows/sanitizers.yaml Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Adds fpic. * Fixes linting. * Skips tests in msan. * Resets op_cache in every worker thread after termination * Review fixes part 1. * Test: installing libstc++ instead of gcc. * Test: using msan ignorelist. * Test: using msan ignorelist. * Test: using msan ignorelist. * Allows '/**/' for global recursion and '**/' for relative recursion. * Reverts using the ignorelist. * Calls opcache directly. * Adds --watch to php-server command * Properly free CStrings. * Sorts alphabetically and uses curl instead of git. * Labeling and formatting. * Update .github/workflows/sanitizers.yaml Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update .github/workflows/sanitizers.yaml Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update .github/workflows/tests.yaml Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update .github/workflows/tests.yaml Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update caddy/caddy.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update docs/config.md Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update frankenphp_with_watcher_test.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update watcher/watcher.h Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update frankenphp.c Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update watcher/watcher.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update docs/config.md Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update frankenphp_with_watcher_test.go Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update testdata/files/.gitignore Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update watcher/watcher-c.h Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Update watcher/watcher.c Co-authored-by: Kévin Dunglas <kevin@dunglas.fr> * Fixes test and Dockerfile. * Fixes Dockerfiles. * Resets go versions. * Replaces unsafe.pointer with uintptr_t * Prevents worker channels from being destroyed on reload. * Minimizes the public api by only passing a []string. * Adds support for directory patterns and multiple '**' globs. * Adjusts label. * go fmt. * go mod tidy. * Fixes merge conflict. * Refactoring and formatting. * Cleans up unused vars and functions. * Allows dirs with a dot. * Makes test nicer. * Add dir tests. * Moves the watch directive inside the worker directive. * Adds debug log on special events. * Removes line about symlinks. * Hints at multiple possible --watch flags. * Adds ./**/*.php as default watch configuration. * Changes error to a warning. * Changes the default to './**/*.{php,yaml,yml,twig,env}' and supports the {bracket} pattern. * Fixes linting. * Fixes merge conflict and adjust values. * Adjusts values. --------- Co-authored-by: a.stecher <a.stecher@sportradar.com> Co-authored-by: Kévin Dunglas <kevin@dunglas.fr>
5.4 KiB
Using FrankenPHP Workers
Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds.
Starting Worker Scripts
Docker
Set the value of the FRANKENPHP_CONFIG environment variable to worker /path/to/your/worker/script.php:
docker run \
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Standalone Binary
Use the --worker option of the php-server command to serve the content of the current directory using a worker:
./frankenphp php-server --worker /path/to/your/worker/script.php
If your PHP app is embedded in the binary, you can add a custom Caddyfile in the root directory of the app.
It will be used automatically.
It's also possible to restart the worker on file changes with the --watch option.
The following command will trigger a restart if any file ending in .php in the /path/to/your/app/ directory or subdirectories is modified:
./frankenphp php-server --worker /path/to/your/worker/script.php --watch "/path/to/your/app/**/*.php"
Symfony Runtime
The worker mode of FrankenPHP is supported by the Symfony Runtime Component. To start any Symfony application in a worker, install the FrankenPHP package of PHP Runtime:
composer require runtime/frankenphp-symfony
Start your app server by defining the APP_RUNTIME environment variable to use the FrankenPHP Symfony Runtime:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Laravel Octane
See the dedicated documentation.
Custom Apps
The following example shows how to create your own worker script without relying on a third-party library:
<?php
// public/index.php
// Prevent worker script termination when a client connection is interrupted
ignore_user_abort(true);
// Boot your app
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// Handler outside the loop for better performance (doing less work)
$handler = static function () use ($myApp) {
// Called when a request is received,
// superglobals, php://input and the like are reset
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
// Do something after sending the HTTP response
$myApp->terminate();
// Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation
gc_collect_cycles();
if (!$keepRunning) break;
}
// Cleanup
$myApp->shutdown();
Then, start your app and use the FRANKENPHP_CONFIG environment variable to configure your worker:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
By default, 2 workers per CPU are started. You can also configure the number of workers to start:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Restart the Worker After a Certain Number of Requests
As PHP was not originally designed for long-running processes, there are still many libraries and legacy codes that leak memory. A workaround to using this type of code in worker mode is to restart the worker script after processing a certain number of requests:
The previous worker snippet allows configuring a maximum number of request to handle by setting an environment variable named MAX_REQUESTS.
Worker Failures
If a worker script crashes with a non-zero exit code, FrankenPHP will restart it with an exponential backoff strategy.
If the worker script stays up longer than the last backoff * 2,
it will not penalize the worker script and restart it again.
However, if the worker script continues to fail with a non-zero exit code in a short period of time
(for example, having a typo in a script), FrankenPHP will crash with the error: too many consecutive failures.
Superglobals Behavior
PHP superglobals ($_SERVER, $_ENV, $_GET...)
behave as follows:
- before the first call to
frankenphp_handle_request(), superglobals contain values bound to the worker script itself - during and after the call to
frankenphp_handle_request(), superglobals contain values generated from the processed HTTP request, each call tofrankenphp_handle_request()changes the superglobals values
To access the superglobals of the worker script inside the callback, you must copy them and import the copy in the scope of the callback:
<?php
// Copy worker's $_SERVER superglobal before the first call to frankenphp_handle_request()
$workerServer = $_SERVER;
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // Request-bound $_SERVER
var_dump($workerServer); // $_SERVER of the worker script
};
// ...