Skip to content

Commit 86dd794

Browse files
authored
fix(error): prevent warnings when trying to increase memory for OOM errors (#2063)
1 parent bdba59a commit 86dd794

3 files changed

Lines changed: 171 additions & 1 deletion

src/ErrorHandler.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,15 @@ private function handleFatalError(): void
394394
&& preg_match(self::OOM_MESSAGE_MATCHER, $error['message'], $matches) === 1
395395
) {
396396
$currentMemoryLimit = (int) $matches['memory_limit'];
397+
$newMemoryLimit = $currentMemoryLimit + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue;
397398

398-
ini_set('memory_limit', (string) ($currentMemoryLimit + $this->memoryLimitIncreaseOnOutOfMemoryErrorValue));
399+
// It can happen that the memory limit + increase is still lower than
400+
// the memory that is currently being used. This produces warnings
401+
// that may end up in Sentry. To prevent this, we can check the real
402+
// usage before.
403+
if ($newMemoryLimit > memory_get_usage(true)) {
404+
$this->setMemoryLimitWithoutHandlingWarnings($newMemoryLimit);
405+
}
399406

400407
self::$didIncreaseMemoryLimit = true;
401408
}
@@ -452,6 +459,23 @@ private function handleException(\Throwable $exception): void
452459
$this->handleException($previousExceptionHandlerException);
453460
}
454461

462+
/**
463+
* Set the memory_limit while having no real error handler so that a warning emitted
464+
* will not get reported.
465+
*/
466+
private function setMemoryLimitWithoutHandlingWarnings(int $memoryLimit): void
467+
{
468+
set_error_handler(static function (): bool {
469+
return true;
470+
}, \E_WARNING);
471+
472+
try {
473+
ini_set('memory_limit', (string) $memoryLimit);
474+
} finally {
475+
restore_error_handler();
476+
}
477+
}
478+
455479
/**
456480
* Cleans and returns the backtrace without the first frames that belong to
457481
* this error handler.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
--TEST--
2+
Test that OOM handling does not capture warnings from the memory limit increase attempt
3+
--INI--
4+
memory_limit=67108864
5+
--FILE--
6+
<?php
7+
8+
declare(strict_types=1);
9+
10+
namespace Sentry {
11+
// override the function so that we can trace how often it got invoked
12+
function ini_set(string $option, string $value)
13+
{
14+
if (strtolower($option) !== 'memory_limit') {
15+
return \ini_set($option, $value);
16+
}
17+
18+
$GLOBALS['sentry_test_ini_set_calls'] = ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) + 1;
19+
20+
return \ini_set($option, $value);
21+
}
22+
23+
// override the function so that we can test if the memory gets increased to a value
24+
// that is lower than currently in use
25+
function memory_get_usage(bool $realUsage = false): int
26+
{
27+
return $GLOBALS['sentry_test_memory_get_usage'] ?? \memory_get_usage($realUsage);
28+
}
29+
}
30+
31+
namespace Sentry\Tests {
32+
use Sentry\ErrorHandler;
33+
34+
$vendor = __DIR__;
35+
36+
while (!file_exists($vendor . '/vendor')) {
37+
$vendor = \dirname($vendor);
38+
}
39+
40+
require $vendor . '/vendor/autoload.php';
41+
42+
error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED);
43+
44+
$GLOBALS['sentry_test_memory_get_usage'] = 1;
45+
46+
set_error_handler(static function (int $level): bool {
47+
if (\E_WARNING !== $level) {
48+
return false;
49+
}
50+
51+
$GLOBALS['sentry_test_warning_handler_calls'] = ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) + 1;
52+
53+
return true;
54+
});
55+
56+
$errorHandler = ErrorHandler::registerOnceFatalErrorHandler();
57+
$errorHandler->addFatalErrorHandlerListener(static function (): void {
58+
echo 'Fatal error listener called' . \PHP_EOL;
59+
});
60+
61+
register_shutdown_function(static function (): void {
62+
echo 'Memory limit increase attempts: ' . ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) . \PHP_EOL;
63+
echo 'Warning handler calls: ' . ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) . \PHP_EOL;
64+
});
65+
66+
$foo = str_repeat('x', 1024 * 1024 * 1024);
67+
}
68+
?>
69+
--EXPECTF--
70+
%A
71+
Fatal error listener called
72+
Memory limit increase attempts: 1
73+
Warning handler calls: 0
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
--TEST--
2+
Test that OOM handling skips the memory limit increase when current usage is already higher
3+
--INI--
4+
memory_limit=67108864
5+
--FILE--
6+
<?php
7+
8+
declare(strict_types=1);
9+
10+
namespace Sentry {
11+
// override the function so that we can trace how often it got invoked
12+
function ini_set(string $option, string $value)
13+
{
14+
if (strtolower($option) !== 'memory_limit') {
15+
return \ini_set($option, $value);
16+
}
17+
18+
$GLOBALS['sentry_test_ini_set_calls'] = ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) + 1;
19+
20+
return \ini_set($option, $value);
21+
}
22+
23+
// override the function so that we can test if the memory gets increased to a value
24+
// that is lower than currently in use
25+
function memory_get_usage(bool $realUsage = false): int
26+
{
27+
return $GLOBALS['sentry_test_memory_get_usage'] ?? \memory_get_usage($realUsage);
28+
}
29+
}
30+
31+
namespace Sentry\Tests {
32+
use Sentry\ErrorHandler;
33+
34+
$vendor = __DIR__;
35+
36+
while (!file_exists($vendor . '/vendor')) {
37+
$vendor = \dirname($vendor);
38+
}
39+
40+
require $vendor . '/vendor/autoload.php';
41+
42+
error_reporting(\E_ALL & ~\E_DEPRECATED & ~\E_USER_DEPRECATED);
43+
44+
$GLOBALS['sentry_test_memory_get_usage'] = (64 * 1024 * 1024) + (5 * 1024 * 1024) + 1;
45+
46+
set_error_handler(static function (int $level): bool {
47+
if (\E_WARNING !== $level) {
48+
return false;
49+
}
50+
51+
$GLOBALS['sentry_test_warning_handler_calls'] = ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) + 1;
52+
53+
return true;
54+
});
55+
56+
$errorHandler = ErrorHandler::registerOnceFatalErrorHandler();
57+
$errorHandler->addFatalErrorHandlerListener(static function (): void {
58+
echo 'Fatal error listener called' . \PHP_EOL;
59+
});
60+
61+
register_shutdown_function(static function (): void {
62+
echo 'Memory limit increase attempts: ' . ($GLOBALS['sentry_test_ini_set_calls'] ?? 0) . \PHP_EOL;
63+
echo 'Warning handler calls: ' . ($GLOBALS['sentry_test_warning_handler_calls'] ?? 0) . \PHP_EOL;
64+
});
65+
66+
$foo = str_repeat('x', 1024 * 1024 * 1024);
67+
}
68+
?>
69+
--EXPECTF--
70+
%A
71+
Fatal error listener called
72+
Memory limit increase attempts: 0
73+
Warning handler calls: 0

0 commit comments

Comments
 (0)