diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 260d076c3a..0a0cf26a1b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,7 @@ # Contributing +For an overview of FrankenPHP's architecture (thread types, state machine, CGO boundary, request flow), see the [Internals documentation](docs/internals.md). + ## Compiling PHP ### With Docker (Linux) @@ -299,7 +301,7 @@ The steps assume the following environment: 4. Debug Go files from CLion - - Right click on a *.go file in the Project view on the left + - Right click on a \*.go file in the Project view on the left - Override file type → C/C++ Now you can place breakpoints in C, C++ and Go files. @@ -348,7 +350,7 @@ Use GoLand for primary Go development, but the debugger cannot debug C code. To debug C files from GoLand -- Right click on a *.c file in the Project view on the left +- Right click on a \*.c file in the Project view on the left - Override file type → Go Now you can place breakpoints in C, C++ and Go files. diff --git a/README.md b/README.md index 4e09c329c3..dd0e7f9767 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ Go to `https://localhost`, and enjoy! - [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo) - [Go library documentation](https://pkg.go.dev/github.com/dunglas/frankenphp) - [Contributing and debugging](https://frankenphp.dev/docs/contributing/) +- [Internals (architecture overview)](docs/internals.md) ## Examples and Skeletons diff --git a/docs/cn/extension-workers.md b/docs/cn/extension-workers.md index 2c2d0c645a..98cef730ef 100644 --- a/docs/cn/extension-workers.md +++ b/docs/cn/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP 提供了钩子,用于在生命周期的特定点执行 Go 代码。 -| 钩子类型 | 选项名称 | 签名 | 上下文与用例 | -| :------- | :--------------------------- | :----------------------- | :--------------------------------------------------- | -| **服务器** | `WithWorkerOnServerStartup` | `func()` | 全局设置。**只运行一次**。示例:连接到 NATS/Redis。 | -| **服务器** | `WithWorkerOnServerShutdown` | `func()` | 全局清理。**只运行一次**。示例:关闭共享连接。 | -| **线程** | `WithWorkerOnReady` | `func(threadID int)` | 每线程设置。在线程启动时调用。接收线程 ID。 | -| **线程** | `WithWorkerOnShutdown` | `func(threadID int)` | 每线程清理。接收线程 ID。 | +| 钩子类型 | 选项名称 | 签名 | 上下文与用例 | +| :--------- | :--------------------------- | :------------------- | :-------------------------------------------------- | +| **服务器** | `WithWorkerOnServerStartup` | `func()` | 全局设置。**只运行一次**。示例:连接到 NATS/Redis。 | +| **服务器** | `WithWorkerOnServerShutdown` | `func()` | 全局清理。**只运行一次**。示例:关闭共享连接。 | +| **线程** | `WithWorkerOnReady` | `func(threadID int)` | 每线程设置。在线程启动时调用。接收线程 ID。 | +| **线程** | `WithWorkerOnShutdown` | `func(threadID int)` | 每线程清理。接收线程 ID。 | ### 示例 diff --git a/docs/cn/hot-reload.md b/docs/cn/hot-reload.md index e83a6e5f36..ffca766dbe 100644 --- a/docs/cn/hot-reload.md +++ b/docs/cn/hot-reload.md @@ -25,7 +25,7 @@ FrankenPHP 包含一个内置的**热重载**功能,旨在极大改善开发 > > 此功能仅适用于**开发环境**。 > 请勿在生产环境中启用 `hot_reload`,因为此功能不安全(会暴露敏感的内部细节)并且会降低应用程序的速度。 -> + ```caddyfile localhost @@ -145,5 +145,5 @@ php_server { 4. **接收**:浏览器通过 JavaScript 库监听,接收 Mercure 事件。 5. **更新**: - - 如果检测到 **Idiomorph**,它会获取更新的内容并修改当前的 HTML 以匹配新状态,即时应用更改而不会丢失状态。 - - 否则,将调用 `window.location.reload()` 来刷新页面。 + - 如果检测到 **Idiomorph**,它会获取更新的内容并修改当前的 HTML 以匹配新状态,即时应用更改而不会丢失状态。 + - 否则,将调用 `window.location.reload()` 来刷新页面。 diff --git a/docs/cn/worker.md b/docs/cn/worker.md index 33a040bbac..f39dd4a9f1 100644 --- a/docs/cn/worker.md +++ b/docs/cn/worker.md @@ -184,3 +184,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/es/classic.md b/docs/es/classic.md index 1176723ac3..154daeaf9f 100644 --- a/docs/es/classic.md +++ b/docs/es/classic.md @@ -3,9 +3,9 @@ Sin ninguna configuración adicional, FrankenPHP opera en modo clásico. En este modo, FrankenPHP funciona como un servidor PHP tradicional, sirviendo directamente archivos PHP. Esto lo convierte en un reemplazo directo para PHP-FPM o Apache con mod_php. Al igual que Caddy, FrankenPHP acepta un número ilimitado de conexiones y utiliza un [número fijo de hilos](config.md#caddyfile-config) para atenderlas. La cantidad de conexiones aceptadas y en cola está limitada únicamente por los recursos disponibles del sistema. -El *pool* de hilos de PHP opera con un número fijo de hilos inicializados al inicio, comparable al modo estático de PHP-FPM. También es posible permitir que los hilos [escale automáticamente en tiempo de ejecución](performance.md#max_threads), similar al modo dinámico de PHP-FPM. +El _pool_ de hilos de PHP opera con un número fijo de hilos inicializados al inicio, comparable al modo estático de PHP-FPM. También es posible permitir que los hilos [escalen automáticamente en tiempo de ejecución](performance.md#max_threads), similar al modo dinámico de PHP-FPM. Las conexiones en cola esperarán indefinidamente hasta que un hilo de PHP esté disponible para atenderlas. Para evitar esto, puedes usar la configuración `max_wait_time` en la [configuración global de FrankenPHP](config.md#caddyfile-config) para limitar la duración que una petición puede esperar por un hilo de PHP libre antes de ser rechazada. Adicionalmente, puedes establecer un [tiempo límite de escritura razonable en Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts). -Cada instancia de Caddy iniciará solo un *pool* de hilos de FrankenPHP, el cual será compartido entre todos los bloques `php_server`. +Cada instancia de Caddy iniciará solo un _pool_ de hilos de FrankenPHP, el cual será compartido entre todos los bloques `php_server`. diff --git a/docs/es/compile.md b/docs/es/compile.md index 269b0ff629..540e31ac81 100644 --- a/docs/es/compile.md +++ b/docs/es/compile.md @@ -79,11 +79,11 @@ sudo make install Algunas características de FrankenPHP dependen de dependencias opcionales del sistema que deben instalarse. Alternativamente, estas características pueden deshabilitarse pasando etiquetas de compilación al compilador Go. -| Característica | Dependencia | Etiqueta de compilación para deshabilitarla | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| Compresión Brotli | [Brotli](https://github.com/google/brotli) | nobrotli | -| Reiniciar workers al cambiar archivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | -| [Mercure](mercure.md) | [Biblioteca Mercure Go](https://pkg.go.dev/github.com/dunglas/mercure) (instalada automáticamente, licencia AGPL) | nomercure | +| Característica | Dependencia | Etiqueta de compilación para deshabilitarla | +| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| Compresión Brotli | [Brotli](https://github.com/google/brotli) | nobrotli | +| Reiniciar workers al cambiar archivos | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | +| [Mercure](mercure.md) | [Biblioteca Mercure Go](https://pkg.go.dev/github.com/dunglas/mercure) (instalada automáticamente, licencia AGPL) | nomercure | ## Compilar la aplicación Go diff --git a/docs/es/embed.md b/docs/es/embed.md index fa324609b4..5841ce3414 100644 --- a/docs/es/embed.md +++ b/docs/es/embed.md @@ -66,7 +66,7 @@ La forma más fácil de crear un binario para Linux es usar el constructor basad RUN EMBED=dist/app/ ./build-static.sh ``` - > [!CAUTION] + > [!CAUTION] > > Algunos archivos `.dockerignore` (por ejemplo, el [`.dockerignore` predeterminado de Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)) > ignorarán el directorio `vendor/` y los archivos `.env`. Asegúrese de ajustar o eliminar el archivo `.dockerignore` antes de la construcción. diff --git a/docs/es/extension-workers.md b/docs/es/extension-workers.md index fa7eecfc40..c7e3654507 100644 --- a/docs/es/extension-workers.md +++ b/docs/es/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP proporciona hooks para ejecutar código Go en puntos específicos del ciclo de vida. -| Tipo de Hook | Nombre de Opción | Firma | Contexto y Caso de Uso | -| :----------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------------- | -| **Server** | `WithWorkerOnServerStartup` | `func()` | Configuración global. Se ejecuta **Una vez**. Ejemplo: Conectar a NATS/Redis. | -| **Server** | `WithWorkerOnServerShutdown` | `func()` | Limpieza global. Se ejecuta **Una vez**. Ejemplo: Cerrar conexiones compartidas. | -| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuración por hilo. Llamado cuando un hilo inicia. Recibe el ID del hilo. | -| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpieza por hilo. Recibe el ID del hilo. | +| Tipo de Hook | Nombre de Opción | Firma | Contexto y Caso de Uso | +| :----------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------- | +| **Servidor** | `WithWorkerOnServerStartup` | `func()` | Configuración global. Se ejecuta **Una vez**. Ejemplo: Conectar a NATS/Redis. | +| **Servidor** | `WithWorkerOnServerShutdown` | `func()` | Limpieza global. Se ejecuta **Una vez**. Ejemplo: Cerrar conexiones compartidas. | +| **Hilo** | `WithWorkerOnReady` | `func(threadID int)` | Configuración por hilo. Llamado cuando un hilo inicia. Recibe el ID del hilo. | +| **Hilo** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpieza por hilo. Recibe el ID del hilo. | ### Ejemplo diff --git a/docs/es/extensions.md b/docs/es/extensions.md index da85623a24..cf22542f45 100644 --- a/docs/es/extensions.md +++ b/docs/es/extensions.md @@ -87,21 +87,21 @@ Mientras que el primer punto se explica por sí mismo, el segundo puede ser más Aunque algunos tipos de variables tienen la misma representación en memoria entre C/PHP y Go, algunos tipos requieren más lógica para ser usados directamente. Esta es quizá la parte más difícil cuando se trata de escribir extensiones porque requiere entender los internos del motor Zend y cómo se almacenan las variables internamente en PHP. Esta tabla resume lo que necesitas saber: -| Tipo PHP | Tipo Go | Conversión directa | Helper de C a Go | Helper de Go a C | Soporte para Métodos de Clase | -|---------------------|--------------------------------|---------------------|---------------------------------------|----------------------------------------|-------------------------------| -| `int` | `int64` | ✅ | - | - | ✅ | -| `?int` | `*int64` | ✅ | - | - | ✅ | -| `float` | `float64` | ✅ | - | - | ✅ | -| `?float` | `*float64` | ✅ | - | - | ✅ | -| `bool` | `bool` | ✅ | - | - | ✅ | -| `?bool` | `*bool` | ✅ | - | - | ✅ | -| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ | -| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ | -| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ | -| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ | -| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ | -| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | -| `object` | `struct` | ❌ | _Aún no implementado_ | _Aún no implementado_ | ❌ | +| Tipo PHP | Tipo Go | Conversión directa | Helper de C a Go | Helper de Go a C | Soporte para Métodos de Clase | +| ------------------ | ----------------------------- | ------------------ | --------------------------------- | ---------------------------------- | ----------------------------- | +| `int` | `int64` | ✅ | - | - | ✅ | +| `?int` | `*int64` | ✅ | - | - | ✅ | +| `float` | `float64` | ✅ | - | - | ✅ | +| `?float` | `*float64` | ✅ | - | - | ✅ | +| `bool` | `bool` | ✅ | - | - | ✅ | +| `?bool` | `*bool` | ✅ | - | - | ✅ | +| `string`/`?string` | `*C.zend_string` | ❌ | `frankenphp.GoString()` | `frankenphp.PHPString()` | ✅ | +| `array` | `frankenphp.AssociativeArray` | ❌ | `frankenphp.GoAssociativeArray()` | `frankenphp.PHPAssociativeArray()` | ✅ | +| `array` | `map[string]any` | ❌ | `frankenphp.GoMap()` | `frankenphp.PHPMap()` | ✅ | +| `array` | `[]any` | ❌ | `frankenphp.GoPackedArray()` | `frankenphp.PHPPackedArray()` | ✅ | +| `mixed` | `any` | ❌ | `GoValue()` | `PHPValue()` | ❌ | +| `callable` | `*C.zval` | ❌ | - | frankenphp.CallPHPCallable() | ❌ | +| `object` | `struct` | ❌ | _Aún no implementado_ | _Aún no implementado_ | ❌ | > [!NOTE] > diff --git a/docs/es/known-issues.md b/docs/es/known-issues.md index bf7e637b0e..e4ce1cf56a 100644 --- a/docs/es/known-issues.md +++ b/docs/es/known-issues.md @@ -4,17 +4,17 @@ Las siguientes extensiones se sabe que no son compatibles con FrankenPHP: -| Nombre | Razón | Alternativas | -| ----------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- | -| [imap](https://www.php.net/manual/es/imap.installation.php) | No es thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) | -| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | No es thread-safe | - | +| Nombre | Razón | Alternativas | +| ----------------------------------------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- | +| [imap](https://www.php.net/manual/es/imap.installation.php) | No es thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) | +| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | No es thread-safe | - | ## Extensiones PHP con Errores Las siguientes extensiones tienen errores conocidos y comportamientos inesperados cuando se usan con FrankenPHP: -| Nombre | Problema | -| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Nombre | Problema | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ext-openssl](https://www.php.net/manual/es/book.openssl.php) | Cuando se usa musl libc, la extensión OpenSSL puede fallar bajo cargas pesadas. El problema no ocurre cuando se usa la más popular GNU libc. Este error está [siendo rastreado por PHP](https://github.com/php/php-src/issues/13648). | ## get_browser diff --git a/docs/es/worker.md b/docs/es/worker.md index a2d49f4439..41002cb1eb 100644 --- a/docs/es/worker.md +++ b/docs/es/worker.md @@ -152,7 +152,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart ### Fallos en Workers Si un script de worker falla con un código de salida distinto de cero, FrankenPHP lo reiniciará con una estrategia de retroceso exponencial. -Si el script de worker permanece activo más tiempo que el último retroceso * 2, +Si el script de worker permanece activo más tiempo que el último retroceso × 2, no penalizará al script de worker y lo reiniciará nuevamente. Sin embargo, si el script de worker continúa fallando con un código de salida distinto de cero en un corto período de tiempo (por ejemplo, tener un error tipográfico en un script), FrankenPHP fallará con el error: `too many consecutive failures`. diff --git a/docs/extensions.md b/docs/extensions.md index ba07c397fd..715b4d485d 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -17,10 +17,10 @@ We'll start with the generator approach as it's the easiest way to get started, ## Using the Extension Generator -FrankenPHP is bundled with a tool that allows you **to create a PHP extension** only using Go. **No need to write C code** or use CGO directly: FrankenPHP also includes a **public types API** to help you write your extensions in Go without having to worry about **the type juggling between PHP/C and Go**. +FrankenPHP is bundled with a tool that lets you **create a PHP extension** using only Go. **No need to write C code** or use CGO directly: FrankenPHP also includes a **public types API** to help you write your extensions in Go without having to worry about **the type juggling between PHP/C and Go**. > [!TIP] -> If you want to understand how extensions can be written in Go from scratch, you can read the manual implementation section below demonstrating how to write a PHP extension in Go without using the generator. +> If you want to understand how extensions can be written in Go from scratch, you can read the manual implementation section below, demonstrating how to write a PHP extension in Go without using the generator. Keep in mind that this tool is **not a full-fledged extension generator**. It is meant to help you write simple extensions in Go, but it does not provide the most advanced features of PHP extensions. If you need to write a more **complex and optimized** extension, you may need to write some C code or use CGO directly. @@ -44,7 +44,7 @@ tar xf php-* ### Writing the Extension -Everything is now setup to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this: +Everything is now set up to write your native function in Go. Create a new file named `stringext.go`. Our first function will take a string as an argument, the number of times to repeat it, a boolean to indicate whether to reverse the string, and return the resulting string. This should look like this: ```go package example @@ -77,7 +77,7 @@ func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer { There are two important things to note here: -- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type; +- A directive comment `//export_php:function` defines the function signature in PHP. This is how the generator knows how to generate the PHP function with the right parameters and return type. - The function must return an `unsafe.Pointer`. FrankenPHP provides an API to help you with type juggling between C and Go. While the first point speaks for itself, the second may be harder to apprehend. Let's take a deeper dive to type juggling later in this guide. @@ -142,7 +142,7 @@ Once you've integrated your extension into FrankenPHP as demonstrated in the pre ### Type Juggling -While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding internals of the Zend Engine and how variables are stored internally in PHP. +While some variable types have the same memory representation between C/PHP and Go, some types require more logic to be directly used. This is maybe the hardest part when it comes to writing extensions because it requires understanding the internals of the Zend Engine and how variables are stored internally in PHP. This table summarizes what you need to know: | PHP type | Go type | Direct conversion | C to Go helper | Go to C helper | Class Methods Support | @@ -167,7 +167,7 @@ This table summarizes what you need to know: > > For class methods specifically, primitive types and arrays are currently supported. Objects cannot be used as method parameters or return types yet. -If you refer to the code snippet of the previous section, you can see that helpers are used to convert the first parameter and the return value. The second and third parameter of our `repeat_this()` function don't need to be converted as memory representation of the underlying types are the same for both C and Go. +If you refer to the code snippet of the previous section, you can see that helpers are used to convert the first parameter and the return value. The second and third parameters of our `repeat_this()` function don't need to be converted, as the memory representation of the underlying types is the same for both C and Go. #### Working with Arrays @@ -258,8 +258,8 @@ func process_data_packed(arr *C.zend_array) unsafe.Pointer { - **Ordered key-value pairs** - Option to keep the order of the associative array - **Optimized for multiple cases** - Option to ditch the order for better performance or convert straight to a slice -- **Automatic list detection** - When converting to PHP, automatically detects if array should be a packed list or hashmap -- **Nested Arrays** - Arrays can be nested and will convert all support types automatically (`int64`,`float64`,`string`,`bool`,`nil`,`AssociativeArray`,`map[string]any`,`[]any`) +- **Automatic list detection** - When converting to PHP, automatically detects if the array should be a packed list or a hashmap +- **Nested Arrays** - Arrays can be nested and will convert all supported types automatically (`int64`, `float64`, `string`, `bool`, `nil`, `AssociativeArray`, `map[string]any`, `[]any`) - **Objects are not supported** - Currently, only scalar types and arrays can be used as values. Providing an object will result in a `null` value in the PHP array. ##### Available methods: Packed and Associative @@ -471,6 +471,7 @@ const ( ``` > [!NOTE] +> > PHP constants will take the name of the Go constant, thus using upper case letters is recommended. #### Class Constants @@ -498,6 +499,7 @@ const ( ``` > [!NOTE] +> > Just like global constants, the class constants will take the name of the Go constant. Class constants are accessible using the class name scope in PHP: @@ -515,7 +517,7 @@ echo User::ROLE_ADMIN; // "admin" echo Order::STATE_PENDING; // 0 ``` -The directive supports various value types including strings, integers, booleans, floats, and iota constants. When using `iota`, the generator automatically assigns sequential values (0, 1, 2, etc.). Global constants become available in your PHP code as global constants, while class constants are scoped to their respective classes using the public visibility. When using integers, different possible notation (binary, hex, octal) are supported and dumped as is in the PHP stub file. +The directive supports various value types, including strings, integers, booleans, floats, and iota constants. When using `iota`, the generator automatically assigns sequential values (0, 1, 2, etc.). Global constants become available in your PHP code as global constants, while class constants are scoped to their respective classes using the public visibility. When using integers, different possible notations (binary, hex, octal) are supported and dumped as is in the PHP stub file. You can use constants just like you are used to in the Go code. For example, let's take the `repeat_this()` function we declared earlier and change the last argument to an integer: diff --git a/docs/fr/extension-workers.md b/docs/fr/extension-workers.md index 936049c7bc..bfa463a75b 100644 --- a/docs/fr/extension-workers.md +++ b/docs/fr/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP fournit des hooks pour exécuter du code Go à des points spécifiques du cycle de vie. -| Type de Hook | Nom de l'Option | Signature | Contexte et Cas d'Utilisation | -| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- | -| **Serveur** | `WithWorkerOnServerStartup` | `func()` | Configuration globale. Exécuté **Une fois**. Exemple : Connexion à NATS/Redis. | -| **Serveur** | `WithWorkerOnServerShutdown` | `func()` | Nettoyage global. Exécuté **Une fois**. Exemple : Fermeture des connexions partagées. | -| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuration par thread. Appelé lorsqu'un thread démarre. Reçoit l'ID du Thread. | -| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Nettoyage par thread. Reçoit l'ID du Thread. | +| Type de Hook | Nom de l'Option | Signature | Contexte et Cas d'Utilisation | +| :----------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------------ | +| **Serveur** | `WithWorkerOnServerStartup` | `func()` | Configuration globale. Exécuté **Une fois**. Exemple : Connexion à NATS/Redis. | +| **Serveur** | `WithWorkerOnServerShutdown` | `func()` | Nettoyage global. Exécuté **Une fois**. Exemple : Fermeture des connexions partagées. | +| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuration par thread. Appelé lorsqu'un thread démarre. Reçoit l'ID du Thread. | +| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Nettoyage par thread. Reçoit l'ID du Thread. | ### Exemple diff --git a/docs/fr/hot-reload.md b/docs/fr/hot-reload.md index fc26a6541f..2f10567608 100644 --- a/docs/fr/hot-reload.md +++ b/docs/fr/hot-reload.md @@ -57,7 +57,7 @@ php_server { } ``` -Utilisez la forme longue de `hot_reload` pour spécifier le *topic* Mercure à utiliser ainsi que les répertoires ou fichiers à surveiller : +Utilisez la forme longue de `hot_reload` pour spécifier le _topic_ Mercure à utiliser ainsi que les répertoires ou fichiers à surveiller : ```caddyfile localhost @@ -83,8 +83,8 @@ php_server { Le serveur détecte les modifications et publie les modifications automatiquement. Le navigateur doit s'abonner à ces événements pour mettre à jour la page. FrankenPHP expose l'URL du Hub Mercure à utiliser pour s'abonner aux modifications de fichiers via la variable d'environnement `$_SERVER['FRANKENPHP_HOT_RELOAD']`. -La bibliothèque JavaScript [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload) gére la logique côté client. -Pour l'utiliser, ajoutez le code suivant à votre gabarit (*layout*) principal : +La bibliothèque JavaScript [frankenphp-hot-reload](https://www.npmjs.com/package/frankenphp-hot-reload) gère la logique côté client. +Pour l'utiliser, ajoutez le code suivant à votre gabarit (_layout_) principal : ```php @@ -103,12 +103,14 @@ Alternativement, vous pouvez implémenter votre propre logique côté client en ### Conserver les nœuds DOM existants -Dans de rares cas, comme lors de l'utilisation d'outils de développement tels que [la *web debug toolbar* de Symfony](https://github.com/symfony/symfony/pull/62970), +Dans de rares cas, comme lors de l'utilisation d'outils de développement tels que [la _web debug toolbar_ de Symfony](https://github.com/symfony/symfony/pull/62970), vous pouvez souhaiter conserver des nœuds DOM spécifiques. Pour ce faire, ajoutez l'attribut `data-frankenphp-hot-reload-preserve` à l'élément HTML concerné : ```html -
+
+ +
``` ## Mode Worker diff --git a/docs/fr/worker.md b/docs/fr/worker.md index 954d91c7db..0c86b75082 100644 --- a/docs/fr/worker.md +++ b/docs/fr/worker.md @@ -150,7 +150,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart ### Échecs des workers Si un script de worker se plante avec un code de sortie non nul, FrankenPHP le redémarre avec une stratégie de backoff exponentielle. -Si le script worker reste en place plus longtemps que le dernier backoff \* 2, FrankenPHP ne pénalisera pas le script et le redémarrera à nouveau. +Si le script worker reste en place plus longtemps que le dernier backoff × 2, FrankenPHP ne pénalisera pas le script et le redémarrera à nouveau. Toutefois, si le script de worker continue d'échouer avec un code de sortie non nul dans un court laps de temps (par exemple, une faute de frappe dans un script), FrankenPHP plantera avec l'erreur : `too many consecutive failures` (trop d'échecs consécutifs). @@ -186,3 +186,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/hot-reload.md b/docs/hot-reload.md index 0ae4ca7ef3..49790d1e68 100644 --- a/docs/hot-reload.md +++ b/docs/hot-reload.md @@ -26,7 +26,7 @@ To enable hot reloading, enable Mercure, then add the `hot_reload` sub-directive > > This feature is intended for **development environments only**. > Do not enable `hot_reload` in production, as this feature is not secure (exposes sensitive internal details) and slows down the application. -> + ```caddyfile localhost diff --git a/docs/internals.md b/docs/internals.md new file mode 100644 index 0000000000..bd1bb636fa --- /dev/null +++ b/docs/internals.md @@ -0,0 +1,249 @@ +# Internals + +This document explains FrankenPHP's internal architecture, focusing on thread management, the state machine, and the CGO boundary between Go and C/PHP. + +## Overview + +FrankenPHP embeds the PHP interpreter directly into Go via CGO. Each PHP execution runs on a real POSIX thread (not a goroutine) because PHP's ZTS (Zend Thread Safety) model requires it. Go orchestrates these threads through a state machine, while C handles the PHP SAPI lifecycle. + +The main layers are: + +1. **Go layer** (top-level `*.go` files such as `frankenphp.go`, `phpthread.go`, `thread*.go`, `scaling.go`): Thread pool management, request routing, auto-scaling +2. **C layer** (`frankenphp.c`, `frankenphp.h`): PHP SAPI implementation, script execution loop, superglobal management +3. **State machine** (`internal/state/`): Synchronization between Go goroutines and C threads + +## Thread Types + +### Main Thread (`phpmainthread.go`) + +The main PHP thread (`phpMainThread`) initializes the PHP runtime: + +1. Applies `php.ini` overrides +2. Takes a snapshot of the environment (`main_thread_env`) for sandboxing +3. Starts the PHP SAPI module +4. Signals readiness to the Go side + +It stays alive for the lifetime of the server. All other threads are started after it signals `Ready`. + +### Regular Threads (`threadregular.go`) + +Handle classic one-request-per-invocation PHP scripts. Each request: + +1. Receives a request via `requestChan` or the shared `regularRequestChan` +2. Returns the script filename from `beforeScriptExecution()` +3. The C layer executes the PHP script +4. `afterScriptExecution()` closes the request context + +### Worker Threads (`threadworker.go`) + +Keep a PHP script alive across multiple requests. The PHP script calls `frankenphp_handle_request()` in a loop: + +1. `beforeScriptExecution()` returns the worker script filename +2. The C layer starts executing the PHP script +3. The PHP script calls `frankenphp_handle_request()`, which calls `waitForWorkerRequest()` in Go +4. Go blocks until a request arrives, then sets up the request context +5. The PHP callback handles the request +6. `go_frankenphp_finish_worker_request()` cleans up the request context +7. The PHP script loops back to step 3 + +After the script exits, the worker is restarted immediately if it had reached `frankenphp_handle_request()` at least once (whether the exit was clean or the result of a fatal error). Exponential backoff is only applied to consecutive startup failures, where the script exits before ever reaching `frankenphp_handle_request()`. + +## Thread State Machine + +Each thread has a `ThreadState` (defined in `internal/state/state.go`) that governs its lifecycle. The state machine uses a `sync.RWMutex` for all state transitions and a channel-based subscriber pattern for blocking waits. + +### States + +```text +Lifecycle: Reserved → BootRequested → Booting → Inactive → Ready ⇄ (processing) + ↓ +Shutdown: ShuttingDown → Done → Reserved + ↑ +Restart (admin/watcher): Restarting → Yielding → Ready + ↑ +ZTS reboot (max_requests): Rebooting → RebootReady → Ready + ↑ +Handler transition: TransitionRequested → TransitionInProgress → TransitionComplete +``` + +The full set of states is defined in `internal/state/state.go`: + +| State | Description | +| ---------------------- | ------------------------------------------------------------------------------------ | +| `Reserved` | Thread slot allocated but not booted. Can be booted on demand. | +| `BootRequested` | Boot has been queued (e.g., by the main thread) but the POSIX thread hasn't started. | +| `Booting` | Underlying POSIX thread is starting up. | +| `Inactive` | Thread is alive but has no handler assigned. Minimal memory footprint. | +| `Ready` | Thread has a handler and is ready to accept work. | +| `ShuttingDown` | Thread is shutting down. | +| `Done` | Thread has completely shut down. Transitions back to `Reserved` for potential reuse. | +| `Restarting` | Worker thread is being restarted (e.g., via admin API or file watcher). | +| `Yielding` | Worker thread has yielded control and is waiting to be re-activated. | +| `Rebooting` | Worker thread is exiting the C loop for a full ZTS restart (e.g., `max_requests`). | +| `RebootReady` | The C thread has exited and ZTS state is cleaned up, ready to spawn a new C thread. | +| `TransitionRequested` | A handler change has been requested from the Go side. | +| `TransitionInProgress` | The C thread has acknowledged the transition request. | +| `TransitionComplete` | The Go side has installed the new handler. | + +### Key Operations + +**`RequestSafeStateChange(nextState)`**: The primary way external goroutines request state changes. It: + +- Atomically succeeds from `Ready` or `Inactive` (under mutex) +- Returns `false` immediately from `ShuttingDown`, `Done`, or `Reserved` +- Blocks and retries from any other state, waiting for `Ready`, `Inactive`, or `ShuttingDown` + +This guarantees mutual exclusion: only one of `shutdown()`, `setHandler()`, or `drainWorkerThreads()` can succeed at a time on a given thread. + +**`WaitFor(states...)`**: Blocks until the thread reaches one of the specified states. Uses a channel-based subscriber pattern so waiters are efficiently notified. + +**`Set(nextState)`**: Unconditional state change. Used by the thread itself (from C callbacks) to signal state transitions. + +**`CompareAndSwap(compareTo, swapTo)`**: Atomic compare-and-swap. Used for boot initialization. + +### Handler Transition Protocol + +When a thread needs to change its handler (e.g., from inactive to worker): + +```text +Go side (setHandler) C side (PHP thread) +───────────────── ───────────────── +RequestSafeStateChange( + TransitionRequested) +close(drainChan) + detects drain + Set(TransitionInProgress) +WaitFor(TransitionInProgress) + → unblocked WaitFor(TransitionComplete) +handler = newHandler +drainChan = make(chan struct{}) +Set(TransitionComplete) + → unblocked + newHandler.beforeScriptExecution() +``` + +This protocol ensures the handler pointer is never read and written concurrently. + +### Worker Restart Protocol + +When workers are restarted (e.g., via admin API): + +```text +Go side (RestartWorkers) C side (worker thread) +───────────────── ───────────────── +RequestSafeStateChange( + Restarting) +close(drainChan) + detects drain in waitForWorkerRequest() + returns false → PHP script exits + beforeScriptExecution(): + state is Restarting → + Set(Yielding) +WaitFor(Yielding) + → unblocked WaitFor(Ready, ShuttingDown) +drainChan = make(chan struct{}) +Set(Ready) + → unblocked + beforeScriptExecution() recurse: + state is Ready → normal execution +``` + +## CGO Boundary + +### Exported Go Functions + +C code calls Go functions via CGO exports. The main callbacks are: + +| Function | Called when | +| ------------------------------------------- | ------------------------------------------------ | +| `go_frankenphp_before_script_execution` | C loop needs the next script to execute | +| `go_frankenphp_after_script_execution` | PHP script has finished executing | +| `go_frankenphp_worker_handle_request_start` | Worker's `frankenphp_handle_request()` is called | +| `go_frankenphp_finish_worker_request` | Worker request handler has returned | +| `go_ub_write` | PHP produces output (`echo`, etc.) | +| `go_read_post` | PHP reads POST body (`php://input`) | +| `go_read_cookies` | PHP reads cookies | +| `go_write_headers` | PHP sends response headers | +| `go_sapi_flush` | PHP flushes output | +| `go_log_attrs` | PHP logs a structured message | + +All these functions receive a `threadIndex` parameter identifying the calling thread. This is a thread-local variable in C (`__thread uintptr_t thread_index`) set during thread initialization. + +### C Thread Main Loop + +Each PHP thread runs `php_thread()` in `frankenphp.c`: + +```c +while ((scriptName = go_frankenphp_before_script_execution(thread_index))) { + php_request_startup(); + php_execute_script(&file_handle); + php_request_shutdown(); + go_frankenphp_after_script_execution(thread_index, exit_status); +} +``` + +Bailouts (fatal PHP errors) are caught by `zend_catch`, which marks the thread as unhealthy and forces cleanup. + +### Memory Management + +- **Go → C strings**: `C.CString()` allocates with `malloc()`. The C side is responsible for freeing (e.g., `frankenphp_free_request_context()` frees cookie data). +- **Go string pinning**: `phpThread` (in `phpthread.go`) embeds Go's [`runtime.Pinner`](https://pkg.go.dev/runtime#Pinner). `thread.Pin()` / `thread.Unpin()` keep Go memory referenced from C alive without copying it. The thread is unpinned after each script execution. +- **PHP memory**: Managed by Zend's memory manager (`emalloc`/`efree`). Automatically freed at request shutdown. + +## Auto-Scaling + +FrankenPHP can automatically scale the number of PHP threads based on demand (`scaling.go`). + +### Configuration + +- `num_threads`: Initial number of threads started at boot +- `max_threads`: Maximum number of threads allowed (includes auto-scaled) + +### Upscaling + +A dedicated goroutine reads from an unbuffered `scaleChan`: + +1. A request handler can't find an available thread +2. It sends the request context to `scaleChan` +3. The scaling goroutine checks: + - Has the request been stalled long enough? (minimum 5ms) + - Is CPU usage below the threshold? (80%) + - Is the thread limit reached? +4. If all checks pass, a new thread is booted and assigned + +### Downscaling + +A separate goroutine periodically checks (every 5s) for idle auto-scaled threads. Threads in `Ready` state idle longer than `maxIdleTime` (default 5s) are converted to `Inactive` (up to 10 per cycle). They are not fully stopped: a code path exists for that, but it is currently disabled because some PECL extensions leak memory and prevent threads from cleanly shutting down. + +## Environment Sandboxing + +FrankenPHP sandboxes environment variables per-thread: + +1. At startup, the main thread snapshots `os.Environ()` into `main_thread_env` (a PHP `HashTable`). +2. `$_SERVER` is built from a copy of `main_thread_env` plus request-specific variables (in `frankenphp_register_server_vars`). It is rebuilt for every request, including each iteration of a worker script. +3. `$_ENV` is populated from the same snapshot through PHP's `php_import_environment_variables` hook. In regular mode this happens once per script execution; in worker mode it happens once when the worker script starts and is **not** rebuilt between worker requests, which is why writes to `$_ENV` leak across requests (see [Worker Mode](worker.md)). +4. `frankenphp_putenv()` / `frankenphp_getenv()` operate on a thread-local `sandboxed_env` initialized lazily from `main_thread_env`, preventing race conditions on the global C environment. +5. `reset_sandboxed_environment()` releases `sandboxed_env` after each PHP script execution. In regular mode that's per request; in worker mode it only runs when the worker script itself exits, so `putenv()` writes are visible to subsequent worker requests on the same thread until the script restarts. + +## Request Flow (Regular Mode) + +1. HTTP request arrives at Caddy +2. FrankenPHP's Caddy module resolves the PHP script path +3. A `frankenPHPContext` is created with the request and script info +4. The context is sent to an available regular thread via `requestChan` +5. The thread's `beforeScriptExecution()` returns the script filename +6. The C layer executes the PHP script +7. During execution, Go callbacks handle I/O (`go_ub_write`, `go_read_post`, etc.) +8. After execution, `afterScriptExecution()` signals completion +9. The response is sent to the client + +## Request Flow (Worker Mode) + +1. HTTP request arrives at Caddy +2. FrankenPHP's Caddy module resolves the worker for this request +3. A `frankenPHPContext` is created +4. The context is sent to the worker's `requestChan` or a specific thread's `requestChan` +5. The worker thread's `waitForWorkerRequest()` receives it +6. PHP's `frankenphp_handle_request()` callback is invoked +7. After the callback returns, `go_frankenphp_finish_worker_request()` cleans up +8. The worker loops back to `waitForWorkerRequest()` diff --git a/docs/ja/extension-workers.md b/docs/ja/extension-workers.md index 573d831474..3c7c0aee0e 100644 --- a/docs/ja/extension-workers.md +++ b/docs/ja/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHPは、ライフサイクルの特定の時点でGoコードを実行するためのフックを提供します。 -| フックタイプ | オプション名 | シグネチャ | コンテキストと使用例 | -| :--------- | :-------------------------- | :------------------ | :--------------------------------------------------------------------- | -| **サーバー** | `WithWorkerOnServerStartup` | `func()` | グローバルなセットアップ。**一度だけ**実行されます。例: NATS/Redisへの接続。 | -| **サーバー** | `WithWorkerOnServerShutdown` | `func()` | グローバルなクリーンアップ。**一度だけ**実行されます。例: 共有接続のクローズ。 | -| **スレッド** | `WithWorkerOnReady` | `func(threadID int)` | スレッドごとのセットアップ。スレッドが開始したときに呼び出されます。スレッドIDを受け取ります。 | -| **スレッド** | `WithWorkerOnShutdown` | `func(threadID int)` | スレッドごとのクリーンアップ。スレッドIDを受け取ります。 | +| フックタイプ | オプション名 | シグネチャ | コンテキストと使用例 | +| :----------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------------------------------- | +| **サーバー** | `WithWorkerOnServerStartup` | `func()` | グローバルなセットアップ。**一度だけ**実行されます。例: NATS/Redisへの接続。 | +| **サーバー** | `WithWorkerOnServerShutdown` | `func()` | グローバルなクリーンアップ。**一度だけ**実行されます。例: 共有接続のクローズ。 | +| **スレッド** | `WithWorkerOnReady` | `func(threadID int)` | スレッドごとのセットアップ。スレッドが開始したときに呼び出されます。スレッドIDを受け取ります。 | +| **スレッド** | `WithWorkerOnShutdown` | `func(threadID int)` | スレッドごとのクリーンアップ。スレッドIDを受け取ります。 | ### 例 diff --git a/docs/ja/hot-reload.md b/docs/ja/hot-reload.md index 2ee3f6b358..0c1f4ebbcc 100644 --- a/docs/ja/hot-reload.md +++ b/docs/ja/hot-reload.md @@ -26,7 +26,7 @@ FrankenPHPはページの内容をリアルタイムで更新します。 > > この機能は**開発環境のみ**を対象としています。 > `hot_reload`を本番環境で有効にしないでください。この機能は安全ではなく(機密性の高い内部詳細を公開します)、アプリケーションの速度を低下させます。 -> + ```caddyfile localhost @@ -145,5 +145,5 @@ php_server { 4. **受信**: JavaScriptライブラリを介してリッスンしているブラウザがMercureイベントを受信します。 5. **更新**: - - **Idiomorph**が検出された場合、更新されたコンテンツをフェッチし、現在のHTMLを新しい状態に合わせてモーフィングし、状態を失うことなく即座に変更を適用します。 - - それ以外の場合、`window.location.reload()`が呼び出されてページがリフレッシュされます。 + - **Idiomorph**が検出された場合、更新されたコンテンツをフェッチし、現在のHTMLを新しい状態に合わせてモーフィングし、状態を失うことなく即座に変更を適用します。 + - それ以外の場合、`window.location.reload()`が呼び出されてページがリフレッシュされます。 diff --git a/docs/ja/worker.md b/docs/ja/worker.md index aa71b6d3b4..ffda677b71 100644 --- a/docs/ja/worker.md +++ b/docs/ja/worker.md @@ -186,3 +186,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/migrate.md b/docs/migrate.md index 2aabbfeedc..07016fef10 100644 --- a/docs/migrate.md +++ b/docs/migrate.md @@ -5,14 +5,14 @@ This guide covers a basic migration for a typical PHP application. ## Key Differences -| PHP-FPM setup | FrankenPHP equivalent | -|---|---| -| Nginx/Apache + PHP-FPM | Single `frankenphp` binary | -| `php-fpm.conf` pool settings | [`frankenphp` global option](config.md#caddyfile-config) | -| Nginx `server {}` block | `Caddyfile` site block | -| `php_value` / `php_admin_value` | [`php_ini` Caddyfile directive](config.md#php-config) | -| `pm = static` / `pm.max_children` | `num_threads` | -| `pm = dynamic` | [`max_threads auto`](performance.md#max_threads) | +| PHP-FPM setup | FrankenPHP equivalent | +| --------------------------------- | -------------------------------------------------------- | +| Nginx/Apache + PHP-FPM | Single `frankenphp` binary | +| `php-fpm.conf` pool settings | [`frankenphp` global option](config.md#caddyfile-config) | +| Nginx `server {}` block | `Caddyfile` site block | +| `php_value` / `php_admin_value` | [`php_ini` Caddyfile directive](config.md#php-config) | +| `pm = static` / `pm.max_children` | `num_threads` | +| `pm = dynamic` | [`max_threads auto`](performance.md#max_threads) | ## Step 1: Replace Your Web Server Config diff --git a/docs/pt-br/extension-workers.md b/docs/pt-br/extension-workers.md index b7ddc1bc8e..bb6623de95 100644 --- a/docs/pt-br/extension-workers.md +++ b/docs/pt-br/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP oferece hooks para executar código Go em pontos específicos do ciclo de vida. -| Tipo de Hook | Nome da Opção | Assinatura | Contexto e Caso de Uso | -| :--------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------- | -| **Servidor** | `WithWorkerOnServerStartup` | `func()` | Configuração global. Executado **Uma Vez**. Exemplo: Conectar ao NATS/Redis. | -| **Servidor** | `WithWorkerOnServerShutdown` | `func()` | Limpeza global. Executado **Uma Vez**. Exemplo: Fechar conexões compartilhadas. | -| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuração por thread. Chamado quando um thread inicia. Recebe o ID do Thread. | -| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpeza por thread. Recebe o ID do Thread. | +| Tipo de Hook | Nome da Opção | Assinatura | Contexto e Caso de Uso | +| :----------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------- | +| **Servidor** | `WithWorkerOnServerStartup` | `func()` | Configuração global. Executado **Uma Vez**. Exemplo: Conectar ao NATS/Redis. | +| **Servidor** | `WithWorkerOnServerShutdown` | `func()` | Limpeza global. Executado **Uma Vez**. Exemplo: Fechar conexões compartilhadas. | +| **Thread** | `WithWorkerOnReady` | `func(threadID int)` | Configuração por thread. Chamado quando um thread inicia. Recebe o ID do Thread. | +| **Thread** | `WithWorkerOnShutdown` | `func(threadID int)` | Limpeza por thread. Recebe o ID do Thread. | ### Exemplo diff --git a/docs/pt-br/hot-reload.md b/docs/pt-br/hot-reload.md index c60f546d1e..f94c1ac4db 100644 --- a/docs/pt-br/hot-reload.md +++ b/docs/pt-br/hot-reload.md @@ -24,7 +24,7 @@ Para ativar o recarregamento instantâneo, habilite o Mercure e, em seguida, adi > > Este recurso é destinado **apenas para ambientes de desenvolvimento**. > Não ative `hot_reload` em produção, pois este recurso não é seguro (expõe detalhes internos sensíveis) e desacelera a aplicação. -> + ```caddyfile localhost diff --git a/docs/pt-br/worker.md b/docs/pt-br/worker.md index 64c442264e..1f9377f2e5 100644 --- a/docs/pt-br/worker.md +++ b/docs/pt-br/worker.md @@ -147,7 +147,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart ### Falhas de Worker Se um worker script travar com um código de saída diferente de zero, o FrankenPHP o reiniciará com uma estratégia de backoff exponencial. -Se o worker script permanecer ativo por mais tempo do que o último backoff \* 2, ele não irá penalizar o worker script e reiniciá-lo novamente. +Se o worker script permanecer ativo por mais tempo do que o último backoff × 2, ele não irá penalizar o worker script e reiniciá-lo novamente. No entanto, se o worker script continuar a falhar com um código de saída diferente de zero em um curto período de tempo (por exemplo, com um erro de digitação em um script), o FrankenPHP travará com o erro: `too many consecutive failures`. O número de falhas consecutivas pode ser configurado no seu [Caddyfile](config.md#caddyfile-config) com a opção `max_consecutive_failures`: @@ -182,3 +182,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/ru/extension-workers.md b/docs/ru/extension-workers.md index af9adcaa65..5ee8b5d148 100644 --- a/docs/ru/extension-workers.md +++ b/docs/ru/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP предоставляет хуки для выполнения Go-кода в определенные моменты жизненного цикла. -| Тип хука | Имя опции | Подпись | Контекст и вариант использования | -| :-------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------- | +| Тип хука | Имя опции | Подпись | Контекст и вариант использования | +| :--------- | :--------------------------- | :------------------- | :-------------------------------------------------------------------------------- | | **Сервер** | `WithWorkerOnServerStartup` | `func()` | Глобальная настройка. Выполняется **один раз**. Пример: Подключение к NATS/Redis. | -| **Сервер** | `WithWorkerOnServerShutdown` | `func()` | Глобальная очистка. Выполняется **один раз**. Пример: Закрытие общих соединений. | -| **Поток** | `WithWorkerOnReady` | `func(threadID int)` | Настройка для каждого потока. Вызывается при запуске потока. Получает ID потока. | -| **Поток** | `WithWorkerOnShutdown` | `func(threadID int)` | Очистка для каждого потока. Получает ID потока. | +| **Сервер** | `WithWorkerOnServerShutdown` | `func()` | Глобальная очистка. Выполняется **один раз**. Пример: Закрытие общих соединений. | +| **Поток** | `WithWorkerOnReady` | `func(threadID int)` | Настройка для каждого потока. Вызывается при запуске потока. Получает ID потока. | +| **Поток** | `WithWorkerOnShutdown` | `func(threadID int)` | Очистка для каждого потока. Получает ID потока. | ### Пример diff --git a/docs/ru/hot-reload.md b/docs/ru/hot-reload.md index 4443b5fd52..a7af1f2ba5 100644 --- a/docs/ru/hot-reload.md +++ b/docs/ru/hot-reload.md @@ -25,7 +25,7 @@ FrankenPHP включает встроенную функцию **горячей > > Эта функция предназначена **только для сред разработки**. > Не включайте `hot_reload` в продакшене, так как эта функция небезопасна (раскрывает конфиденциальные внутренние детали) и замедляет работу приложения. -> + ```caddyfile localhost diff --git a/docs/ru/worker.md b/docs/ru/worker.md index dba8517db1..57babc77d9 100644 --- a/docs/ru/worker.md +++ b/docs/ru/worker.md @@ -146,7 +146,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart ### Сбои worker-скрипта Если worker-скрипт завершится с ненулевым кодом выхода, FrankenPHP перезапустит его со стратегией экспоненциальной задержки. -Если скрипт воркера остается активным дольше, чем время последней задержки \* 2, FrankenPHP не будет применять штраф к worker-скрипту и перезапустит его снова. +Если скрипт воркера остается активным дольше, чем время последней задержки × 2, FrankenPHP не будет применять штраф к worker-скрипту и перезапустит его снова. Однако, если worker-скрипт продолжает завершаться с ненулевым кодом выхода в течение короткого промежутка времени (например, из-за опечатки в скрипте), FrankenPHP завершит работу с ошибкой: `too many consecutive failures`. @@ -181,3 +181,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/symfony.md b/docs/symfony.md index 54acb5cefb..7c41b49df9 100644 --- a/docs/symfony.md +++ b/docs/symfony.md @@ -49,6 +49,15 @@ docker run \ Learn more about [the worker mode](worker.md). +### Auditing Worker Compatibility + +[Igor PHP](https://github.com/igor-php/igor-php) is a static linter that scans Symfony projects for state leaks before they bite in production: services missing `ResetInterface`, stateful properties that aren't reset, mutable local statics, `exit()`/`die()` calls, and superglobal writes. It audits your application code as well as services declared in `vendor/`. + +```console +composer require --dev igor-php/igor-php +vendor/bin/igor-php . +``` + ## Hot Reload Hot reloading is enabled by default in [Symfony Docker](https://github.com/dunglas/symfony-docker). diff --git a/docs/tr/extension-workers.md b/docs/tr/extension-workers.md index ea8590d1fb..1ccbf07052 100644 --- a/docs/tr/extension-workers.md +++ b/docs/tr/extension-workers.md @@ -135,12 +135,12 @@ while (frankenphp_handle_request($handler)) { FrankenPHP, yaşam döngüsünün belirli noktalarında Go kodunu yürütmek için kancalar sağlar. -| Kanca Türü | Seçenek Adı | İmza | Bağlam ve Kullanım Durumu | -| :--------- | :--------------------------- | :------------------- | :--------------------------------------------------------------------- | -| **Sunucu** | `WithWorkerOnServerStartup` | `func()` | Genel kurulum. **Bir Kez** çalışır. Örnek: NATS/Redis'e bağlanma. | -| **Sunucu** | `WithWorkerOnServerShutdown` | `func()` | Genel temizleme. **Bir Kez** çalışır. Örnek: Paylaşılan bağlantıları kapatma. | +| Kanca Türü | Seçenek Adı | İmza | Bağlam ve Kullanım Durumu | +| :--------------- | :--------------------------- | :------------------- | :------------------------------------------------------------------------------------------------ | +| **Sunucu** | `WithWorkerOnServerStartup` | `func()` | Genel kurulum. **Bir Kez** çalışır. Örnek: NATS/Redis'e bağlanma. | +| **Sunucu** | `WithWorkerOnServerShutdown` | `func()` | Genel temizleme. **Bir Kez** çalışır. Örnek: Paylaşılan bağlantıları kapatma. | | **İş Parçacığı** | `WithWorkerOnReady` | `func(threadID int)` | İş parçacığı başına kurulum. Bir iş parçacığı başladığında çağrılır. İş Parçacığı Kimliğini alır. | -| **İş Parçacığı** | `WithWorkerOnShutdown` | `func(threadID int)` | İş parçacığı başına temizleme. İş Parçacığı Kimliğini alır. | +| **İş Parçacığı** | `WithWorkerOnShutdown` | `func(threadID int)` | İş parçacığı başına temizleme. İş Parçacığı Kimliğini alır. | ### Örnek @@ -169,3 +169,4 @@ func init() { }), ) } +``` diff --git a/docs/tr/hot-reload.md b/docs/tr/hot-reload.md index a6450728bd..cb1af4c9f4 100644 --- a/docs/tr/hot-reload.md +++ b/docs/tr/hot-reload.md @@ -26,7 +26,7 @@ Sıcak yeniden yüklemeyi etkinleştirmek için Mercure'ü etkinleştirin, ardı > > Bu özellik **yalnızca geliştirme ortamları** içindir. > `hot_reload`'u üretimde etkinleştirmeyin, zira bu özellik güvenli değildir (hassas dahili ayrıntıları açığa çıkarır) ve uygulamanın yavaşlamasına neden olur. -> + ```caddyfile localhost diff --git a/docs/tr/worker.md b/docs/tr/worker.md index ebca47ebcb..1aa65ba411 100644 --- a/docs/tr/worker.md +++ b/docs/tr/worker.md @@ -179,3 +179,4 @@ $handler = static function () use ($workerServer) { }; // ... +``` diff --git a/docs/worker.md b/docs/worker.md index 65e7e3b0d9..30cc0cfa73 100644 --- a/docs/worker.md +++ b/docs/worker.md @@ -130,7 +130,7 @@ curl -X POST http://localhost:2019/frankenphp/workers/restart ### 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, +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`. @@ -168,3 +168,38 @@ $handler = static function () use ($workerServer) { // ... ``` + +Most superglobals (`$_GET`, `$_POST`, `$_COOKIE`, `$_FILES`, `$_SERVER`, `$_REQUEST`) are automatically reset between requests. +However, **`$_ENV` is currently not reset between requests**. +This means that any modifications made to `$_ENV` during a request will persist and be visible to subsequent requests handled by the same worker thread. +Avoid storing request-specific or sensitive data in `$_ENV`. + +## State Persistence + +Because worker mode keeps the PHP process alive between requests, the following state persists across requests: + +- **Static variables**: Variables declared with `static` inside functions or methods retain their values between requests. +- **Class static properties**: Static properties on classes persist between requests. +- **Global variables**: Variables in the global scope of the worker script persist between requests. +- **In-memory caches**: Any data stored in memory (arrays, objects) outside the request handler persists. + +This is by design and is what makes worker mode fast. However, it requires attention to avoid unintended side effects: + +```php +