Skip to content

Signal handling omni issue #2473

@casey

Description

@casey

There are a bunch of issues related to how just handles signals. I've been trying to figure out what they are, so I created this issue to collect all the information in one place, and summarize what's going on.

Way back in 2018, in #302, @bheisler opened an issue that just was not waiting for child processes to exit on CTRL-C (which sends SIGINT). This would cause them to be orphaned, and continue writing output to the terminal, and have to be cleaned up manually.

In response to #302, I merged #345, which made just ignore SIGINT, SIGHUP, and SIGTERM while running commands. SIGINT is CTRL-C, SIGHUP is sent when you close the terminal ("hup" is for "hangup"), and SIGTERM is the signal which the kill command sends by default.

This gave just the behavior it has now. When running a long-running command, for example sleep 60, if you hit CTRL-C, just will ignore the generated SIGINT signal, but, depending on your terminal or setup, hitting CTRL-C sends SIGINT to all processes in the foreground process group, so sleep 60 will receive SIGINT which will terminate it, just will see it was terminated with a signal, and just will return with an error reporting that it terminated with a signal.

There appear to be a number of issues with this behavior, reported in:

  • Running Firestore Emulator From Just Prevents Control C #1558 by @LLBlumire. Here, just is used to run gcloud emulators firestore start. When gcloud emulators firestore start is run on its own, CTRL-C stops it, but when run under just, CTRL-C does nothing.

    I'm not entirely sure what's going on here. When you do sleep 60 under just, CTRL-C gets sent to just, which ignores it, but also gets sent to sleep 60, which terminates it.

    What's going on in this case? Why isn't CTRL-C getting sent to the gcloud command? When CTRL-C is hit, it is the system (some combination of the kernel, shell, terminal) which arranges to send SIGINT to the relevant processes, so I'm not sure what just could be doing to interfere with this.

  • Trap signals? #1560 by @OJFord. I'm not sure what's going on here, because the signals mentioned are SIGUSR1, and SIGINFO (generated by CTRL-T), which I don't think just touches at all. However, @stefanchrobot mentions that his app terminates when it receives SIGTERM, but nothing happens if the same command is run under just.

  • SIGHUP seems to not be propagated to inner child processes #1781 by @davoclavo. Here, just is not responding to SIGHUP. This is expected, because just ignores these signals while running child processes. This isn't necessarily bad, because in the normal case, SIGHUP is sent to all members of the process group, which should terminate the child and cause it to exit.

  • Improve signal/interrupt handling #1803 PR by @davoclavo. Reworks the signal handler to actively poll for new signals, and terminate the child when it receives one. Never got merged, I think due it being hard to implement/test on Windows.

I'll also summarize what I know about signals, which may be wildly off.

The relevant signals are:

  • SIGHUP Sent when the terminal is closed.
  • SIGINT Sent on CTRL-C.
  • SIGTERM Sent by default by the kill command.

Currently, just running sleep 60, will close when you hit CTRL-C or close the terminal. This is, I believe, because CTRL-C and closing the terminal not only send the signal to the foreground process, but also to members of the foreground process group. So just ignores the SIGHUP or SIGINT signal, but sleep receives it and terminates, so just, which is waiting for it, sees that it has stopped and terminates it as well.

If you generate SIGHUP, SIGINT, or SIGTERM manually, with kill -s HUP/INT/TERM and send it to just, nothing will happen, because only just is receiving the signal, not the child sleep process.

I think the fact that in some cases, a signal is sent to a single process, and in some cases, all members of a process group, causes confusion, since it means that a SIGINT generated by a CTRL-C will have a different effect from one generated by the kill command, even though it's the same signal.

As far as I know, a parent process is not responsible for "forwarding" signals to child processes, it's the terminal / system which does this, so I don't think just is interfering with that. (The only possible exception to this is the process signal mask, which indicates some signals that should be ignored, and which is inherited by child processes. However, just does not set the signal mask, so this should not be an issue.)

Also, I don't know how this varies between different setups, because there are a lot of variables.

  • Do different terminals handle this differently?
  • What even happens on Windows? Can you CTRL-C just at all on Windows?
  • What about shell background jobs? I.e., just &?
  • What about tmux, zellij, dtach, etc?

Here are my current questions:

  • When is ignoring SIGINT and SIGHUP, just's current behavior, desirable? If SIGINT and SIGHUP are sent with CTRL-C or closing the terminal, they should be sent to all children, and thus everything should get cleaned up. I think it would only be in the case of child processes which don't handle these signals that anything doesn't get cleaned up, so a reasonable argument might be that just shouldn't try to handle this, and it's the fault of the child processes that they aren't responding to these signals.

  • When is ignoring SIGTERM, just's current behavior, desirable? This is a bit different from SIGINT and SIGHUP, since SIGTERM, when sent with kill $JUST_PID, is not sent to child processes, so if just exited, it would leave behind child processes.

  • How can I reproduce examples of just processes not terminating when they should? An example from SIGHUP seems to not be propagated to inner child processes #1781 is with zellij, but I wasn't able to reproduce this. If I create a zellij session, open two tabs, run just with a recipe that calls just sleep, and then close the tab, just sleep is not left running in the background.

  • just ignores SIGINT and SIGHUP. As far as I can tell, this is fine, since SIGINT and SIGHUP should be sent to child processes on CTRL-C and terminal close, so just can just ignore them and wait for children to exit. Are there cases when this is problematic?

  • just ignores SIGTERM, which is sent by kill. As far as I know, this signal only gets sent to just, so it may be surprising that it doesn't stop just. Should we change just to forward SIGTERM to child processes, so that kill $JUST_PID will cause just to exit? (Assuming that child processes respond to SIGTERM by exiting.)

My current thoughts are:

  • I think that just should probably not ignore SIGINT and SIGHUP. These signals are generated by CTRL-C and closing the terminal, respectively, and should be sent to children of just as well. So, unless something is wrong, just and all children should exit, which is what the user wants.

  • I think that just should probably either ignore SIGTERM, or forward it to any running child processes. If the user runs kill $JUST_PID, this only sends SIGTERM to just, so if it exits, it will leave child processes behind.

  • I think the original reason for ignoring SIGINT was not necessarily a good one. In that particular use-case, just was being used to run a test runner which would interpret CTRL-C as a failed test, but continue with further tests. So if just ignored CTRL-C, you could hold it down and it would eventually run out of tests to run and exit. However, most programs exit immediately on CTRL-C, so I don't think it makes sense to optimize for the case that CTRL-C doesn't terminate child processes.

(Sorry for tagging a bunch of people! I wanted to make sure people were aware of the new issue. Feel free to unsubscribe!)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions