[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: wait inside subshell waits for sibling

From: Emanuele Torre
Subject: Re: wait inside subshell waits for sibling
Date: Mon, 24 Oct 2022 16:16:21 +0200

On 24/10/2022, Robert Elz <kre@munnari.oz.au> wrote:
>   | The problem that was described is caused by that
>   | optimisation (not the version that is applied to simple commands that
>   | run external program, but to subshell compound command as mentioned).
> I understand that.
>   | In case you did not understand, the issue is not that "wait inside
>   | subshell waits for sibling", because the process that runs `cat' IS NOT
>   | a sibling of the process that runs `wait', it is a CHILD.
> I understand that, but you are missing the point.   This is where the
> "see below" that appeared earlier applies.   You're thinking of how it
> all exists at the unix process level.   And you're probably right (I don't
> know bash internals at all) that at that level the process substitution is
> a child of the shell environment that is running wait.

It does not make any sense that you keep insisting that this is about
bash internals, or low level system call (especially `wait', `fork',
etc), and I keep having to repeat that this is only caused by


being sematically equivalent, *in bash*, to

    (exec REDIRS; CMDS)
    # N.B. this is actually exactly the same change in behaviour applied
    #      to simple commands that run external programs, but with
    #      subshell compound commands it makes sense to say that it is
    #      equivalent to (exec REDIRS; CMDS) since subshell compound
    #      commands themselves only expand redirections, and not regular
    #      words, and temporary environment assignment words.

which is totally intended.

>   | > That is an entirely different issue, and is working as is supposed
>   | > to work.   That it isn't what some people might expect is irrelevant.
>   |
>   | What do you mean?
> The ineffectiveness of var modifications in redirects is a direct
> result of how they work in general, they've always been (when a
> fork is required) evaluated in the subshell (though it is only barely
> that, just a forked process between the fork and the exec).

That is a modification of how the code works.

> But that's not the way we (or the shell) should be looking at this.  At
> shell script level, the command substitution is not a child of the
> subshell,
> and a wait command in the subshell environment should only see processes
> that were created inside that environment, at the shell script level.
> Any optimisation should be exactly that - retain the precise effects of
> the original code, but run faster (or smaller, or whatever).

I don't get why you accept that:

  'externalcmd' 'a' 'b' > "$(( ++i ))"

is not equivalent to:

  { 'externalcmd' 'a' 'b' ;} > "$(( ++i ))"

but can't accept that, *in bash*, this exact same change in behaviour
to subshell compound commands which.

This behaviour probably originated in ksh (irrelevant note: and was
probably implemented because applying redirection after fork() is easier
and safer, than having to dup() the original file descriptors, applying
the redirections, and finally having to restore the original file
descriptors with dup2() and close(), and the final behaviour is
equivalent "in most cases"), and bash copied it.

But note that that behaviour is even implemented by every sh; for
example, dash doesn't have it, and always runs redirections in the main
shell and then reverts them.

In bash, this same behaviour is also applied, to subshell compound
commands. Which makes sense (if you think that the behaviour for simple
commands that run external programs makes sense, which I don't
necessarily do personally), since they also have a "mandatory" fork()
that can be exploited to prevent the work needed to restore file
descriptors at the end, and is what inspired the simple command
behaviour in the first place.

> If an optimisation alters the way the code works, then it is broken, and
> needs to be fixed, or removed.
> That's as true of a shell as it is of a compiler, or anything else.

Perhaps you are mislead by the use of the word "optimisation" into
thinking that the shell is making an attempt at making the code not
change behaviour, it is not making any attempt at that, and it is
emulating an old (probably) ksh behaviour that allows it to do less work
achieving a similar behaviour most of the times.

    'external' > "$(( ++i ))"
is not equivalent to
    { 'external' ;} > "$(( ++i ))"

nor is
    'external' > "$(printf %s\\n "$PPID")"
equivalent to
    { 'external' ;} > "$(printf %s\\n "$PPID")"

    (builtin echo hi) > "$BASHPID"
    { (builtin echo hi) ;} > "$BASHPID"

>   | bash will always evalute  (CMDS) REDIRS  as  (exec REDIRS; CMDS).
> That's just fine, but it needs to avoid having anything in REDIRS
> affect the execution environment of the subshell.

I can agree that I would also like if bash expanded everything in the
main shell, and only applied the redirection of the expanded values in
the subshell.
That way there would be no change in behaviour, but retain the easier
handling of redirections. I am not sure why it is not implemented that
way, but probably simply because that is not how ksh does it. I would
not be opposed to this behaviour changing to that, since I do not like
that simple command behaviour to begin with, but I do not care much.

I do not agree at all, though, with your idea that variable
modifications *should* not be evaluated in the main shell, but
everything else should: all the expansions, and/or redirections,
regardless of what they are, should be evaluated by the same shell; if
they are not, it simply does not make sense.

