6

In my Java application I want to capture SIGINTs, do some pre-processing, and then let the default behavior (process termination) run. I would think I could do something like this:

Signal.handle(new Signal("INT"), new SignalHandler() {
  @Override
  public void handle(Signal signal) {
    // preprocessing
    // ...

    // now do default behavior
    SignalHandler.SIG_DFL.handle(signal);
  }
});

However when I send at SIGINT to this application, I get a SEGV:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x0000000000000000, pid=10261, tid=21507
#
# JRE version: Java(TM) SE Runtime Environment (8.0_51-b16) (build 1.8.0_51-b16)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.51-b03 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# C  0x0000000000000000
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /private/tmp/hs_err_pid10261.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
# The crash happened outside the Java Virtual Machine in native code.
# See problematic frame for where to report the bug.
#
Abort trap: 6

It seems SignalHandler.SIG_DFL is not meant to be called directly (even from other signal handling code). So how can I manually trigger it?

Alternatively, how can I manually replicate the behavior of SIG_DFL? It appears to be equivalent to:

System.exit(signal.getNumber() + 128)

but I don't see any documentation to that effect.


Another way to phrase my question:

In practice* is there a difference between these two code blocks?

A)

Signal.handle(new Signal("INT"), SignalHandler.SIG_DFL);

B)

Signal.handle(new Signal("INT"), new SignalHandler() {
  @Override
  public void handle(Signal signal) {
    System.exit(signal.getNumber() + 128)
  }});

*I know undocumented behavior could change at any time, but it's unlikely that the JVM's exit behavior will change mid-version. An answer that simply details what happens now is acceptable, in practice.

dimo414
  • 47,227
  • 18
  • 148
  • 244

3 Answers3

4

I think the key to the mystery here is that SIG_DFL is not the original handler for SIGINT.

The following code worked for me:

    Signal sigInt = new Signal("INT");

    // First register with SIG_DFL, just to get the old handler.
    final SignalHandler oldHandler = Signal.handle(sigInt, SignalHandler.SIG_DFL );

    // Now register the actual handler
    Signal.handle(sigInt, new SignalHandler(){

        @Override
        public void handle(Signal signal) {
            System.err.println("Sigint is being handled");
            oldHandler.handle(signal);
        }

    });

This does not cause the segmentation violation, and instead terminates the program as expected (after printing the sample text).

RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
  • Great idea, I should have tried that! It turns out the object returned from your first call is an instance of [`java.lang.Terminator`](http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/43cb25339b55/src/solaris/classes/java/lang/Terminator.java), and is registered in [`System.initializeSystemClass()`](http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/43cb25339b55/src/share/classes/java/lang/System.java#l1196). And so I think the right thing to do is to avoid using `SignalHandler.SIG_DFL` at all. Now to figure out how to safely get the `Terminator` instance without that first register... – dimo414 Aug 24 '15 at 21:15
  • For reference, here's how I think you can best get the `oldHandler` and pass it to the new one: http://stackoverflow.com/q/32193064/113632 – dimo414 Aug 24 '15 at 23:44
3

Credit for originally noticing this goes to RealSkeptic, but I wanted to expand on it in an answer.

The default behavior for SIGINT, SIGTERM, and SIGHUP is not, in fact, SignalHandler.SIG_DFL. Instead, the java.lang.Terminator class registers a SignalHandler that simply calls Shutdown.exit():

SignalHandler sh = new SignalHandler() {
    public void handle(Signal sig) {
      Shutdown.exit(sig.getNumber() + 0200);
    }
};

You can capture this SignalHandler by calling Signal.handle() (since it returns the old handler), or you can simply define your own handler that calls System.exit() which will do the same thing.

Note that Terminator's call to Shutdown.exit() is not exactly the same as System.exit(). The former is package-private, meaning you can't call it directly. If a security manager prevents you from calling System.exit(), you'll have to capture the original handler and reuse it.

Warning: this is undocumented behavior. It's unlikely but entirely possible that future releases of Java could change this behavior.

dimo414
  • 47,227
  • 18
  • 148
  • 244
  • Actually, I was going through this code now for another answer. Note that it is *not* calling `System.exit`, it is calling `Shutdown.exit`! `Shutdown` is package-private, and differs from `System.exit` by not verifying with the security manager if the JVM is allowed to halt. Thus, you cannot really "define your own handler that does the same thing" because theirs will never be blocked by the security manager, and uses a class not available to you. Better keep the old handler around. :-) – RealSkeptic Aug 31 '15 at 21:22
  • Good eye! Updated my answer. Capturing the old handler without race conditions is [a little tricky](http://stackoverflow.com/a/32193175/113632), but if you can't be confident you control the security manager, it might be worth the extra effort. – dimo414 Aug 31 '15 at 21:53
  • Interesting side effect of this behavior - this gives a user the ability to call `Shutdown.exit()` even when a `SecurityManager` is installed with `checkExit()` defined. You could prevent the malicious user from accessing `sun.misc` via the `accessClassInPackage.sun.misc` `RuntimePermission`, but that's a bigger hammer, and most "prevent calls to `System.exit()`" examples I've seen don't do that. – dimo414 Aug 31 '15 at 22:33
1

Exiting with 128+signal number seems to be typical on Unix/Linux.

For more evidence, see also:

Browsing the source code on OpenJDK suggests that the default behaviour is to allow the underlying C Runtime or OS default action to proceed.

In any case this is only typical behaviour, and as you point out is not documented. Exit codes are not standardized - generally there is a convention that zero means success, and nonzero failure, but even the first part is not always adhered to. Therefore it is for you to define your own exit codes.

Community
  • 1
  • 1
Ben
  • 34,935
  • 6
  • 74
  • 113
  • I'm more concerned with what *else* the signal handler might do than what particular exit code is returned. – dimo414 Aug 24 '15 at 20:43
  • You are exiting. What do you want it to do? The OS will clean up filehandles and network connections. – Ben Aug 24 '15 at 20:47
  • One way to exit a JVM is to call `System.exit()`. It *seems* another way to exit is to send a `SIGINT` or other signal. But what is unclear is if these behaviors are equivalent. Does the default signal handler simply call `System.exit()`? If not, what *does* it do? Is that documented anywhere? – dimo414 Aug 24 '15 at 20:49
  • It turns out the default signal handler does *not* just exit. Not only does Java explicitly register a signal handler for `INT`, `TERM`, and `HUP`, but it uses `Shutdown.exit()` rather than `System.exit()`. – dimo414 Nov 13 '15 at 15:51