Subject: Re: Sane exit from a program on receipt of a signal
To: None <tech-userlevel@NetBSD.org>
From: ITOH Yasufumi <itohy@NetBSD.org>
List: tech-userlevel
Date: 07/30/2007 22:57:31
Hello,

mouse@Rodents.Montreal.QC.CA writes:
> > Hm, before unusual SIGTSTP usages, we should discuss normal SIGTSTP
> > usages.
> 
> > The most normal way to suspend a foreground job (aka process group)
> > is this.
> 
> >  0. Send SIGTSTP to the process group, which do the default behavior.
> >     (such as kill(-pgid, SIGTSTP))
> 
> The "most normal way" does not involve kill(2); the most normal way to
> generate SIGTSTP is to type c_cc[VSUSP].

Yeah.  I wrote it just as an equivalent.

> > If the process group needs some work before suspension, [...]
> 
> Except here, we don't have "the process group" needing work; we have an
> individual process needing work.

Thanks for more precise wording.

> > the most foreground process of the process group
> 
> What is this?  Process group leader?  (What if the SIGTSTP-aware
> process isn't the group leader?)  Process that happens to want to
> handle SIGTSTP?  (What if there are multiple such in the process
> group?)

Usually, only one process that change the tty(4) setting
requires SIGTSTP handling.  I meant the process.
It is the vi process if following command is used.
	sh -c "vi /tmp/foo"

> > will do one from following.
> 
> >  1. Simulate tty SIGTSTP.  If the process wants to suspend, send SIGTSTP
> >     to its own process group.
> >     (such as kill(0, SIGTSTP))
> 
> This is correct if, and only if, no SIGTSTP has already been generated
> to the process group.  An example might be vi's behaviour on receipt of
> ^Z, which vi uses as a suspend command regardless of the user's suspc.

OK, so kill(0, SIGTSTP) code is required anyway.

> >  2. Trap SIGTSTP, do something, reset signal hander and resend
> >     SIGTSTP to itself.
> 
> Right.  This is the case under discussion.  And you said it yourself:
> "to itself".  Not "to its entire process group".
> 
> > A SIGTSTP usage other than above is just a corner case or an
> > accident.
> 
> Agreed.
> 
> > Since 1. is the most often the case that requires SIGTSTP handling,
> > we need kill(0, SIGTSTP) anyway.
> 
> Only when a SIGTSTP to the group hasn't already been generated.
> Occasionally this is what you want, as with the vi example above.  More
> often it's not, as with curses's SIGTSTP code, which is invoked only
> when a SIGTSTP has already been generated.

What we can do in a signal hander is very limited, most tty-based
text editors take 1., as far is I know.
Such programs must not use the proposed function that does
kill(getpid(), SIGTSTP) (or raise(SIGTSTP)).

> > The case 2. we can use either kill(getpid(), SIGTSTP) or kill(0, SIGTSTP).
> > I'd recommend kill(0, SIGTSTP) here since
> >  - we can share the same code with 1.,
> >  - code is simpler,
> >  - it behaves better (for users) in the corner cases.
> 
> I disagree.  Na$B}W(Be users will notice the difference only when the extra
> SIGTSTPs generated by kill-to-group calls make programs behave in ways
> that make no intuitive sense (rare, since it requires non-idempotent
> SIGTSTP handling, but certainly possible).

A UNIX process should not count signals of the same type raised
in succession, since it is unreliable.  Even if two signals of the
same type is sent to a proceess, the process may receive only one.

Hence, an extra SIGTSTP does no harm if the process is sane.

> > OK, then, let's discuss the corner cases.
> 
> >> So?  Just because other software is broken is no reason for us to
> >> write more broken software.  (I wasn't aware of this bug in curses;
> >> it really ought to be fixed.  Thanks for pointing it out.)
> > What is the benefit of ``fixing'' programs to be less safe to users?
> 
> How is it less safe?  I'd say the kill-to-group way is less safe,
> because it risks sending other processes in the group multiple SIGTSTPs
> when they should receive only one.

A UNIX process should not count signals of the same type raised
in succession, so extra SIGTSTPs are safe if the process is sane.

> >>> Stopping only one process of a process group [...]
> >> Right.  So?  As I wrote, if it was generated from the tty, it's
> >> already been sent to the rest of the process group.  If it was
> >> generated with kill(), then either it's already been sent to the
> >> rest of the process group or it shouldn't be sent to the rest of the
> >> process group.
> > As you notice, it is not needed,
> 
> Right.
> 
> > but does no harm.
> 
> Wrong.  *Usually* it does no harm.  It does no harm precisely when all
> affected processes have idempotent SIGTSTP handling.  This is the usual
> case (SIGTSTP stops the process, and SIGTSTP to a stopped process is
> ignored) but it certainly does not have to be so.

Extra signals do no harm if the process is sane.

> > So this is OK.
> 
> Disagree.  It is never necessary and occasionally harmful.  The only

It does no harm as I said above.

> case where it is a good thing is when someone specifically sends a
> SIGTSTP to just one process but expects it to affect the entire process
> group.  This isn't the way default SIGTSTP handling works, so any such
> attempt is broken in general; I prefer this routine behave the same way
> as default SIGTSTP handling to the extent possible, which means *not*
> resending to other processes.

The way you propose is not easy to implement and has no benefit.
Processes can't distinguish between the (possibly) useful "someone
specifically sends a SIGTSTP to just one process" case and
useless other cases, as far as I know.

> >> Yes...if the process happens to be stopped at that point.  Which it
> >> may not be, for any of various reasons.
> > I admit it is a waste, but better than leaving terminal unusable when
> > the SIGTSTP is sent to this process specifically.
> 
> Disagree.  You don't - can't - know that it will result in "leaving
> [the] terminal unusable".

I know of course.
OK, I'm going to explain it for audience.

Here's an example of "sh -c rogue", the processes are

	#process	sleeping at	comment
	base shell	wait		with job control
	sh		wait		no job control (process group leader)
	rogue				the terminal manipulating process

If only the rogue process is suspended, the processes will be

	base shell	wait		waiting for status change of sh
	sh		wait		waiting for child to exit
	rogue				suspended

and the terminal will not respond to user's input.
This is the hang I'm saying.

This hang can be easily avoided if the rogue process (re)send SIGTSTP
to the process group (sh and rogue).  The processes will be

	base shell			back to command line
	sh				suspended
	rogue				suspended

and user will aquire control.  I believe this is safer to users.

FYI, the rogue(6) uses curses(3) and does the latter behavior.
Try "pkill -TSTP rogue" and you can see the job suspends properly.

>                            "Unix does not stop you from doing stupid
> things, because that would also stop you from doing clever things."

Please use SIGSTOP if you like to do stupid things.
Try "pkill -STOP rogue" and you can see the job hangs properly.

Regards,
-- 
ITOH Yasufumi