/*
* Copyright (c) 2016, Oracle and/or its affiliates.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.oracle.truffle.llvm.nodes.intrinsics.c;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.llvm.runtime.LLVMContext;
import com.oracle.truffle.llvm.runtime.LLVMFunction;
import com.oracle.truffle.llvm.runtime.LLVMFunctionDescriptor;
import com.oracle.truffle.llvm.runtime.LLVMFunctionHandle;
import com.oracle.truffle.llvm.runtime.LLVMLogger;
import com.oracle.truffle.llvm.runtime.LLVMThread;
import com.oracle.truffle.llvm.runtime.nodes.api.LLVMExpressionNode;
import sun.misc.Signal;
import sun.misc.SignalHandler;
@NodeChildren({@NodeChild(type = LLVMExpressionNode.class, value = "signal"), @NodeChild(type = LLVMExpressionNode.class, value = "handler")})
public abstract class LLVMSignal extends LLVMExpressionNode {
@Specialization
public LLVMFunction doSignal(int signal, LLVMFunctionDescriptor handler, @Cached("getContext()") LLVMContext context) {
return setSignalHandler(context, signal, handler);
}
@Specialization
public LLVMFunction doSignal(int signal, LLVMFunctionHandle handler, @Cached("getContext()") LLVMContext context) {
return setSignalHandler(context, signal, context.lookup(handler));
}
private static LLVMFunction setSignalHandler(LLVMContext context, int signalId, LLVMFunctionDescriptor function) {
try {
Signals decodedSignal = Signals.decode(signalId);
return setSignalHandler(context, decodedSignal.signal(), function);
} catch (NoSuchElementException e) {
CompilerDirectives.transferToInterpreterAndInvalidate();
LLVMLogger.error(e.getMessage());
return context.getSigErr();
}
}
private static Lock globalSignalHandlerLock = new ReentrantLock();
private static final Map<Integer, LLVMSignalHandler> registeredSignals = new HashMap<>();
@TruffleBoundary
public static int getNumberOfRegisteredSignals() {
return registeredSignals.size();
}
@TruffleBoundary
private static LLVMFunction setSignalHandler(LLVMContext context, Signal signal, LLVMFunctionDescriptor function) {
int signalId = signal.getNumber();
LLVMFunction returnFunction = context.getSigDfl();
try {
LLVMSignalHandler newSignalHandler = new LLVMSignalHandler(context, signal, function);
synchronized (registeredSignals) {
if (registeredSignals.containsKey(signalId)) {
LLVMSignalHandler currentFunction = registeredSignals.get(signalId);
if (currentFunction.isRunning()) {
returnFunction = currentFunction.getFunction();
/*
* the new signal handler already manages this signal, so we can safely
* deactivate the old one.
*/
currentFunction.setStopped();
}
}
registeredSignals.put(signalId, newSignalHandler);
}
} catch (IllegalArgumentException e) {
LLVMLogger.error("could not register signal with id " + signalId + " (" + signal + ")");
return context.getSigErr();
}
return returnFunction;
}
/**
* Registers a signal handler using sun.misc.SignalHandler. Unfortunately, using signals in java
* leads to some problems which are not resolved in our implementation yet.
*
* One of this issue is, that signals are executed in an asynchronous way, which means raise()
* exits before the signal was handled. Another Issue is that Java already registered some
* signal handlers, which therefore cannot be used in sulong.
*
* Therefore, our implementation does not comply with the ANSI C standard and could lead to
* timing issues when calling multiple signals in a defined sequence, or when a program has to
* wait until the signal was handled (which is not guaranteed because of the asynchronous
* behavior in our implementation).
*/
private static final class LLVMSignalHandler implements SignalHandler, LLVMThread {
private final Signal signal;
private final LLVMContext context;
private final LLVMFunctionDescriptor handler;
private final Lock lock = new ReentrantLock();
private final AtomicBoolean isRunning = new AtomicBoolean(false);
@TruffleBoundary
private LLVMSignalHandler(LLVMContext context, Signal signal, LLVMFunctionDescriptor function) throws IllegalArgumentException {
this.signal = signal;
this.handler = function;
this.context = context;
lock.lock();
try {
if (function.equals(context.getSigDfl())) {
Signal.handle(signal, SignalHandler.SIG_DFL);
isRunning.set(true);
context.registerThread(this);
return;
} else if (function.equals(context.getSigIgn())) {
Signal.handle(signal, SignalHandler.SIG_IGN);
isRunning.set(true);
context.registerThread(this);
return;
}
Signal.handle(signal, this);
isRunning.set(true);
context.registerThread(this);
} catch (IllegalArgumentException e) {
throw e;
} finally {
lock.unlock();
}
}
public LLVMFunction getFunction() {
return handler;
}
@TruffleBoundary
public boolean isRunning() {
return isRunning.get();
}
@Override
@TruffleBoundary
protected void finalize() throws Throwable {
super.finalize();
stop();
unregisterFromContext();
}
private static final long HANDLE_MAX_WAITING_TIME = 250; // ms
@Override
@TruffleBoundary
public void handle(Signal arg0) {
try {
if (!globalSignalHandlerLock.tryLock(HANDLE_MAX_WAITING_TIME, TimeUnit.MILLISECONDS)) {
LLVMLogger.error("could not execute signal handler. Sulong can currently only execute one signal at once!");
return;
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
lock.lock();
try {
if (isRunning.get()) {
try {
ForeignAccess.sendExecute(Message.createExecute(1).createNode(), handler, signal.getNumber());
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
throw new AssertionError(e);
}
}
} finally {
lock.unlock();
globalSignalHandlerLock.unlock();
}
// probably, this was our last turn for the signal handler
if (!isRunning.get()) {
unregisterFromContext();
}
}
/**
* Required to call if a new LLVMSignalHandler take over the signal. Otherwise it would
* unregister the signal when this Object is going to be deallocated or stopped.
*/
@TruffleBoundary
private void setStopped() {
isRunning.set(false);
/*
* Either no handler is currently running, or it would unregister after finishing it's
* execution.
*/
tryUnregisterFromContext();
}
@Override
@TruffleBoundary
public void stop() {
if (isRunning.getAndSet(false)) {
/*
* it seems like we don't want to catch this signal anymore, as well as there is no
* other signal handler which want to catch it. So we simply reset it to look like
* no signal handler was registered at all.
*/
Signal.handle(signal, SignalHandler.SIG_DFL);
}
/*
* Either no handler is currently running, or it would unregister after finishing it's
* execution.
*/
tryUnregisterFromContext();
}
@Override
@TruffleBoundary
public void awaitFinish() {
stop();
// wait until handle is finished
lock.lock();
lock.unlock();
// this thread wouldn't start anymore, so we can assume it's stopped
unregisterFromContext();
}
/**
* Unregister this SignalHandler from context.
*/
@TruffleBoundary
private void unregisterFromContext() {
assert !isRunning.get();
context.unregisterThread(this);
int signalId = signal.getNumber();
synchronized (registeredSignals) {
if (registeredSignals.get(signalId) == this) {
registeredSignals.remove(signalId);
}
}
}
/**
* Only unregister this SignalHandler, if there is currently no lock held.
*/
@TruffleBoundary
private boolean tryUnregisterFromContext() {
assert !isRunning.get();
if (lock.tryLock()) {
try {
unregisterFromContext();
} finally {
lock.unlock();
}
return true;
}
return false;
}
@Override
@TruffleBoundary
public String toString() {
return "LLVMSignalHandler [signal=" + signal + ", lock=" + lock + ", isRunning=" + isRunning + "]";
}
}
/**
* Handling conversation from Signal number to Signal Object in a clean way.
*
* documentation: http://man7.org/linux/man-pages/man7/signal.7.html
*/
private enum Signals {
// POSIX.1-1990
SIG_HUP("HUP"),
SIG_INT("INT"),
SIG_QUIT("QUIT"),
SIG_ILL("ILL"),
SIG_ABRT("ABRT"),
SIG_FPE("FPE"),
SIG_KILL("KILL"),
SIG_SEGV("SEGV"),
SIG_PIPE("PIPE"),
SIG_ALRM("ALRM"),
SIG_TERM("TERM"),
SIG_USR1("USR1"),
SIG_USR2("USR2"),
SIG_CHLD("CHLD"),
SIG_CONT("CONT"),
SIG_STOP("STOP"),
SIG_TSTP("TSTP"),
SIG_TTIN("TTIN"),
// SUSv2 and POSIX.1-2001
SIG_BUS("BUS"),
SIG_POLL("POLL"),
SIG_PROF("PROF"),
SIG_SYS("SYS"),
SIG_TRAP("TRAP"),
SIG_URG("URG"),
SIG_VTALRM("VTALRM"),
SIG_XCPU("XCPU"),
SIG_XFSZ("XFSZ"),
// various other signals
SIG_IOT("IOT"),
SIG_EMT("EMT"),
SIG_STKFLT("STKFLT"),
SIG_IO("IO"),
SIG_CLD("CLD"),
SIG_PWR("PWR"),
SIG_INFO("INFO"),
SIG_LOST("LOST"),
SIG_WINCH("WINCH"),
SIG_UNUSED("UNUSED");
@TruffleBoundary
public static Signals decode(int code) throws NoSuchElementException {
for (Signals currentSignal : values()) {
if (currentSignal.signal() != null && currentSignal.signal().getNumber() == code) {
return currentSignal;
}
}
throw new NoSuchElementException("signal with the id " + code + " not found");
}
private final Signal signal;
Signals(String signalName) {
Signal constructedSignal;
try {
constructedSignal = new Signal(signalName);
} catch (IllegalArgumentException e) {
// it seems like this signal is not available on your system
constructedSignal = null;
}
this.signal = constructedSignal;
}
public Signal signal() {
return signal;
}
}
}