Skip to content

feat(frankenphp-symfony): add kernel reboot strategy #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/frankenphp-symfony/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 0.3.0

- Add `frankenphp_kernel_reboot` option

## 0.2.0

- Add support for Symfony 7
Expand Down
5 changes: 5 additions & 0 deletions src/frankenphp-symfony/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ Define the environment variable `APP_RUNTIME` for your application.

Dotenv Component is executed after Runtime Component, so APP_RUNTIME must be available in your container.

The `FRANKENPHP_` environment variables are used to configure the runtime. If omitted, the default values are used.

```
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-e FRANKENPHP_LOOP_MAX=500 \
-e FRANKENPHP_KERNEL_REBOOT=never \
-v $PWD:/app \
-p 80:80 -p 443:443 \
dunglas/frankenphp
Expand All @@ -40,3 +44,4 @@ return function (array $context) {
## Options

* `frankenphp_loop_max`: the number of requests after which the worker must restart, to prevent weird memory leaks (default to `500`, set to `-1` to never restart)
* `frankenphp_kernel_reboot`: whether the kernel should be rebooted after a request (default to `never`, set to `always` to reboot on each request)
6 changes: 6 additions & 0 deletions src/frankenphp-symfony/src/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\RebootableInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Runtime\RunnerInterface;

Expand All @@ -19,6 +20,7 @@ class Runner implements RunnerInterface
public function __construct(
private HttpKernelInterface $kernel,
private int $loopMax,
private string $kernelReboot,
) {
}

Expand Down Expand Up @@ -48,6 +50,10 @@ public function run(): int
$this->kernel->terminate($sfRequest, $sfResponse);
}

if ($this->kernel instanceof RebootableInterface && ('always' === $this->kernelReboot)) {
$this->kernel->reboot(null);
}

gc_collect_cycles();
} while ($ret && (-1 === $this->loopMax || ++$loops <= $this->loopMax));

Expand Down
4 changes: 3 additions & 1 deletion src/frankenphp-symfony/src/Runtime.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ class Runtime extends SymfonyRuntime
/**
* @param array{
* frankenphp_loop_max?: int,
* frankenphp_kernel_reboot?: string,
* } $options
*/
public function __construct(array $options = [])
{
$options['frankenphp_loop_max'] = (int) ($options['frankenphp_loop_max'] ?? $_SERVER['FRANKENPHP_LOOP_MAX'] ?? $_ENV['FRANKENPHP_LOOP_MAX'] ?? 500);
$options['frankenphp_kernel_reboot'] = (string) ($options['frankenphp_kernel_reboot'] ?? $_SERVER['FRANKENPHP_KERNEL_REBOOT'] ?? $_ENV['FRANKENPHP_KERNEL_REBOOT'] ?? 'never');

parent::__construct($options);
}

public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof HttpKernelInterface && ($_SERVER['FRANKENPHP_WORKER'] ?? false)) {
return new Runner($application, $this->options['frankenphp_loop_max']);
return new Runner($application, $this->options['frankenphp_loop_max'], $this->options['frankenphp_kernel_reboot']);
}

return parent::getRunner($application);
Expand Down
22 changes: 20 additions & 2 deletions src/frankenphp-symfony/tests/RunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\RebootableInterface;
use Symfony\Component\HttpKernel\TerminableInterface;

interface TestAppInterface extends HttpKernelInterface, TerminableInterface
interface TestAppInterface extends HttpKernelInterface, TerminableInterface, RebootableInterface
{
}

Expand All @@ -34,10 +35,27 @@ public function testRun(): void
return new Response();
});
$application->expects($this->once())->method('terminate');
$application->expects($this->never())->method('reboot');

$_SERVER['FOO'] = 'bar';

$runner = new Runner($application, 500);
$runner = new Runner($application, 500, 'never');
$this->assertSame(0, $runner->run());
}

public function testRebootAlways(): void
{
$application = $this->createMock(TestAppInterface::class);
$application
->expects($this->once())
->method('handle')
->willReturnCallback(function (Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response {
return new Response();
});
$application->expects($this->once())->method('terminate');
$application->expects($this->once())->method('reboot');

$runner = new Runner($application, 500, 'always');
$this->assertSame(0, $runner->run());
}
}