Skip to content

Commit

Permalink
Increase the memory limit when handling OOM errors
Browse files Browse the repository at this point in the history
  • Loading branch information
stayallive committed Nov 8, 2023
1 parent 3a33cbb commit ac1cf4c
Showing 1 changed file with 39 additions and 8 deletions.
47 changes: 39 additions & 8 deletions src/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ final class ErrorHandler
*/
public const DEFAULT_RESERVED_MEMORY_SIZE = 10240;

/**
* The regular expression used to match the message of an out of memory error.
*/
private const OOM_MESSAGE_MATCHER = '/^Allowed memory size of (?<memory_limit>\d+) bytes exhausted \(tried to allocate \d+ bytes\)/';

/**
* The fatal error types that cannot be silenced using the @ operator in PHP 8+.
*/
Expand Down Expand Up @@ -89,8 +94,19 @@ final class ErrorHandler
private $isFatalErrorHandlerRegistered = false;

/**
* @var string|null A portion of pre-allocated memory data that will be reclaimed
* in case a fatal error occurs to handle it
* @var int|null the amount of bytes of memory to increase the memory limit by when we are capturing a out of memory error, set to null to not increase the memory limit
*/
private $memoryLimitIncreaseValue = 5 * 1024 * 1024; // 5 MiB

/**
* @var bool Whether the memory limit has been increased
*/
private static $didIncreaseMemoryLimit = false;

/**
* @var string|null A portion of pre-allocated memory data that will be reclaimed in case a fatal error occurs to handle it
*
* @phpstan-ignore-next-line This property is used to reserve memory for the fatal error handler and is thus never read
*/
private static $reservedMemory;

Expand Down Expand Up @@ -254,6 +270,16 @@ public function addExceptionHandlerListener(callable $listener): void
$this->exceptionListeners[] = $listener;
}

/**
* Sets the amount of memory to increase the memory limit by when we are capturing a out of memory error.
*
* @param int|null $valueInBytes the number of bytes to increase the memory limit by, or null to not increase the memory limit
*/
public function setMemoryLimitIncrease(?int $valueInBytes): void
{
$this->memoryLimitIncreaseValue = $valueInBytes;
}

/**
* Handles errors by capturing them through the client according to the
* configured bit field.
Expand Down Expand Up @@ -315,16 +341,21 @@ private function handleError(int $level, string $message, string $file, int $lin
*/
private function handleFatalError(): void
{
// If there is not enough memory that can be used to handle the error
// do nothing
if (null === self::$reservedMemory) {
return;
}

// Free the reserved memory that allows us to potentially handle OOM errors
self::$reservedMemory = null;

$error = error_get_last();

if (!empty($error) && $error['type'] & (\E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_CORE_WARNING | \E_COMPILE_ERROR | \E_COMPILE_WARNING)) {
// If we did not do so already and we are allowed to increase the memory limit, we do so when we detect an OOM error
if (false === self::$didIncreaseMemoryLimit && null !== $this->memoryLimitIncreaseValue && 1 === preg_match(self::OOM_MESSAGE_MATCHER, $error['message'], $matches)) {
$currentMemoryLimit = (int) $matches['memory_limit'];

ini_set('memory_limit', (string) ($currentMemoryLimit + $this->memoryLimitIncreaseValue));

self::$didIncreaseMemoryLimit = true;
}

$errorAsException = new FatalErrorException(self::ERROR_LEVELS_DESCRIPTION[$error['type']] . ': ' . $error['message'], 0, $error['type'], $error['file'], $error['line']);

$this->exceptionReflection->setValue($errorAsException, []);
Expand Down

0 comments on commit ac1cf4c

Please sign in to comment.