#303623 zsh: CHECK_JOBS doesn't work when only one command was run since last Control-D

Package:
zsh
Source:
zsh
Description:
shell with lots of features
Submitter:
Andrew Pimlott
Date:
2012-12-20 23:21:03 UTC
Severity:
minor
#303623#5
Date:
2005-04-07 18:22:33 UTC
From:
To:
When a job is still running and the CHECK_JOBS is set, zsh should only
exit when it receives two exit requests in a row.  However, sometimes it
honors an exit request in other conditions.  One repeatable case is when
the request is a Control-D and there was only one command run since the
last exit request.  The following illustrates (<C-D> stands for pressing
Control-D):

    % vi

    zsh: suspended  vi
    % exit
    zsh: you have suspended jobs.
    % true
    % exit
    zsh: you have suspended jobs.
    % true
    % true
    % <C-D>
    zsh: you have suspended jobs.
    % true
    % <C-D>
    <shell exits>

I have accidentally logged out with my mail open several times because
of this.

Andrew

#303623#8
Date:
2005-04-07 19:10:59 UTC
From:
To:
----- Forwarded message from Andrew Pimlott <andrew@pimlott.net> -----

When a job is still running and the CHECK_JOBS is set, zsh should only
exit when it receives two exit requests in a row.  However, sometimes it
honors an exit request in other conditions.  One repeatable case is when
the request is a Control-D and there was only one command run since the
last exit request.  The following illustrates (<C-D> stands for pressing
Control-D):

    % vi

    zsh: suspended  vi
    % exit
    zsh: you have suspended jobs.
    % true
    % exit
    zsh: you have suspended jobs.
    % true
    % true
    % <C-D>
    zsh: you have suspended jobs.
    % true
    % <C-D>
    <shell exits>

I have accidentally logged out with my mail open several times because
of this.

Andrew
----- End forwarded message -----
#303623#11
Date:
2005-04-07 19:10:59 UTC
From:
To:
----- Forwarded message from Andrew Pimlott <andrew@pimlott.net> -----

When a job is still running and the CHECK_JOBS is set, zsh should only
exit when it receives two exit requests in a row.  However, sometimes it
honors an exit request in other conditions.  One repeatable case is when
the request is a Control-D and there was only one command run since the
last exit request.  The following illustrates (<C-D> stands for pressing
Control-D):

    % vi

    zsh: suspended  vi
    % exit
    zsh: you have suspended jobs.
    % true
    % exit
    zsh: you have suspended jobs.
    % true
    % true
    % <C-D>
    zsh: you have suspended jobs.
    % true
    % <C-D>
    <shell exits>

I have accidentally logged out with my mail open several times because
of this.

Andrew
----- End forwarded message -----
#303623#14
Date:
2005-04-08 00:05:42 UTC
From:
To:
On Apr 7,  3:10pm, Clint Adams wrote:
}
} When a job is still running and the CHECK_JOBS is set, zsh should only
} exit when it receives two exit requests in a row.  However, sometimes it
} honors an exit request in other conditions.  One repeatable case is when
} the request is a Control-D and there was only one command run since the
} last exit request.

The issue here seems to be that zsh doesn't actually receive a ctrl-D
keystroke, but rather that there is a true end-of-file on the tty.
If you try, for example, running zsh and then starting another zsh from
within the first, then when you force the "inner" zsh to exit, the "outer"
one sometimes exits as well.

There may actually be some kind of race condition here, because sometimes
only the "inner" shell exits.  It might even be an xterm bug.

Here's a much simpler example to reproduce the base problem:

zsh -f
% sleep 300 &
% <C-d>
zsh: you have running jobs.
% true
% <C-d>
zsh: warning: 1 jobs SIGHUPed

It only happens with <C-d>, not with the 'exit' builtin, so it may have
to do with an interaction with no_ignore_eof.

#303623#19
Date:
2005-04-09 23:47:00 UTC
From:
To:
Ok, but I don't think this should preclude correct behavior.  My
understanding is that when <C-D> is pressed, the application gets a
zero-length read on the terminal, but that a subsequent read will get
more keyboard input (or another zero-length read if <C-D> is pressed
again).  So zsh should still be able to detect each press of <C-D>
reliably.

Hmm, mysterious.  I should have also said in my message that I think
I've seen unexpected exits in other cases, but the one I posted is the
only one I could reproduce.  It sounds like there is a subtle bug in
zsh.

Right, I should have explained that I gave a longer example to show that
1) exit and <C-D> act differently and 2) running two commands between
<C-D>s behaves as expected.

Andrew

#303623#24
Date:
2005-04-10 01:49:30 UTC
From:
To:
On Apr 9,  4:47pm, Andrew Pimlott wrote:
} Subject: Re: Bug#303623: [andrew@pimlott.net: Bug#303623: zsh: CHECK_JOBS
}
} > The issue here seems to be that zsh doesn't actually receive a ctrl-D
} > keystroke, but rather that there is a true end-of-file on the tty.
}
} Ok, but I don't think this should preclude correct behavior.  My
} understanding is that when <C-D> is pressed, the application gets a
} zero-length read on the terminal

If the TTY driver is interpreting the EOF character, then yes, that is
what happens.  However, zsh supposedly doesn't allow the TTY driver to
interpret <C-d>; it puts the terminal in CBREAK mode, so it receives a
literal ASCII '\04' character, so that it's able to invoke the bindkey
for that, which normally runs delete-char-or-list.

The do-not-exit-when-jobs-are-pending behavior relies on having read a
'\04' when the input buffer is empty.  It's a simulated end-of-file
rather than a real one.

What *appears* to be happening -- I could still be wrong -- is that on
the second C-d the CBREAK setting fails to work as expected and zsh in
fact gets a zero-length read.  When ignoreeof is not set, this causes
the shell to exit.

The point being that although I agree this is not the correct behavior,
the reason for the failure is not what you may think, and therefore the
fix is likely to be in a more obscure part of the C code.  Further, the
workaround in the meantime is to setopt ignoreeof.

#303623#29
Date:
2005-04-10 04:23:22 UTC
From:
To:
Ah, I see, thanks.  However, a strace doesn't seem to show that
happening in this case.  Here are the reads on FD 10 while reproducing
the problem:

    read(10, "\4", 1)                       = 1
    read(10, "t", 1)                        = 1
    read(10, "r", 1)                        = 1
    read(10, "u", 1)                        = 1
    read(10, "e", 1)                        = 1
    read(10, "\n", 1)                       = 1
    read(10, "\4", 1)                       = 1

Andrew

#303623#34
Date:
2009-01-01 17:26:55 UTC
From:
To:
found 303623 4.3.9-1

richih@roadwarrior ~ % zsh-beta -f
roadwarrior% echo $ZSH_VERSION
4.3.9-dev-1-cvs1218
roadwarrior% vim

zsh: suspended  vim
roadwarrior% exit
zsh: you have suspended jobs.
roadwarrior% true
roadwarrior% exit
zsh: you have suspended jobs.
roadwarrior% true
roadwarrior% true
roadwarrior%
zsh: you have suspended jobs.
roadwarrior% true
roadwarrior%
Vim: Caught deadly signal HUP
Vim: Finished.

#303623#39
Date:
2012-04-18 22:38:40 UTC
From:
To:
Hey zsh-workers,

Anyone care to take another stab at this old bug?
http://bugs.debian.org/303623

The entire discussion is in the Debian bug log.  Maybe someone will have
a fresh idea in the new decade. ;-)  I just nuked a session with a bunch
of jobs running. :-(

Andrew

#303623#42
Date:
2012-04-19 04:27:00 UTC
From:
To:
On Apr 18,  4:44pm, Andrew Pimlott wrote:
}
} Anyone care to take another stab at this old bug?
} http://bugs.debian.org/303623

It looks like this behavior has been there since approximately the
beginning of time.  If there was ever an email thread about it, that
thread pre-dates the zsh.org archives that date back to 1995.

However, I think I found some clues, in particular a comment (still
present in Src/jobs.c) that originated in an ancient version of the
shell:

    /* If you immediately type "exit" after "jobs", this      *
     * will prevent zexit from complaining about stopped jobs */

Keep in mind that two exits (or two ctrl+d) in a row are supposed to
cause the shell to exit even if there are running jobs.  In the years
that intervened, code migrated to different files and the *other*
place that sets stopmsg = 2 is now no longer in proximity to that
comment.  That other place is in the handler for ctrl+d.

The rationale seems to be:

1. You type exit (or hit ^D) and the shell says you still have jobs.

2. You type "jobs" to find out what is still running.

3. There's nothing interesting in the jobs list (you just want them
   to die).

4. You type exit (or ^D) again.

5. Zsh deliberately ignores the fact that you ran a command between
   the two exits, and simply goes away without further complaint.

All of this because someone else 17+ years ago was annoyed that the
shell did NOT exit without complaining -- exactly the opposite of the
thing that now annoys you.

I *think*, if we no longer care to provide this particular (mis?)feature,
that it would be OK to remove the "stopmsg = 2" from builtin.c:zexit().
However, I'm not entirely confident there aren't other side-effects.

#303623#45
Date:
2012-04-19 04:37:42 UTC
From:
To:
On Apr 18,  9:27pm, Bart Schaefer wrote:
}
} I *think*, if we no longer care to provide this particular (mis?)feature,
} that it would be OK to remove the "stopmsg = 2" from builtin.c:zexit().
} However, I'm not entirely confident there aren't other side-effects.

One possible side-effect being that the shell does not "try again"
when it gets a *real* EOF as opposed to reading a ctrl+d.  (Events in
the opposite order of my speculation in the earlier discussion in that
bug track.)

#303623#48
Date:
2012-04-19 08:36:45 UTC
From:
To:
It should surely be possible to fix it properly, somehow, so that if
you've just looked at the jobs it will still exit but if you've executed
anything else it won't.

However, I don't think having to type ^D twice in any case is
particularly bad (though I do recall occasions in the completion system
where Sven spent some time adding features to avoid a user having to
type A WHOLE EXTRA CHARACTER).

pws


Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom
More information can be found at www.csr.com. Follow CSR on Twitter at http://twitter.com/CSR_PLC and read our blog at www.csr.com/blog

#303623#51
Date:
2012-04-19 14:40:13 UTC
From:
To:
On Apr 19,  9:36am, Peter Stephenson wrote:
}
} It should surely be possible to fix it properly, somehow, so that if
} you've just looked at the jobs it will still exit but if you've executed
} anything else it won't.

Yes, that would be accomplished by removing stopmsg = 2 from zexit() but
leaving stopmsg = 2 in bin_jobs().

However, as I mentioned in follow-up, I fear the assignment in zexit()
may have an additional purpose, such as forcing the shell to get two
consecutive zero-length reads on SHIN in order to kill jobs and exit.
It's a little tricky to test this and I haven't had a chance yet.

#303623#54
Date:
2012-04-19 21:11:14 UTC
From:
To:
Nice archeology, guys. ;)  I'm grateful to you for checking this out.  I
bet at least a couple other people have experienced this and not known
what hit them.  (Your terminal window is gone, you're in shock: you
probably can't reconstruct how it happened.)

Regarding the idea that "if you've just looked at the jobs it will still
exit", I would argue against that.  It seems like a surprise case to me,
and I'd prefer the extra protection and consistency.  But if that's the
way it's always been meant to work, I can understand it.

Andrew