Why does closing terminal emulator window terminate a bash process with SIGHUP trap changed not to terminate?

In a bash shell in a terminal emulator window of lxterminal, I run

$ trap "echo hello" SIGHUP 
$ kill -s HUP $$
hello
$

and then I close the terminal emulator window.

Does closing the terminal emulator window only cause SIGHUP sent to the controlling process i.e. the bash process?

Since the SIGHUP trap doesn’t terminate the bash process, I expect the bash process isn’t terminated, but why is the bash process actually terminated?

Same thing happen if I change the trap to "" (ignore).

Terminal emulator matters. In bash running in a xterm window, setting trap to "" will make a xterm window not closable, while setting trap to echo hello can still close a xterm window.

Thanks.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

[ I’ll ignore the actual and possible behavior of different terminal emulators; a completely reasonable behavior would be to send a ^D (VEOF) to the pty on a window close / WM_DELETE_WINDOW, instead of tearing it down and causing the process running in it to receive a SIGHUP; the following assumes xterm, which will send a SIGHUP to the shell’s process group in that case ].
The behavior you’re seeing is because of the readline library, which is installing its own signal handlers. If you try the following:

xterm -e bash --noediting

(or dash, zsh or ksh instead of bash --noediting), then run

trap 'echo HUP' HUP

in the terminal, the window will become unclose-able; the shell will just print HUP as expected on trying to close the window; forcefully closing it (eg. with xkill) will cause the shell to exit with EIO errors, which is completely expected, since the pty was torn down.

Here is a simpler testcase for the behavior you’re observing, not involving terminal emulators. Run the following in your terminal:

bash --rcfile <(echo 'trap "echo HUP" HUP')

Then kill -HUP $$ will just print HUP, but (sleep 1; kill -HUP $$) & (or kill -HUP <pid> from another window) will cause the shell to print exit and exit, unless you started it with --noediting (= don’t use readline)

The readline() function as called by bash will install its own signal handlers upon waiting for input from the user, and restore the original handlers upon returning; a SIGHUP while waiting for input from the user will cause it to return NULL, which will be treated as EOF by bash (in the yy_readline_get() function), before having the chance to run the deferred trap handler.

Method 2

Bash also exits when it runs out of input to read. That can happen several ways, common ones being reading the last line of a shell script, the user typing control-D, or… closing the terminal window.

(You could also try bash -i < /dev/null and note how it immediately exits, because it ran out of input).

Method 3

There’s a lot more happening under the covers than just a SIGHUP. For example, closing the terminal window also causes the pty to be closed, so any output or input will return I/O errors.

We can see this by running strace on the bash process when you close the window.

We start with the bash process waiting at the prompt (the pselect()) and then close the window…

% strace -p 1090
strace: Process 1090 attached
pselect6(1, [0], NULL, NULL, NULL, {[], 8}) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=3409, si_uid=500} ---
--- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Interrupted system call)
ioctl(2, TCXONC, TCOON)                 = -1 EIO (Input/output error)
ioctl(0, TCGETS, 0x7ffe1d1734e0)        = -1 EIO (Input/output error)
ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = -1 EIO (Input/output error)

We’re starting to see the I/O errors as bash tries to process the handler…

At this point notice that bash decided to shutdown because it has no control terminal, so it restores all the signal handlers and sends itself another SIGHUP

rt_sigaction(SIGINT, {sa_handler=0x467410, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGTERM, {sa_handler=0x466f10, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGHUP, {sa_handler=0x4640e0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGALRM, {sa_handler=0x4676d0, sa_mask=[HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4bb540, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigaction(SIGWINCH, {sa_handler=0x466f00, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7fe5c31b3060}, {sa_handler=0x4baaa0, sa_mask=[], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7fe5c31b3060}, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
getpid()                                = 1090
kill(1090, SIGHUP)                      = 0
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=1090, si_uid=500} ---

And then the close down process continues (rewriting .bash_history and so on).

So it’s not the initial SIGHUP that terminates the shell, it’s the loss of the pty providing a terminal for input.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x