Tracing exit()

php.internals

Bishop Bettini

10 years ago
Hi! exit (and its doppelganger die) is a hard stop to the engine and there is little telemetry provided about the circumstances (file, line, message, and code). In source I control, exit is no big deal: I don't use exit! But in library code, exit can be frustrating. register_shutdown_function + debug_backtrace doesn't help, because the trace doesn't flow out of the shutdown function. xdebug helps to find the exit frame, but cannot pinpoint the exact line. It seems like the engine could help with a little extra telemetry. I'm wondering if the shutdown functions could access telemetry: <?php register_shutdown_function(function () { $context = shutdown_get_context(); /** array ( 'exit' => array ('file' => '/path/to/Foo.php', 'line' => 242, 'message' => "Calling exit() because...", 'code' => 0)) */ // different SAPI may expand on this context }); require 'vendor/autoload.php'; \Vendor\Package\Class::callsExit(); ?> Or, alternatively, I wonder if a method to convert an exit to an exception would be better: <?php echo ini_get('zend.exit_exception'); // "1" try { require 'vendor/autoload.php'; \Vendor\Package\Class::callsExit(); } catch (\ExitException $ex) { // extends \RuntimeException echo 'Stop that!'; } ?> (In all these examples, "callsExit" is vendor code that performs an undesirable exit();) This latter approach feels more modern, at least from a user perspective, but it has the side effect of making exit recoverable. IMO, that's a good thing, because the conditions under which libraries exit may merely be exceptional for consuming applications. However, I'm uncertain of an "exit exception" implementation. Perhaps when INI enabled, zend_compile_exit could rewrite to emit ZEND_THROW with a synthetic node. Or all the ZEND_EXIT could be updated to throw instead of bailout. Don't know. TL;DR: Engine support for tracing/trapping/debugging exit helps developers find and avoid hard exits in dependent code they don't control. Thoughts on proceeding with an RFC? bishop

David Walker

10 years ago
On Thu, Aug 4, 2016 at 12:11 PM Bishop Bettini <bishop@php.net> wrote:
> Hi! > > exit (and its doppelganger die) is a hard stop to the engine and there is > little telemetry provided about the circumstances (file, line, message, and > code). In source I control, exit is no big deal: I don't use exit! But in > library code, exit can be frustrating. > > register_shutdown_function + debug_backtrace doesn't help, because the > trace doesn't flow out of the shutdown function. xdebug helps to find the > exit frame, but cannot pinpoint the exact line. It seems like the engine > could help with a little extra telemetry. > > I'm wondering if the shutdown functions could access telemetry: > > <?php > register_shutdown_function(function () { > > $context = shutdown_get_context(); > > /** array ( 'exit' => array ('file' => '/path/to/Foo.php', 'line' => > 242, 'message' => "Calling exit() because...", 'code' => 0)) */ > // different SAPI may expand on this context > }); > > require 'vendor/autoload.php'; > \Vendor\Package\Class::callsExit(); > ?> > > Or, alternatively, I wonder if a method to convert an exit to an exception > would be better: > > <?php > echo ini_get('zend.exit_exception'); // "1" > > try { > require 'vendor/autoload.php'; > \Vendor\Package\Class::callsExit(); > } catch (\ExitException $ex) { // extends \RuntimeException > echo 'Stop that!'; > } > ?> > > (In all these examples, "callsExit" is vendor code that performs an > undesirable exit();) > > This latter approach feels more modern, at least from a user perspective, > but it has the side effect of making exit recoverable. IMO, that's a good > thing, because the conditions under which libraries exit may merely be > exceptional for consuming applications. > > However, I'm uncertain of an "exit exception" implementation. Perhaps when > INI enabled, zend_compile_exit could rewrite to emit ZEND_THROW with a > synthetic node. Or all the ZEND_EXIT could be updated to throw instead of > bailout. Don't know. > > TL;DR: Engine support for tracing/trapping/debugging exit helps developers > find and avoid hard exits in dependent code they don't control. Thoughts on > proceeding with an RFC? > > bishop >
I'd be supportive of this, as you define it. However, since you're asking for comments, I'd really be supportive of some means in which telemetry of a call could be gathered for more than just exit & die. Rarely, but not never, we have a new-ish developer who's just been given perms to commit/update production and accidentally pushes code with print-to-screen debugging. It would be amazing if this concept could be extended out to general userland functions/functions/constructs. To be able to see what lines a specific call is being made to help track down where offending code might be happening. -- Dave

Dan Ackroyd

10 years ago
Hi Bishop, It would be good to preserve contexts to make debugging easier.
> I wonder if a method to convert an exit to an exception > would be better:
That doesn't sound like a good plan. There are times when exiting almost straight away is the correct thing to do. If we changed exit() to not do that, it would mean there would be a need for a exit_real() that does exit straight away. cheers Dan

Sara Golemon

10 years ago
On Thu, Aug 4, 2016 at 11:10 AM, Bishop Bettini <bishop@php.net> wrote:
> I'm wondering if the shutdown functions could access telemetry: > > <?php > register_shutdown_function(function () { > > $context = shutdown_get_context(); > > /** array ( 'exit' => array ('file' => '/path/to/Foo.php', 'line' => > 242, 'message' => "Calling exit() because...", 'code' => 0)) */ > // different SAPI may expand on this context > }); > > require 'vendor/autoload.php'; > \Vendor\Package\Class::callsExit(); > ?> >
I like this idea! I can easily imagine the frustration of having untrackable calls to exit, and the effort to expose this info for logging is pretty trivial to implement.
> Or, alternatively, I wonder if a method to convert an exit to an exception > would be better: >
Eh... That feels less-obvious to me for the reasons you stated in your OP. I'm not convinced that being able to recover from an \ExitException is a good thing, but I don't have a compelling reason why it's bad, so I'll keep thinking about that.
> However, I'm uncertain of an "exit exception" implementation. Perhaps when > INI enabled, zend_compile_exit could rewrite to emit ZEND_THROW with a > synthetic node. Or all the ZEND_EXIT could be updated to throw instead of > bailout. Don't know. >
I'd probably leave the compile step alone and have the opcode handler switch on the INI setting. One lil' integer compare on an instruction meant to *stop* execution isn't going to be a problem. (You wouldn't have exit being called repeatedly in a loop)
> TL;DR: Engine support for tracing/trapping/debugging exit helps developers > find and avoid hard exits in dependent code they don't control. Thoughts on > proceeding with an RFC? >
+1 from me. -Sara

Christoph Becker

10 years ago
On 05.08.2016 at 01:37, Sara Golemon wrote:
> On Thu, Aug 4, 2016 at 11:10 AM, Bishop Bettini <bishop@php.net> wrote: > >> Or, alternatively, I wonder if a method to convert an exit to an exception >> would be better: > > Eh... That feels less-obvious to me for the reasons you stated in your > OP. I'm not convinced that being able to recover from an > \ExitException is a good thing, but I don't have a compelling reason > why it's bad, so I'll keep thinking about that.
That could be useful for testing. uopz allows to overload ZEND_EXIT (PHP 5 only, not sure why that has been removed in the PHP 7 version), and there's also a respective request for runkit: <https://github.com/zenovich/runkit/issues/49>.
-- Christoph M. Becker