Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:108973 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 16452 invoked from network); 11 Mar 2020 11:48:25 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 11 Mar 2020 11:48:25 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 8C14A1804DF for ; Wed, 11 Mar 2020 03:09:31 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE, RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-lf1-f54.google.com (mail-lf1-f54.google.com [209.85.167.54]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Wed, 11 Mar 2020 03:09:30 -0700 (PDT) Received: by mail-lf1-f54.google.com with SMTP id b186so1088878lfg.11 for ; Wed, 11 Mar 2020 03:09:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=tK/m2mHuvn5a0tLBGy3YRMfir7Rvyf02D61Qk9WbIzI=; b=jkIdPnteXgs7YxWfeU2SvpseWW0o1d+c5XPwhbAjgQwdQr989ZbUn5pYqPZjr3ss80 RW8P7wJ47uO83G0g8RohFnKk6/dGOyM32JbOhASH4c+WbXULVbLjFLJt6iOCjvKkASZh XbrCL+EEjauwMbTrZKefOhTo3MpPg64Eg8Qw322RlvAGoPklnmjCji5M+ijqWplEQULD 5Lh/D/ssnBWIkFD9IxDuZl2hzmGjbLe7DK1YSqA+8SJ4AhAwoQADXUhTP7E8Jq5DJQ6C cUHgnVWcK4BG6Wk6fdDVenvySSBv5fyWNNLiOtDrE1rwiWqSOTkqXvPlswdKU1zrGfX2 8IXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=tK/m2mHuvn5a0tLBGy3YRMfir7Rvyf02D61Qk9WbIzI=; b=tKdoQYBF9SfGh3NdPIlhdycYsRk/F+Q4F6KKn0b7kmOFqcOMFpIiwG60unNTmmQrwG r4BDP+IuJpmdfAXwTS6spjsxPop2M9JdHIyKgHMEsw/y1kD9s3abQ4vOtA+u7ufcy2tt oR/y7ySGzlM7rdmXrujN3jH6Z71PGguyuLW6fViKYT7GC74Op9Yi8VMCLImdzkuErjrQ DCTPJxSymXWFe85z7mpUaDYnNdvLcqiBF+DPCVVolxaZjWCqxnvx19VYA9A2Na8v0wvY L7E7pu4WuJx2LnL3BXaxHA3Kfuh5PWF7rhkooWuIpum6FlEnERsIq/VMs+uzq9AIY66b DO2w== X-Gm-Message-State: ANhLgQ1G1EHyHRhtR6S3V4HX1xAja0ASiWN6OtUGdxvW3+WsFmqgkVJ5 tv/g4cb4Gjh/eiigQpJ+LMXj7DKeDE8VUDt0LCg= X-Google-Smtp-Source: ADFU+vuRpQ13whNnN+w+iSxXBKWya7d6OjQeB616eru9H2PC1S5YGSXm6cwjZbFtt602g5QDXKExMOCbeGKbKh+tmV8= X-Received: by 2002:ac2:489c:: with SMTP id x28mr910199lfc.190.1583921369601; Wed, 11 Mar 2020 03:09:29 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: Date: Wed, 11 Mar 2020 11:09:13 +0100 Message-ID: To: Marcio Almada Cc: PHP internals Content-Type: multipart/alternative; boundary="000000000000965b6505a0916eee" Subject: Re: [PHP-DEV] exit() via exception From: nikita.ppv@gmail.com (Nikita Popov) --000000000000965b6505a0916eee Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, Oct 11, 2019 at 4:11 PM Nikita Popov wrote: > On Fri, Oct 11, 2019 at 3:47 PM Marcio Almada > wrote: > >> Em sex, 11 de out de 2019 =C3=A0s 08:05, Nikita Popov >> 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 scop= e >> and >> > let the memory manager clean up all the memory it knows about. Anythin= g >> 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 properl= y >> > unwind the stack, executing finally blocks (which are currently skippe= d) >> > 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 usab= le > in a webserver context, while a hypothetical pcntl_exit() would bring dow= n > the server process. As the Python docs mention, the primary use-case woul= d > be exiting from forked processes without going through shutdown, which ha= s > 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 al= l > "exceptions" implement the interface, which is rather odd. It still allow= s > 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 a= nd > 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 --000000000000965b6505a0916eee--