/*
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.util;
import jdk.internal.misc.NativeSignal;
import sun.misc.InnocuousThread;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Signal provides support for responding to and raising platform signals.
* Each signal has a current signal handler.
* The signals supported are implementation and operating system specific.
*
* {@link Signal#register(Consumer) Registering} a handler replaces the
* current handler with the provided handler.
* {@link Signal#unregister(Consumer) Unregistering} a handler restores the
* signal handling to the state before registering the handler.
* The signal handler is initially registered for the signals
* {@code SIGINT}, {@code SIGTERM}, and {@code SIGHUP}.
* The system registered handler terminates the Java Runtime
* by invoking {@link System#exit System.exit} with a
* value of {@code 128 + the signal number}. Other signals are ignored.
* Handlers for signals that are unknown or reserved by the
* Java implementation can not be registered.
*
* Handling and raising signals are sensitive operations. Access to
* Signal instances is restricted via {@code RuntimePermission("handleSignal")}
* if a SecurityManager is installed.
*
* Common signal names that follow ANSI/ISO C conventions include:
*
* - SIGINT - Interrupt as if the user typed {@code Ctrl-C}
*
- SIGTERM - A request for normal termination
*
- SIGHUP - The process is no longer connected to a controlling terminal
*
*
* Signal objects are created based on their names. For example:
*
{@code
* Signal s = Signal.of("SIGINT");
* }
* constructs a Signal for {@code SIGINT}, which is
* typically produced when the user presses {@code Ctrl-C}.
*
* To handle a signal a handler is registered for the Signal.
* For example, to respond to {@code Ctrl-C}:
*
{@code
* Consumer handler = s -> {
* ... // handle SIGINT
* };
* Signal.of("SIGINT").register(handler);
* }
*
* Signal is an interned object. There is a single instance for a particular
* signal name.
*
* Unless otherwise noted, passing a {@code null} argument to any method in this
* class causes a {@link java.lang.NullPointerException NullPointerException}
* to be thrown.
*
*
*
* @implNote Signal handling is asynchronous to the native signal mechanisms.
* The implementation handles the native signal and signals a Java Thread to
* deliver the signal to the registered handler.
* Since the Java signal handler runs in a newly created thread, it may not be
* executed until some time after the native signal handler returns.
*
* @since 9
*/
public class Signal {
/**
* Returns the named Signal.
*
* @param name the name of the signal
* @return the named Signal
* @throws UnsupportedOperationException if the signal is unknown or reserved
* @throws SecurityException if a SecurityManager is installed and
* the {@code RuntimePermission("handleSignal")} is denied
*/
public static Signal of(String name) {
Objects.requireNonNull(name, "name");
Signal signal = SIGNAL_BY_NAME.get(name);
if (signal != null) {
return signal;
}
int number;
if (!name.startsWith("SIG") || name.length() <= 3 ||
(number = findSignal0(name.substring(3))) < 0) {
throw new UnsupportedOperationException("Unknown signal: " + name);
}
signal = SIGNAL_BY_NUMBER.computeIfAbsent(
number,
new Function() {
@Override
public Signal apply(Integer number) {
return new Signal.NativeImpl(name, number);
}
}
);
SIGNAL_BY_NAME.putIfAbsent(name, signal);
return signal;
}
private static final ConcurrentMap SIGNAL_BY_NAME = new ConcurrentHashMap<>(4);
private static final ConcurrentMap SIGNAL_BY_NUMBER = new ConcurrentHashMap<>(4);
private final String name;
private final int number;
volatile HandlerNode handlerNode;
long topNativeHandler;
Signal(String name, int number) {
this.name = name;
this.number = number;
}
/**
* Returns the signal name.
* Signal names are implementation specific.
*
* @return the signal name
*/
public String name() { return name; }
/**
* Returns the signal number.
* Signal numbers are implementation specific.
*
* @return the signal number
*/
public int number() { return number; }
/**
* Raises the signal in the current process.
* A consumer must have been registered for the signal.
* @throws UnsupportedOperationException if handling of the signal is
* not supported by the implementation
*/
public void raise() { raise0(number); }
/**
* Register a handler of the Signal.
* Given handler replaces current signal handler.
*
* @param handler a signal handler
* @throws UnsupportedOperationException if handling of the signal is
* not supported by the implementation
*/
public synchronized void register(Consumer handler) {
Objects.requireNonNull(handler, "handler");
HandlerNode oldNode = handlerNode;
HandlerNode newNode = new HandlerNode(oldNode, handler);
replace(oldNode, newNode);
}
/**
* Unregister a handler of the Signal.
* If given handler is the most recently registered handler for the signal,
* signal handling is restored to the state prior to registering given handler
* and the method returns {@code true}; otherwise the currently registered
* handler is left unchanged and {@code false} is returned.
*
* @param handler a signal handler; non-null
* @return {@code true} if given handler was the most recently registered handler
* and was unregistered, otherwise {@code false}
*/
public synchronized boolean unregister(Consumer handler) {
Objects.requireNonNull(handler, "handler");
HandlerNode oldNode = handlerNode;
if (oldNode != null && !oldNode.isNative() && oldNode.handler == handler) {
replace(oldNode, oldNode.previous);
return true;
} else {
return false;
}
}
void replace(HandlerNode oldNode, HandlerNode newNode) {
assert Thread.holdsLock(this);
if ((oldNode == null || oldNode.isNative()) &&
(newNode != null && !newNode.isNative())) {
// when transitioning from native to java handler, we must
// set handlerNode before setting native handler to avoid
// possible race...
handlerNode = newNode;
long oldNativeHandler;
try {
oldNativeHandler = handle1(newNode.nativeHandler);
} catch (RuntimeException | Error e) {
// ...but must restore it if unsuccessful
handlerNode = oldNode;
throw e;
}
if (oldNode == null) {
// save top-level native handler if replacing the null oldNode
topNativeHandler = oldNativeHandler;
}
} else {
// when transitioning from java to native handler, we must
// set native handler before setting handlerNode to avoid
// possible race; when transitioning from one native handler to another
// or when transitioning from one java handler to another,
// the order is not important...
handle1(newNode == null
? topNativeHandler // restore top-level native handler if
// replacing with null newNode
: newNode.nativeHandler);
handlerNode = newNode;
}
}
long handle1(long nativeHandler) {
long oldNativeHandler = handle0(number, nativeHandler);
if (oldNativeHandler == -1L) {
throw new UnsupportedOperationException(
"Signal already used by VM or OS: " + name);
}
return oldNativeHandler;
}
/*
* Called by the VM to execute Java signal handlers.
*/
private static void dispatch(int number) {
Signal signal = SIGNAL_BY_NUMBER.get(number);
if (signal != null) {
HandlerNode handlerNode = signal.handlerNode;
if (handlerNode != null) {
Consumer handler = handlerNode.handler;
if (handler != null) {
new InnocuousThread(() -> handler.accept(signal))
.start();
}
}
}
}
/**
* Find the signal number, given a name.
*
* @param sigName the signal name
* @return the signal number or -1 for unknown signals.
*/
private static native int findSignal0(String sigName);
/**
* Registers a native signal handler, and returns the old handler.
* Handler values:
* 0 default handler
* 1 ignore the signal
* 2 call back to Signal.dispatch
* other arbitrary native signal handlers
* @param nativeH the index or address of the new signal handler
* @return the previous index or address
*/
private static native long handle0(int sig, long nativeH);
/**
* Raise a given signal number.
* @param sig the signal number to raise
*/
private static native void raise0(int sig);
private static final class HandlerNode {
final HandlerNode previous;
final Consumer handler;
final long nativeHandler;
HandlerNode(HandlerNode previous, Consumer handler) {
this.previous = previous;
this.handler = handler;
this.nativeHandler = 2L;
}
HandlerNode(HandlerNode previous, long nativeHandler) {
this.previous = previous;
this.handler = null;
this.nativeHandler = nativeHandler;
}
boolean isNative() {
return handler == null;
}
}
private static final class NativeImpl extends Signal implements NativeSignal {
NativeImpl(String name, int number) {
super(name, number);
}
@Override
public synchronized void register(long nativeHandler) {
HandlerNode oldNode = handlerNode;
HandlerNode newNode = new HandlerNode(oldNode, nativeHandler);
replace(oldNode, newNode);
}
@Override
public synchronized boolean unregister(long nativeHandler) {
HandlerNode oldNode = handlerNode;
if (oldNode != null && oldNode.isNative() && oldNode.nativeHandler == nativeHandler) {
replace(oldNode, oldNode.previous);
return true;
} else {
return false;
}
}
}
}