zsh-workers
 help / color / mirror / code / Atom feed
From: Bart Schaefer <schaefer@brasslantern.com>
To: Tycho Kirchner <tychokirchner@mail.de>
Cc: Zsh hackers list <zsh-workers@zsh.org>
Subject: Re: exit_function - strange behavior
Date: Sun, 31 Oct 2021 16:52:58 -0700	[thread overview]
Message-ID: <CAH+w=7ZcGXnFBDwoK2hrgdg0uz7vTaKftCmYQsAhpm0eWdGytQ@mail.gmail.com> (raw)
In-Reply-To: <CAH+w=7bH0sSRzgTOtucm27xGxuUYGNz_U+3S_OFHipm8cR8ZfQ@mail.gmail.com>

On Mon, Oct 25, 2021 at 5:53 PM Bart Schaefer <schaefer@brasslantern.com> wrote:
>
> There's definitely a bug in this somewhere because if the last command
> executed by the final subshell [is] an anonymous function call:
>
> zsh -fc '...; ( () { return 123 } )'
>
> then the zshexit hook is NEVER called.

When an anonymous function (or probably any function) calls "exit", we
pass through this bit of code in builtin.c bin_break():

5719             *
5720             * If we are already exiting... give this all up as
5721             * a bad job.
5722             */
5723            if (stopmsg || (zexit(0, ZEXIT_DEFERRED), !stopmsg)) {
5724            retflag = 1;
5725            breaks = loops;
5726            exit_pending = 1;
5727            exit_level = locallevel;
5728            exit_val = num;

With ZEXIT_DEFERRED, zexit() always bails out here:

5842     /* Positive shell_exiting means we have been here before */
5843     if (from_where == ZEXIT_DEFERRED ||
5844         (shell_exiting++ && from_where != ZEXIT_NORMAL))
5845         return;

We then eventually call zexit(exit_val, ZEXIT_NORMAL) from doshfunc().

If instead the function calls "return 123" and is the last command in
the subshell, we pass through this branch of execcmd_exec():

4239     if (forked) {
4240         /*
4241          * So what's going on here then?  Well, I'm glad you asked.
[...]
4263          */
4264         for (i = 0; i < 10; i++)
4265             if (fdtable[i] != FDT_UNUSED)
4266                 close(i);
4267         closem(FDT_UNUSED, 1);
4268         if (thisjob != -1)
4269             waitjobs();
4270         _realexit();
4271     }

The call to _realexit() bypasses the hook.  This is the expected
behavior for "falling off the end" of a subshell, rather than
explicitly "exit"-ing.

However, in
  zsh -fc '( () { return 123 } )'
the parent shell optimizes away the subshell and we arrive here:

3596             /*
3597              * If we are in a subshell environment anyway, say
we're forked,
3598              * even if we're actually not forked because we know the
3599              * subshell is exiting.  This ensures SHLVL reflects
the current
3600              * shell, and also optimises out any save/restore we'd need to
3601              * do if we were returning to the main shell.
3602              */
3603             if (type == WC_SUBSH)
3604                 forked = 1;

This should not happen when there is an exit trap or an exit hook, I
think?  However, I'm not following the comment reference to SHLVL --
why would it not reflect the correct thing?  I suppose the actual
correct thing is higher up the call stack, where we should not assert
that the subshell is exiting if the parent shell still has traps or
hooks to process, so that we really have forked here.

As to this:

> You can avoid that by having the subshell end with "return 123"
> instead of "exit 123", except for some reason (possibly a bug) when
> the -c option is used in which case return behaves like exit again.

bin_break() treats "return" as synonymous with "exit" here:

5683     case BIN_RETURN:
5684         if ((isset(INTERACTIVE) && isset(SHINSTDIN))
5685             || locallevel || sourcelevel) {
[...]
5699             return lastval;
5700         }
5701         zexit(num, ZEXIT_NORMAL);       /* else treat return as
logout/exit */

To Tycho's original question, this means there is no way to have an
exit hook or trap called exactly when the original shell exits.  You
can't forcibly finish a subshell before it "falls off the end" without
possibly invoking the trap and hook, and you can't even reliably test
$ZSH_SUBSHELL inside the hook, because the parent might optimize out a
fork without decrementing that.

[[ $sysparams[pid] = $$ ]] almost gets there, except that you can't
assure the hook itself won't be skipped by a function that calls exit.


  reply	other threads:[~2021-10-31 23:53 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-25 23:28 Tycho Kirchner
2021-10-26  0:53 ` Bart Schaefer
2021-10-31 23:52   ` Bart Schaefer [this message]
2021-11-01  9:47     ` Tycho Kirchner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CAH+w=7ZcGXnFBDwoK2hrgdg0uz7vTaKftCmYQsAhpm0eWdGytQ@mail.gmail.com' \
    --to=schaefer@brasslantern.com \
    --cc=tychokirchner@mail.de \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).