[php-src] Issue #8164: PHP8.1.3. opcache bug with monolog/monolog

From: Date: Thu, 21 Apr 2022 10:39:33 +0000
Subject: [php-src] Issue #8164: PHP8.1.3. opcache bug with monolog/monolog
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-241117@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/8164 Comment Author: Levivb Stepping in here since we're seeing this same message in excess in production. I've been chasing this bug for the last two weeks now and as far as I can tell, the bug (whatever it is) is very much real. It's nearly impossible to reproduce locally and even in production it irregularly happens. Daily, but not predictable. I'll try to provide some insights which might (or mightn't) help getting to the source of the issue. It's going to be a bit of lengthy post though. For clarity I'll focus on Monolog\ DateTimeImmutable class not found during the logging of a Deprecation notice _'class not found' errors are also triggered for Illuminate\Log\LogManager, Illuminate\Log\Logger_ --- We're running several Laravel 8.8x applications in Kubernetes which were recently upgraded from php 8.0 to php 8.1.4. Ever since the upgrade to php 8.1.4, our sentry looks like this ![Screenshot 2022-04-21 at 00-35-44 Issues - radancy-nl - Sentry](https://user-images.githubusercontent.com/2494320/164336196-ee7cab5f-7d1d-43bb-bea6-f982afe7e682.png) Unfortunately this amount of errors is pretty much nuking our budget in sentry :| The origin of the needs for logging is known, the 'missing' classes however, is not. The LivenessProbe and ReadinessProbe in our pods point to a Laravel api endpoint which returns a {"status":"running smooth"} json response. Nothing fancy. One of the request middleware classes logs each request to a file during the termination of the request. Most of the time, this works as expected. But every now and then, Monolog\DateTimeImmutable class not found error occurs. Whenever that happens, the exception handling of Laravel is triggered, which also uses the Monolog\DateTimeImmutable class to construct the message being written to laravel.log. Since the original 'class not found' error is still occurring, the exception handler crashes, causing a new exception handling flow. Effectively triggering an infinite loop causing sentry to be flooded with reports. Anyway, the request handling will crash causing the Kubernetes probes to fail. During this time, request from legitimate traffic will fail for the same reason. After a while, Kubernetes will kill the unhealthy pods (ending the infinite loop), start new pods and the problem is gone (until it happens again). My colleague managed to catch one of the failures in the act and shot these screenshots of the failing pods running in production: Stacktrace: <img width="1707" alt="Screenshot 2022-04-19 at 06 41 14" src="https://user-images.githubusercontent.com/2494320/164414708-6a561e52-0051-4384-92a5-85ec7f884899.png"> Deprecation notices which are also logged using the logging, causing the same class not found loop error. <img width="1719" alt="Screenshot 2022-04-19 at 06 40 32" src="https://user-images.githubusercontent.com/2494320/164414768-5dfab711-8a9b-4ede-a416-3bfcf8d32c6c.png"> Error handlers are registered in https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L45 **Stack trace through Github links when Monolog\ DateTimeImmutable is not found:** - private code calls $logger->debug() - debug is called on the default stack channel/driver: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L643 - Monolog\Logger::error is called: https://github.com/Seldaek/monolog/blob/2.4.0/src/Monolog/Logger.php#L538 - new DateTimeImmutable is called in Monolog\Logger::addRecord: https://github.com/Seldaek/monolog/blob/2.4.0/src/Monolog/Logger.php#L311 - **Class Monolog\DateTimeImmutable is not found** - handleException is triggered: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L165 - Illuminate\Foundation\Exceptions\Handler::report: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Exceptions/Handler.php#L247 - LogManager::error: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L180 - error is called on the default stack channel/driver: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L590 - Monolog\Logger::error is called: https://github.com/Seldaek/monolog/blob/2.4.0/src/Monolog/Logger.php#L538 - new DateTimeImmutable is called in Monolog\Logger::addRecord: https://github.com/Seldaek/monolog/blob/2.4.0/src/Monolog/Logger.php#L311 And we're back at square one. The DateTimeImmutable class is not found. Causing an exception in the exception handling, causing an infinite loop. **Stack trace through Github links when Illuminate\Log\Logger is not found:** - handleError is triggered: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L68 - Error is deprecation: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L71 - Deprecation handling is called: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L87 - Deprecation log channel is being created: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L104 - channel call: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L101 - channel call forwards to driver: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L112 - driver forwards to get: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L125 - with callback runs new Logger: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L126 - Logger is not found. Falling back to Emergency logger in the catch: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L129 - Emergency logger also needs Logger: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L180 - handleException is called: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php#L165 - Illuminate\Foundation\Exceptions\Handler::report: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Foundation/Exceptions/Handler.php#L247 - LogManager::error: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L180 - which creates the stack driver. also needing the Logger class: https://github.com/laravel/framework/blob/v8.83.9/src/Illuminate/Log/LogManager.php#L126 And we're back at square one. The Logger class is not found. An attempt is made to create the emergency driver. Which also needs the Logger class. Causing an exception in the exception handling, causing an infinite loop. --- **Locally reproduced** (sort of) I did manage to reproduce it locally running php 8.1.4 in [Valet](https://github.com/laravel/valet) (which basically is just nginx + php-fpm installed and ran through brew services) When a breakpoint was hit during a failed exception handing, I got a pretty close look at stack-trace and got to run some code as well. (forgot to create screenshots though :(). Even a class_exists(\Monolog\DateTimeImmutable::class) returns false whenever the bug is triggered. A file_exists to Monolog/DateTimeImmutable.php returns true and the autoload_classmap.php from vendor/composer contains a valid mapping for the expected class(es) ('Monolog\\DateTimeImmutable' => $vendorDir . '/monolog/monolog/src/Monolog/DateTimeImmutable.php',) But it gets even weirder, since we're talking about an infinite loop due a class not found exception I expected to see the stack-trace from the production screenshots. However, what I got was the moment the Monolog\DateTimeImmutable class not found exception triggered, not the handleException was triggered, but the handleError handler registered in the set_error_handler. When stepping into the handleError function, prior function calls in the stack trace showed Monolog\DateTimeImmutable class not found exceptions, but in the handleError the reported error was a deprecation notice from the file https://github.com/jenssegers/model/blob/master/src/Model.php (the deprecation notices are visible in the Deprecation notices screenshot above and are reported during normal website requests on the site). Jenssegers\Model\Model however, is not even loaded nor touched during the handing of the status api endpoint. I emptied the Model.php file, but the error remained. I even removed vendor/jenssegers altogether and still the error remained (also in a new request and a new xdebug session). So even though the model.php file didn't even exist anymore, deprecations from that file were still being reported. Apparently requests to unrelated webpages (on the same site) loaded the jenssegers model file and the deprecation error leaked through to separate requests to an api endpoint not using the Jenssegers\Model\Model class. This caused me to reload the php service via brew to check if that would fix the issue. And sure enough, I haven't been able to reproduce the issue locally anymore despite thousands of requests (as I'm writing this message) and artisan commands both with and without --no-dev composer install packages. Both with and without production environment variables and with and without xdebug enabled. Manually triggering a deprecation warning with trigger_error('testing deprecation here --12ba2b--', E_USER_DEPRECATED); also doesn't initiate any class not found infinite loop (deprecation is logged just fine). --- My opcache settings are the same as mentioned by SCIF in https://github.com/php/php-src/issues/8358#issuecomment-1100775251 => <details> <summary>opcache ini</summary> ``` { "opcache.blacklist_filename": { "global_value": "", "local_value": "", "access": 4 }, "opcache.consistency_checks": { "global_value": "0", "local_value": "0", "access": 7 }, "opcache.dups_fix": { "global_value": "0", "local_value": "0", "access": 7 }, "opcache.enable": { "global_value": "1", "local_value": "1", "access": 7 }, "opcache.enable_cli": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.enable_file_override": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.error_log": { "global_value": "", "local_value": "", "access": 4 }, "opcache.file_cache": { "global_value": null, "local_value": null, "access": 4 }, "opcache.file_cache_consistency_checks": { "global_value": "1", "local_value": "1", "access": 4 }, "opcache.file_cache_only": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.file_update_protection": { "global_value": "2", "local_value": "2", "access": 7 }, "opcache.force_restart_timeout": { "global_value": "180", "local_value": "180", "access": 4 }, "opcache.huge_code_pages": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.interned_strings_buffer": { "global_value": "8", "local_value": "8", "access": 4 }, "opcache.jit": { "global_value": "tracing", "local_value": "tracing", "access": 7 }, "opcache.jit_bisect_limit": { "global_value": "0", "local_value": "0", "access": 7 }, "opcache.jit_blacklist_root_trace": { "global_value": "16", "local_value": "16", "access": 7 }, "opcache.jit_blacklist_side_trace": { "global_value": "8", "local_value": "8", "access": 7 }, "opcache.jit_buffer_size": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.jit_debug": { "global_value": "0", "local_value": "0", "access": 7 }, "opcache.jit_hot_func": { "global_value": "127", "local_value": "127", "access": 4 }, "opcache.jit_hot_loop": { "global_value": "64", "local_value": "64", "access": 4 }, "opcache.jit_hot_return": { "global_value": "8", "local_value": "8", "access": 4 }, "opcache.jit_hot_side_exit": { "global_value": "8", "local_value": "8", "access": 7 }, "opcache.jit_max_exit_counters": { "global_value": "8192", "local_value": "8192", "access": 4 }, "opcache.jit_max_loop_unrolls": { "global_value": "8", "local_value": "8", "access": 7 }, "opcache.jit_max_polymorphic_calls": { "global_value": "2", "local_value": "2", "access": 7 }, "opcache.jit_max_recursive_calls": { "global_value": "2", "local_value": "2", "access": 7 }, "opcache.jit_max_recursive_returns": { "global_value": "2", "local_value": "2", "access": 7 }, "opcache.jit_max_root_traces": { "global_value": "1024", "local_value": "1024", "access": 4 }, "opcache.jit_max_side_traces": { "global_value": "128", "local_value": "128", "access": 4 }, "opcache.jit_prof_threshold": { "global_value": "0.005", "local_value": "0.005", "access": 7 }, "opcache.lockfile_path": { "global_value": "\/tmp", "local_value": "\/tmp", "access": 4 }, "opcache.log_verbosity_level": { "global_value": "1", "local_value": "1", "access": 4 }, "opcache.max_accelerated_files": { "global_value": "10000", "local_value": "10000", "access": 4 }, "opcache.max_file_size": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.max_wasted_percentage": { "global_value": "5", "local_value": "5", "access": 4 }, "opcache.memory_consumption": { "global_value": "128", "local_value": "128", "access": 4 }, "opcache.opt_debug_level": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.optimization_level": { "global_value": "0x7FFEBFFF", "local_value": "0", "access": 4 }, "opcache.preferred_memory_model": { "global_value": "", "local_value": "", "access": 4 }, "opcache.preload": { "global_value": "", "local_value": "", "access": 4 }, "opcache.preload_user": { "global_value": "", "local_value": "", "access": 4 }, "opcache.protect_memory": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.record_warnings": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.restrict_api": { "global_value": "", "local_value": "", "access": 4 }, "opcache.revalidate_freq": { "global_value": "2", "local_value": "2", "access": 7 }, "opcache.revalidate_path": { "global_value": "0", "local_value": "0", "access": 7 }, "opcache.save_comments": { "global_value": "1", "local_value": "1", "access": 4 }, "opcache.use_cwd": { "global_value": "1", "local_value": "1", "access": 4 }, "opcache.validate_permission": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.validate_root": { "global_value": "0", "local_value": "0", "access": 4 }, "opcache.validate_timestamps": { "global_value": "1", "local_value": "1", "access": 7 } } ``` </details> --- This is pretty much as far as I can get. It's hard to reproduce and even harder to pinpoint why this is happening. But from the looks of it, it all points to php's opcache :| The sore reality though, is that changing opcache ini settings requires a reload of php-fpm. But reloading php-fpm in and of itself is enough to return to a working code (probably by flushing opcache's cache). Effectively ditching the possibility to debug persistently. Really hoping more information regarding this issue and possible solutions will come to light. Perhaps someone can take a look at the diff between php 8.0 and php 8.1.x regarding opcache? Although I can read a bit of c, it's not really my area of expertise. I reckon the opcache's code is also not the easiest to understand. --- Some other sources which describe the same issue in one form, or another: - https://github.com/laravel/framework/issues/41035 (one of Laravel's main issue reports regarding this issue. I joined there are well with some additional stacktraces which are probably(?) irrelevant here) - https://github.com/php/php-src/issues/8358 - https://github.com/laravel/framework/issues/41070 - https://github.com/laravel/framework/pull/39938 (the 'fix' mentioned in previous issue within the Laravel framework to work around this issue, but doesn't actually solve it) - https://github.com/laravel/sail/issues/286 - https://laracasts.com/discuss/channels/laravel/error-class-illuminatelogeventsmessagelogged-not-found - https://laracasts.com/discuss/channels/laravel/logmanager-not-found - https://laracasts.com/discuss/channels/servers/class-logmanager-not-found-in-logserviceprovider-after-upgrade-to-php81 - https://github.com/laravel/framework/compare/v8.64.0...v8.65.0 (Laravel introduces custom deprecation error handling) - https://github.com/Seldaek/monolog/issues/1626 (original report by OP on Seldaek/monolog)

« previous php.bugs (#241117) next »