symfony: symfony/debug ErrorHandler - infinite loop (?)

Q A
Bug report? yes (?)
Feature request? no
BC Break report? no
RFC? no
Symfony version 3.4.5

We have run into a peculiar issue that only occurs under certain conditions, but due to the nature of a closed-source third party extension, we are not entirely sure what these conditions are. We can only see it happening (from time to time).

We use Sentry to track errors in our application (sentry/sentry@1.8.3 & sentry/sentry-symfony@1.0.0). We also have a new relic extension installed (monitoring agent). Both of these register exception handlers. Most of the time, all is well, the sentry handler handles an exception and reports it to their API. End of process.

But sometimes, we end up in a loop where we see the new relic exception handler appear in the call stack (closed source unfortunately), and at this point we end up with some odd/peculiar circular reference where the sentry handler will see the symfony debug errorhandler as the prev handler, which in turn somehow ends up calling the sentry handler again, and this then repeats infinitely.

screen shot 2018-03-06 at 17 51 20

Normally the callstack/timeline would end at the 1.25s marker. But in this screenshot you can see the loop start after this marker.

Not quite sure what to do with this, or how to provide more useful details 🤔

Original error/exception that gets reported once (in healthy/good flow):

Argument 2 passed to Foo::bar() must be an instance of X, null given, called in Y

Error being logged infinitely (until process is killed, in unhealthy/bad flow):

Error: Uncaught Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Argument 2 passed to Foo::bar() must be an instance of X, null given, called in Y

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 33 (28 by maintainers)

Commits related to this issue

Most upvoted comments

Ok, I may have found the issue here; this is the scenario that I see:

setup

  • Symfony installs its error handler
  • The Sentry bundle kicks in
  • Sentry installs its breadcrumb error handler (source) and it gets Symfony’s error handler as the previous one to be called later
  • Sentry installs its base error & exception handler (source); it gets the breadcrumb error handler as the previous one, and Symfony’s for the exception handler

error handling

  • A fatal is generated
  • Sentry’s error handler kicks in, and checks if there is a previous error handler to be called; it finds its breadcrumb error handler and invokes it (source)
  • Sentry’s breadcrumb error handler does its job, and afterward calls the previous error handler, Symfony’s (source)
  • Symfony’s error handler gets called and returns false here

fatal handling

At this point the error handler’s job is done, but we have an unrecoverable error, and the shutdown functions kicks in:

  • shutdown functions are called in the same order as they where registered, so the first one that gets called is Symfony’s, which inspects previous exceptions handlers
  • it does something that I currently don’t understand fully here…
  • …but I’ve seen that basically it calls $handler->setExceptionHandler($h), where $handler is Symfony’s error handlers and $h is Sentry’s error handler at this line
  • it goes on, calling $handler->handleException(...)
  • it transforms the error into a FatalThrowableError here
  • since he just set it, it calls Sentry’s exception handler, starting the loop (here)

conclusions

IMHO the issue is caused by the confusion due to the changes with the \Throwable interface; Symfony’s error handler calls the exception handlers, creating a loop between Symfony’s fatal handler and the exception handlers. I still have to understand how this could be fixed…

I can reproduce what @julia-morg describes, I removed newrelic entirely from my fork and I also get an infinite loop. This might not be related to newrelic entirely.

https://github.com/istepaniuk/symfony-error-handler-infinite-loop-bug

I’ve reported (like @alcohol) the issue to the NewRelic forum: https://discuss.newrelic.com/t/how-to-disable-newrelic-set-exception-handler/35544/23

Let’s see what happens…

I have the same issue with symfony 4 and sentry-symfony bundle. First, symfony adds its ErrorHandler at Symfony\Bundle\FrameworkBundle\FrameworkBundle:boot() Then Sentry-symfony adds its errorHandler (Raven_ErrorHandler) and stores previous ErrorHandler When the Fatal Error occures, Raven_ErrorHandler handles it, and calls the methods of previous errorHandler But \Symfony\Component\Debug\ErrorHandler::handleFatalError contains instructions, which stores current error handler (Raven_ErrorHandler) as previous errorHandler of \Symfony\Component\Debug\ErrorHandler So, \Symfony\Component\Debug\ErrorHandler handles error, then calls Raven_ErrorHanler. And Raven_ErrorHanler handles error and then calls \Symfony\Component\Debug\ErrorHandler Infinite loop : (

I can confirm that the Linux workaround works, thanks a lot! Since my company is having big issues with this, I’ll be opening a support ticket at NewRelic to ask for a solution.

@alcohol I had some small issues with file permissions, but in the end your repo reproduces the issue, thanks a lot!

@maximecolin yes the problem it’s only on fatals with PHP 7+ due to the \Throwable interface, this issue and https://github.com/getsentry/sentry-php/issues/552 already investigated on this. The root cause seems to be the NewRelic extension messing with the error handlers order, which creates a loop between Symfony’s and Sentry’s (or any other one that correctly attempts to restore the previous error handler, I think).

We dont use newrelic, just symfony + sentry-symfony And require ‘any_missing_file’ causes infinite loop Besides i think that the reason is not on sentry side: Symfony Error Handler attempts to set system error handler to the field as a previous error handler when FatalError occures. You can see it in \Symfony\Component\Debug\ErrorHandler::handleFatalError if ($handler !== $h) { $handler[0]->setExceptionHandler($h); } Thin is the moment when the problem starts: Raven_ErrorHandler is $h, $handler[0] is Symfony ErrorHandler. And Raven_ErrorHandler already contains the Symfony ErrorHandler as a previous ErrorHandler. I made a fork of symfony/debug for my project to stop this inifinite loop - just remove this instruction and problem is gone Im not sure what this code had to do but i cant find any other way to stop thousands of errors in sentry

NewRelic license can be put as a build argument in the Dockerfile, you can use an env variable in the .ini to pass it through.

BTW I just confirmed that disabling the NewRelic extensions solves the issue! I was able to reproduce this only in production don’t know why, but I re-deployed the container without the NewRelic ext and it stopped looping over the fatal error.

My setup:

  • PHP 7.2.3
  • Symfony 3.4.6
  • Sentry client 1.8.3
  • Sentry bundle 2.0.2
  • NewRelic ext 8 (when used)