Re: exit() via exception

From: Date: Sun, 15 Mar 2020 17:03:59 +0000
Subject: Re: exit() via exception
References: 1 2 3 4  Groups: php.internals 
Request: Send a blank email to internals+get-109030@lists.php.net to get a copy of this message
---- En mié, 11 mar 2020 11:09:13 +0100 Nikita Popov <nikita.ppv@gmail.com> escribió ---- > On Fri, Oct 11, 2019 at 4:11 PM Nikita Popov <nikita.ppv@gmail.com> wrote: > > > On Fri, Oct 11, 2019 at 3:47 PM Marcio Almada <marcio.web2@gmail.com> > > wrote: > > > >> Em sex, 11 de out de 2019 às 08:05, Nikita Popov > >> <nikita.ppv@gmail.com> escreveu: > >> > > >> > Hi, > >> > > >> > >> Hello :) > >> > >> > Currently exit() is implemented using bailout and unclean shutdown, > >> which > >> > means that we're going to perform a longjmp back to the top-level scope > >> and > >> > let the memory manager clean up all the memory it knows about. Anything > >> not > >> > allocated using ZMM is going to leak persistently. > >> > > >> > For me, one of the most annoying things about this is that we can't > >> perform > >> > proper leak checks on code using PhpUnit, because it will always exit() > >> at > >> > the end, which will result in "expected" memory leaks. > >> > > >> > I think it would be good to switch exit() to work by throwing a magic > >> > exception, similar to what Python does. This would allow us to properly > >> > unwind the stack, executing finally blocks (which are currently skipped) > >> > and perform a clean engine shutdown. > >> > > >> > Depending on the implementation, we could also allow code to actually > >> catch > >> > this exception, which may be useful for testing scenarios, as well as > >> > long-running daemons. > >> > > >> > I'm mainly wondering how exactly we'd go about integrating this in the > >> > existing exception hierarchy. > >> > >> > Assuming that it is desirable to allow people > >> > to actually catch this exception > >> > my first thought would be along these > >> > lines: > >> > > >> > Throwable (convert to abstract class) > >> > \-> Exception > >> > \-> Error > >> > \-> ExitThrowable > >> > > >> > This does mean though that existing code using catch(Throwable) is > >> going to > >> > catch exit()s as well. This can be avoided by introducing *yet another* > >> > super-class/interface above Throwable, which is something I'd rather > >> avoid. > >> > > >> > >> Since you brought python as inspiration, I believe the hierarchy goes > >> like this on their land: > >> > >> BaseException > >> +-- SystemExit > >> +-- KeyboardInterrupt > >> +-- GeneratorExit > >> +-- Exception > >> +-- [kitchen sink] > >> > >> Being BaseException the base class for all built-in > >> exceptions. It > >> is not meant to be directly > >> inherited by user-defined classes. It 's the equivalent to our > >> Throwable situation. In this context > >> ExitThrowable -> Throwable appears legit. > >> > >> > > >> > Anyone have thoughts on this matter? > >> > > >> > >> Yes. There is an obvious can of worms if I've got this right: > >> exit() > >> and die() would no longer guarantee a > >> program to actually terminate in case catching > >> ExitThrowable is > >> allowed. Python solves this by actually > >> having two patterns: > >> > >> 1. quit(), exit(), > >> sys.exit() are the equivalent to `raise > >> SystemExit`, can be caught / interrupted > >> 2. os._exit(), can't be caught but has a > >> callback mechanism like our > >> register_shutdown_function, > >> see https://docs.python.org/3/library/atexit.html > > > > > > I don't believe atexit applies to os._exit(). In any case, I agree that > > this is something we're currently missing -- we should probably add a > > pcntl_exit() for this purpose. It should be noted though that this is > > really very different from exit(), which is still quite graceful and usable > > in a webserver context, while a hypothetical pcntl_exit() would bring down > > the server process. As the Python docs mention, the primary use-case would > > be exiting from forked processes without going through shutdown, which has > > also recently come up in > > https://github.com/php/php-src/pull/4712. > > > > > >> If we bind exit() and > >> die() to a catchable exception how would we > >> still have the scenario 2 available > >> on PHP land without a BCB? :) > >> > > > >> I have one simple suggestion: Introduce EngineShutdown -> > >> Throwable, > >> bind exit|die to it but disallow > >> catch(\EngineShutdown $e) at compile time. This would > >> allow keeping > >> backwards compatibility to > >> scenario 2 without messing with our current exception hierarchy. > >> > > > > I think the options are basically: > > > > 1. Making EngineShutdown implement Throwable, which would make existing > > catch(Throwable) catch it -- probably a no-go. > > > > 2. Making EngineShutdown not implement Throwable, which means that not all > > "exceptions" implement the interface, which is rather odd. It still allows > > explicitly catching the exit. > > > > 3. Introducing a function like catch_exit(function() { ... }). This would > > still allow catching exits (for phpunit + daemon use cases), but the fact > > that this is actually implemented based on an exception would be hidden and > > the only way to catch the exit is through this function. > > > > 4. Don't allow catching exits at all. In this case the exception is just > > an implementation detail. > > > > I've started implementing variant 4 in > https://github.com/php/php-src/pull/5243. It uses an internal > exception > type to implement exit() that cannot be caught. > > Finally blocks do get executed, as they should be. This does mean that it > is possibly to discard the exit through finally, because control flow > performed in finally always wins: > > try { > exit; > } finally { > (null)->method(); // throw Error > } > > This snippet will throw an Error exception, with the original exit being > discarded. > > try { > try { > exit; > } finally { > (null)->method(); // throw Error > } > } catch (Error $e) { > // Ignore > } > > This snippet will catch the Error exception and thus stops unwinding. > > I believe this behavior is correct and expected (by which I mean: > consistent with finally semantics), but it does mean that exits can be > discarded even without having an explicit "catch_exit" function or similar. > > Nikita > Hello, About this subject: Nowadays, register_shutdown_function* function exists in order to custom your the end of program. However, with this code: --- CODE --- <?php register_shutdown_function( function() { echo "\nIn register_shutdown_function"; new Exception("Exception"); }); try { echo "\nIn Try"; exit(); }catch (\Throwable $e) { echo "\nIn Catch"; } --- /CODE --- Out is: --- OUT --- In Try In register_shutdown_function --- /OUT --- Maybe, The solution to exit with exception is that script ends only if all callbacks of register-shutdown-function are ended and It's also necessary that context is keeped. In this way, previous code output should be: --- OUT --- In Try In register_shutdown_function In Catch --- /OUT --- This would keep backward compatibility. References: *https://www.php.net/manual/en/function.register-shutdown-function.php Regards -- Manuel Canga

« previous php.internals (#109030) next »