>   | To show that this optimisation also affects subshell compound commands,
> Once again, examples showing variable modifications made in redirections
> are 100% irrelevant, and have no bearing on this.

I have used a variable modification in the example simply because it is
easier to show.

The change in behaviour is that all redirections (and expansions) are
evaluated after the fork, so I don't understand why you want to restrict
what is allowed to be effected by this change to just variable
modification, and not process spawned by > "$(cmd)" or < <(cmd), and,
perhaps, also not to expansions that get the current pid like
e.g. > "$BASHPID".

How doesn't that make any sense? And what justfies your reasoning since
also other shells that apply this change in behaviour (ksh) run

    external < <(cmd1) > "$(cmd2)"

with `cmd1' and `cmd2' as child processes of the subshell that ends up
exec()-ing `external'.

Are you just saying that because you are conviced that this is an issue
with the `wait' builtin's handling of process

> Let me give you an example
>       bash -c 'sleep 3 & (wait)'
> how long should that run before you get the next prompt back?
> 3 seconds, or as quickly as the commands involved can be started?
> Note that there is absolutely no reason to actually fork to run the
> wait command, it is the final command, once wait is done, its subshell
> exits, and the script exits - that subshell can be (and in many shells
> is) optimised away.   I don't know about bash.
> But any shell that takes 3 seconds to run that script is broken, as
> (wait) as a shell command is identical to (:) as the subshell cannot
> possibly have any children, and so that wait cannot possibly have
> anything to wait upon.
> That the background sleep might have happened to be started in the
> same shell environment as the wait command is run, such that its
> process is a child of the shell running the wait command is irrelevant.
> wait(2) would wait for it, wait(1) must not.   That's what I meant
> in the previous message about them not being the same thing.

This is all irrelevant, there is absolutely no problem with the `wait'
builtin, the handling of jobs, or any underlying syscall that is
affecting the "different" behaviour of the command in question.

Your example does not show any redirection that would be affected by the
behaviour in question and implies that the "problem" is equivalent to if
`(wait)' incorrectly waited for `sleep 3', or any process "That the
shell happens to have forked other processes for its own reasons" in
that example which is absolutely not the case.

The only reason why

    (: & wait) > >(cat)

behaves like it does *in bash* is that, *in bash*, it is semantically
equivalent to:

        exec > >(cat)
        : &

and you are surprised because you didn't know at least of that behaviour
(and, maybe, also that process substitutions processes count as
background jobs).

Just like one would be surprised to see that

    while [ "$i" -lt 10 ]; do
        sleep 10
        program get_currentstate > "/tmp/file$(( ++i ))"

is an infinite loop, if they did not know of that simple command quirk.

>   | (CMDS) REDIRS  =>  (exec REDIRS; CMDS)  is arguably an incorrect
>   | optimisation (afaik, only bash peforms it; ksh93 performs the simple
>   | command optimisation, but not this one),
> I would agree, but the optimisation could be fixed, it doesn't need
> to be removed.

>   | but that is what is causing the issue,
> I don't care what is causing the issue, that's not my problem, my
> only concern is with correct behaviour.

That is the correct, intended, behaviour. The fact that we both don't
like it is another question.

>   | This is not a problem with the `wait' builtin waiting for a sibling
>   | since `cat' is not its sibling;
> It is.   You cannot optimise away a relationship that exists.  Anything
> that's doing that is reinterpreting the code to mean something different
> than what was written, and consequently is broken, and should be fixed.

Again, that is the exact same for simple commands.

    bash-5.1$ { pstree -p -- "$$" ;} > >(cat)
    bash-5.1$ pstree -p -- "$$" > >(cat)

    bash-5.2$ { pstree -p -- "$$"; } > >(cat)
    bash-5.2$ pstree -p -- "$$" > >(cat)

And it is simply not correct to think that the subshell running `cat'
should be a sibling of the subshell running CMDS (in the command below)
if you know how that is supposed to work *in bash*.

    (CMDS) > >(cat)

If we want to discuss changing the behaviour of this "optimisation" to
make it an actual optimisation that doesn't change the behaviour of the
code. (e.g. by making it evaluate all the expansions in the main shell,
and only the redirections of the expanded values in the subshell as I
proposed earlier.), let's please not pretend that bash is not working as
intended, and let's not say and repeat over and over nonsense like that
there is an issue with the handling of background jobs, or system calls;
with the `wait' builtin in general; saying that the "optimisation" works
just fine for simple commands, but not for subshell compound commands
(they are the exact same "optimisation", and they have the exact same
problems!); nonsensical justifications that variable modifications
should not be evaluated in the main shell, but everything else should;


reply via email to

[Prev in Thread] Current Thread [Next in Thread]