Search code examples
phplinuxsignalsfgetsxterm

Why does registering a handler function for SIGHUP prevent clicking the "X" to close the XTerm window when waiting for input in PHP CLI?


Linux 6.1. PHP 8.2.7. KDE Plasma 5.x. X11.

I've long had a very annoying issue with my PHP CLI scripts which keeps coming up and causing problems. I've now finally made a minimal test case and hope that this can be solved once and for all.

If you do these three things:

  1. Register a handler function for the SIGHUP signal (which is left empty/does nothing)...
  2. Check for input from the user (fgets(STDIN);)...
  3. Run the PHP CLI script like xterm -e php test.php (which is how I have to call it, for complex reasons unrelated to this problem)...

... then the resulting XTerm terminal emulator window will not be possible to close by clicking the "X" in the top-right corner! Nothing will happen if you click it.

As soon as you comment or remove the line which registers the signal handler, it will let you close the window when run like above.

Test case

  • xterm -e php 1.php = the "X" CANNOT be clicked to make the XTerm window close.
  • xterm -e php 2.php = the "X" CAN be clicked to make the XTerm window close.

1.php:

<?php
    
    function dummy_handler() { /* deliberately left empty */ }
    pcntl_signal(SIGHUP, 'dummy_handler');
    $input = fgets(STDIN);

2.php:

<?php

    function dummy_handler() { /* deliberately left empty */ }
    $input = fgets(STDIN);

What can I do to bypass this? To make it clear, I need to register the handler function, I need to call xterm with -e, and I need to "wait for input". I should also point out that I've tried those classic setsid and nohup Linux tricks without any luck. I don't understand what exactly causes this.

Please don't tell me to "just remember to use Ctrl + C" or something; I need to be able to close certain PHP CLI scripts before their natural life is over, by clicking the "X", and I need to be able to have some cleanup routines called before it closes. (The handler function is empty in this minimal example just to demonstrate that it still happens without me doing anything weird/complex.)

It probably happens regardless of the desktop environment and terminal emulator, but I'm stuck with KDE Plasma and XTerm on Linux and that's what I need this to work on.

Update: Using exit(1); in the handler function doesn't make a difference.


Solution

  • You can solve this by wrapping the command in a shell command:

    xterm -e sh -c "php 1.php"
    

    You can see the behavior using strace (e.g., "strace -fo trace.log -s 1024 xterm", etc):

    • In the non-working case, SIGHUP is ignored by the initial xterm process (xterm creates two processes, the initial one managing the pseudo-terminal and the second one the X window). The signal is passed to the php process (which is caught in the script).
    • In the working case, even though the initial xterm process ignores SIGHUP, the signal is passed to the shell process, which does not ignore it. Killing the shell closes the pseudo-terminal and stops the xterm.

    The whole log is long, but just the lines with SIGHUP (and referring to the whole log to determine which process-id is which) tells the story. Here is the extract from the non-working case (2209 is initial):

    2212  rt_sigaction(SIGHUP, {sa_handler=SIG_IGN, sa_mask=[HUP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f7719fd0050}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
    2212  rt_sigaction(SIGHUP, {sa_handler=SIG_DFL, sa_mask=[HUP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f7719fd0050},  <unfinished ...>
    2209  rt_sigaction(SIGHUP, {sa_handler=SIG_IGN, sa_mask=[HUP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f7719fd0050},  <unfinished ...>
    2212  rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
    2212  rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
    2212  rt_sigaction(SIGHUP, {sa_handler=0x5567ef0cdeb0, sa_mask=~[ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7fb3ba001050}, NULL, 8) = 0
    2212  read(3, "<?php\n    \n    function dummy_handler() { /* deliberately left empty */ }\n    pcntl_signal(SIGHUP, 'dummy_handler');\n    $input = fgets(STDIN);\n", 4096) = 144
    2212  rt_sigaction(SIGHUP, {sa_handler=0x5567ef0cdeb0, sa_mask=~[ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7fb3ba001050}, NULL, 8) = 0
    2209  kill(-2212, SIGHUP)               = 0
    2212  --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=2209, si_uid=1001} ---
    2209  kill(-2212, SIGHUP)               = 0
    2212  --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=2209, si_uid=1001} ---
    2212  rt_sigaction(SIGHUP, {sa_handler=0x5567ef0cdeb0, sa_mask=~[ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags=SA_RESTORER|SA_INTERRUPT|SA_SIGINFO, sa_restorer=0x7fb3ba001050}, NULL, 8) = 0
    2209  kill(-2212, SIGHUP)               = -1 ESRCH (No such process)
    

    and the working case (2219 is initial):

    2220  rt_sigaction(SIGHUP, {sa_handler=SIG_IGN, sa_mask=[HUP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f6b9ec83050}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
    2220  rt_sigaction(SIGHUP, {sa_handler=SIG_DFL, sa_mask=[HUP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f6b9ec83050},  <unfinished ...>
    2219  rt_sigaction(SIGHUP, {sa_handler=SIG_IGN, sa_mask=[HUP], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f6b9ec83050},  <unfinished ...>
    2221  rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
    2221  rt_sigaction(SIGHUP, NULL, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
    2221  rt_sigaction(SIGHUP, {sa_handler=0x55e3610ddeb0, sa_mask=~[ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags=SA_RESTORER|SA_SIGINFO, sa_restorer=0x7f8e6a25b050}, NULL, 8) = 0
    2221  read(3, "<?php\n    \n    function dummy_handler() { /* deliberately left empty */ }\n    pcntl_signal(SIGHUP, 'dummy_handler');\n    $input = fgets(STDIN);\n", 4096) = 144
    2221  rt_sigaction(SIGHUP, {sa_handler=0x55e3610ddeb0, sa_mask=~[ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags=SA_RESTORER|SA_RESTART|SA_SIGINFO, sa_restorer=0x7f8e6a25b050}, NULL, 8) = 0
    2219  kill(-2220, SIGHUP)               = 0
    2221  --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=2219, si_uid=1001} ---
    2220  --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=2219, si_uid=1001} ---
    2220  +++ killed by SIGHUP +++
    2219  --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=2220, si_uid=1001, si_status=SIGHUP, si_utime=0, si_stime=0} ---
    2221  --- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL} ---
    2219  kill(-2220, SIGHUP)               = 0
    2221  --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=2219, si_uid=1001} ---
    2221  rt_sigaction(SIGHUP, {sa_handler=0x55e3610ddeb0, sa_mask=~[ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags=SA_RESTORER|SA_INTERRUPT|SA_SIGINFO, sa_restorer=0x7f8e6a25b050}, NULL, 8) = 0