From 372cf97c98dfc5bf866c2b5befb2ecc85edfb71b Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Fri, 10 Apr 2026 15:58:16 +0200 Subject: [PATCH 1/2] fix(error): prevent warnings when trying to increase memory for OOM errors --- src/ErrorHandler.php | 26 +++++- ...warning_during_out_of_memory_handling.phpt | 92 +++++++++++++++++++ ...ncrease_during_out_of_memory_handling.phpt | 92 +++++++++++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt create mode 100644 tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt diff --git a/src/ErrorHandler.php b/src/ErrorHandler.php index 0839ce32b..82bf28dd0 100644 --- a/src/ErrorHandler.php +++ b/src/ErrorHandler.php @@ -394,8 +394,15 @@ private function handleFatalError(): void && preg_match(self::OOM_MESSAGE_MATCHER, $error['message'], $matches) === 1 ) { $currentMemoryLimit = (int) $matches['memory_limit']; + $newMemoryLimit = $currentMemoryLimit + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue; - ini_set('memory_limit', (string) ($currentMemoryLimit + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue)); + // It can happen that the memory limit + increase is still lower than + // the memory that is currently being used. This produces warnings + // that may end up in Sentry. To prevent this, we can check the real + // usage before. + if ($newMemoryLimit > memory_get_usage()) { + $this->setMemoryLimitWithoutHandlingWarnings($newMemoryLimit); + } self::$didIncreaseMemoryLimit = true; } @@ -452,6 +459,23 @@ private function handleException(\Throwable $exception): void $this->handleException($previousExceptionHandlerException); } + /** + * Set the memory_limit while having no real error handler so that a warning emitted + * will not get reported. + */ + private function setMemoryLimitWithoutHandlingWarnings(int $memoryLimit): void + { + set_error_handler(static function (): bool { + return true; + }, \E_WARNING); + + try { + ini_set('memory_limit', (string) $memoryLimit); + } finally { + restore_error_handler(); + } + } + /** * Cleans and returns the backtrace without the first frames that belong to * this error handler. diff --git a/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt b/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt new file mode 100644 index 000000000..19e891701 --- /dev/null +++ b/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt @@ -0,0 +1,92 @@ +--TEST-- +Test that OOM handling does not capture warnings from the memory limit increase attempt +--INI-- +memory_limit=67108864 +--FILE-- + 'http://public@example.com/sentry/1', + 'transport' => $transport, + 'capture_silenced_errors' => true, + ]); + + register_shutdown_function(static function (): void { + echo 'Transport calls: ' . ($GLOBALS['sentry_test_transport_calls'] ?? 0) . \PHP_EOL; + echo 'Memory limit increase attempts: ' . ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) . \PHP_EOL; + echo 'Warning handler calls: ' . ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) . \PHP_EOL; + }); + + $foo = str_repeat('x', 1024 * 1024 * 1024); +} +?> +--EXPECTF-- +%A +Transport calls: 1 +Memory limit increase attempts: 1 +Warning handler calls: 0 diff --git a/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt b/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt new file mode 100644 index 000000000..3841fa3e0 --- /dev/null +++ b/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt @@ -0,0 +1,92 @@ +--TEST-- +Test that OOM handling skips the memory limit increase when current usage is already higher +--INI-- +memory_limit=67108864 +--FILE-- + 'http://public@example.com/sentry/1', + 'transport' => $transport, + 'capture_silenced_errors' => true, + ]); + + register_shutdown_function(static function (): void { + echo 'Transport calls: ' . ($GLOBALS['sentry_test_transport_calls'] ?? 0) . \PHP_EOL; + echo 'Memory limit increase attempts: ' . ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) . \PHP_EOL; + echo 'Warning handler calls: ' . ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) . \PHP_EOL; + }); + + $foo = str_repeat('x', 1024 * 1024 * 1024); +} +?> +--EXPECTF-- +%A +Transport calls: 1 +Memory limit increase attempts: 0 +Warning handler calls: 0 From 2fdbdb6664b555f3676798c71c919aa8d73e3c5f Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Fri, 10 Apr 2026 16:07:29 +0200 Subject: [PATCH 2/2] fix --- ...warning_during_out_of_memory_handling.phpt | 31 ++++--------------- ...ncrease_during_out_of_memory_handling.phpt | 31 ++++--------------- 2 files changed, 12 insertions(+), 50 deletions(-) diff --git a/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt b/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt index 19e891701..704084278 100644 --- a/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt +++ b/tests/phpt/error_handler_does_not_capture_memory_limit_increase_warning_during_out_of_memory_handling.phpt @@ -29,10 +29,7 @@ namespace Sentry { } namespace Sentry\Tests { - use Sentry\Event; - use Sentry\Transport\Result; - use Sentry\Transport\ResultStatus; - use Sentry\Transport\TransportInterface; + use Sentry\ErrorHandler; $vendor = __DIR__; @@ -56,28 +53,12 @@ namespace Sentry\Tests { return true; }); - $transport = new class implements TransportInterface { - public function send(Event $event): Result - { - $GLOBALS['sentry_test_transport_calls'] = ($GLOBALS['sentry_test_transport_calls'] ?? 0) + 1; - - return new Result(ResultStatus::success()); - } - - public function close(?int $timeout = null): Result - { - return new Result(ResultStatus::success()); - } - }; - - \Sentry\init([ - 'dsn' => 'http://public@example.com/sentry/1', - 'transport' => $transport, - 'capture_silenced_errors' => true, - ]); + $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); + $errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called' . \PHP_EOL; + }); register_shutdown_function(static function (): void { - echo 'Transport calls: ' . ($GLOBALS['sentry_test_transport_calls'] ?? 0) . \PHP_EOL; echo 'Memory limit increase attempts: ' . ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) . \PHP_EOL; echo 'Warning handler calls: ' . ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) . \PHP_EOL; }); @@ -87,6 +68,6 @@ namespace Sentry\Tests { ?> --EXPECTF-- %A -Transport calls: 1 +Fatal error listener called Memory limit increase attempts: 1 Warning handler calls: 0 diff --git a/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt b/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt index 3841fa3e0..7602b07c1 100644 --- a/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt +++ b/tests/phpt/error_handler_skips_impossible_memory_limit_increase_during_out_of_memory_handling.phpt @@ -29,10 +29,7 @@ namespace Sentry { } namespace Sentry\Tests { - use Sentry\Event; - use Sentry\Transport\Result; - use Sentry\Transport\ResultStatus; - use Sentry\Transport\TransportInterface; + use Sentry\ErrorHandler; $vendor = __DIR__; @@ -56,28 +53,12 @@ namespace Sentry\Tests { return true; }); - $transport = new class implements TransportInterface { - public function send(Event $event): Result - { - $GLOBALS['sentry_test_transport_calls'] = ($GLOBALS['sentry_test_transport_calls'] ?? 0) + 1; - - return new Result(ResultStatus::success()); - } - - public function close(?int $timeout = null): Result - { - return new Result(ResultStatus::success()); - } - }; - - \Sentry\init([ - 'dsn' => 'http://public@example.com/sentry/1', - 'transport' => $transport, - 'capture_silenced_errors' => true, - ]); + $errorHandler = ErrorHandler::registerOnceFatalErrorHandler(); + $errorHandler->addFatalErrorHandlerListener(static function (): void { + echo 'Fatal error listener called' . \PHP_EOL; + }); register_shutdown_function(static function (): void { - echo 'Transport calls: ' . ($GLOBALS['sentry_test_transport_calls'] ?? 0) . \PHP_EOL; echo 'Memory limit increase attempts: ' . ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) . \PHP_EOL; echo 'Warning handler calls: ' . ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) . \PHP_EOL; }); @@ -87,6 +68,6 @@ namespace Sentry\Tests { ?> --EXPECTF-- %A -Transport calls: 1 +Fatal error listener called Memory limit increase attempts: 0 Warning handler calls: 0