Skip to content
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
14 changes: 14 additions & 0 deletions src/frankenphp-symfony/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,17 @@ 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_min_memory_mb`: the minimum amount of available memory (in MB) required before handling a new request. If available memory falls below this threshold after the first request, the worker will restart (default to `-1` to disable)

These options can also be configured via environment variables:

```
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-e FRANKENPHP_LOOP_MAX=500 \
-e FRANKENPHP_MIN_MEMORY_MB=64 \
-v $PWD:/app \
-p 80:80 -p 443:443 \
dunglas/frankenphp
```
35 changes: 34 additions & 1 deletion src/frankenphp-symfony/src/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Runner implements RunnerInterface
public function __construct(
private HttpKernelInterface $kernel,
private int $loopMax,
private int $minMemoryMb = -1,
) {
}

Expand Down Expand Up @@ -56,8 +57,40 @@ public function run(): int
}

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

return 0;
}

private function hasAvailableMemory(int $loops): bool
{
if (-1 === $this->minMemoryMb || 1 === $loops) { // Always allow the first cycle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If 1 === $loops, then we have completed the first request. If we are out of memory after the first request, we should restart, right?

I suggest removing this check.

return true;
}

$memoryLimit = \ini_get('memory_limit');
if (-1 === (int) $memoryLimit || '' === $memoryLimit) {
return true;
}
$memoryLimit = \trim($memoryLimit);
if (strlen($memoryLimit) < 2) {
return true; // Unexpected value, consider enough mem available
}
$last = \strtolower($memoryLimit[\strlen($memoryLimit) - 1]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that a valid ini settings is:

memory_limit=4711

This menas 4711 bytes. See https://www.php.net/manual/en/ini.core.php#ini.memory-limit

if (!\in_array($last, ['g', 'm', 'k'])) {
return true; // Unexpected value, consider enough mem available
}

$value = (int) $memoryLimit; // (int) stops at first non-numeric char
$memoryLimitMb = match ($last) {
'g' => $value * 1024,
'k' => (int) ($value / 1024),
default => $value,
};
$usedMemoryMb = \memory_get_usage(true) / 1024 / 1024;
$availableMemoryMb = $memoryLimitMb - $usedMemoryMb;

return $availableMemoryMb >= $this->minMemoryMb;
}
}
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_min_memory_mb?: int,
* } $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_min_memory_mb'] = (int) ($options['frankenphp_min_memory_mb'] ?? $_SERVER['FRANKENPHP_MIN_MEMORY_MB'] ?? $_ENV['FRANKENPHP_MIN_MEMORY_MB'] ?? -1);

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_min_memory_mb']);
}

return parent::getRunner($application);
Expand Down
Loading