diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java index dfe8945d3814..258b30625866 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixPlatformTimeUtils.java @@ -36,7 +36,7 @@ public final class PosixPlatformTimeUtils extends PlatformTimeUtils { @Override @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/os/posix/os_posix.cpp#L1409-L1415") - protected SecondsNanos javaTimeSystemUTC() { + public SecondsNanos javaTimeSystemUTC() { Time.timespec ts = StackValue.get(Time.timespec.class); int status = PosixUtils.clock_gettime(Time.CLOCK_REALTIME(), ts); PosixUtils.checkStatusIs0(status, "javaTimeSystemUTC: clock_gettime(CLOCK_REALTIME) failed."); diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java new file mode 100644 index 000000000000..4babfff1b172 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSignalHandlerSupport.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2017, 2024, 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 com.oracle.svm.core.posix; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; +import static com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal.Constants.DEFAULT_HANDLER; +import static com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal.Constants.DISPATCH_HANDLER; +import static com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal.Constants.ERROR_HANDLER; +import static com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal.Constants.IGNORE_HANDLER; + +import java.util.List; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.function.CEntryPoint.Publish; +import org.graalvm.nativeimage.c.function.CEntryPointLiteral; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateSegfaultHandler; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.function.CEntryPointOptions; +import com.oracle.svm.core.c.function.CEntryPointOptions.NoEpilogue; +import com.oracle.svm.core.c.function.CEntryPointOptions.NoPrologue; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.jdk.RuntimeSupport; +import com.oracle.svm.core.jdk.RuntimeSupportFeature; +import com.oracle.svm.core.jdk.SignalHandlerSupport; +import com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal; +import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.monitor.MonitorSupport; +import com.oracle.svm.core.posix.headers.CSunMiscSignal; +import com.oracle.svm.core.posix.headers.Errno; +import com.oracle.svm.core.posix.headers.Signal; +import com.oracle.svm.core.posix.headers.Signal.SignalDispatcher; +import com.oracle.svm.core.posix.headers.Signal.SignalEnum; +import com.oracle.svm.core.posix.headers.Signal.sigset_tPointer; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.nodes.extended.MembarNode; + +/** + * The signal handler mechanism exists only once per process. So, only a single isolate at a time + * can do signal handling. The OS-level signal handler is written in C (see {@link CSunMiscSignal} + * and the corresponding C implementation). Once it receives a signal, it increments a counter and + * notifies the {@link DispatcherThread}. This Java thread (which is started on demand) then calls + * the actual Java Signal handler code. + */ +@AutomaticallyRegisteredImageSingleton({SignalHandlerSupport.class, PosixSignalHandlerSupport.class}) +public final class PosixSignalHandlerSupport implements SignalHandlerSupport { + /** + * Note that aliases are allowed in this map, i.e., different signal names may have the same C + * signal number. + */ + private EconomicMap signalNameToSignalNum; + private boolean[] supportedSignals; + private DispatcherThread dispatcherThread; + private boolean initialized; + + @Platforms(Platform.HOSTED_ONLY.class) + public PosixSignalHandlerSupport() { + } + + @Fold + public static PosixSignalHandlerSupport singleton() { + return ImageSingletons.lookup(PosixSignalHandlerSupport.class); + } + + /** Returns whether the currently installed signal handler matched the passed dispatcher. */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + static boolean isCurrentDispatcher(int sig, SignalDispatcher dispatcher) { + Signal.sigaction handler = UnsafeStackValue.get(Signal.sigaction.class); + Signal.sigaction(sig, WordFactory.nullPointer(), handler); + return handler.sa_handler() == dispatcher; + } + + public int findSignal0(String signalName) { + initSupportedSignals(); + + Integer result = signalNameToSignalNum.get(signalName); + if (result != null) { + return result; + } + return -1; + } + + @Override + public long installSignalHandler(int sig, long nativeH) { + assert MonitorSupport.singleton().isLockedByCurrentThread(Target_jdk_internal_misc_Signal.class); + ensureInitialized(); + + /* If the dispatcher is the C signal handler, then check if the signal is in range. */ + SignalDispatcher newDispatcher = handlerToDispatcher(nativeH); + if (newDispatcher == CSunMiscSignal.signalHandlerFunctionPointer() && !CSunMiscSignal.signalRangeCheck(sig)) { + return ERROR_HANDLER; + } + + /* + * If the segfault handler is registered, then the user cannot override this handler within + * Java code. + */ + if (SubstrateSegfaultHandler.isInstalled() && (sig == SignalEnum.SIGSEGV.getCValue() || sig == SignalEnum.SIGBUS.getCValue())) { + return ERROR_HANDLER; + } + + /* + * If the following signals are ignored, then a handler should not be registered for them. + */ + if (sig == SignalEnum.SIGHUP.getCValue() || sig == SignalEnum.SIGINT.getCValue() || sig == SignalEnum.SIGTERM.getCValue()) { + if (isCurrentDispatcher(sig, Signal.SIG_IGN())) { + return IGNORE_HANDLER; + } + } + + /* Install the signal handler and unblock the signal. */ + SignalDispatcher oldDispatcher = PosixUtils.installSignalHandler(sig, newDispatcher, Signal.SA_RESTART()); + + sigset_tPointer sigset = UnsafeStackValue.get(sigset_tPointer.class); + Signal.sigemptyset(sigset); + Signal.sigaddset(sigset, sig); + Signal.sigprocmask(Signal.SIG_UNBLOCK(), sigset, WordFactory.nullPointer()); + + return dispatcherToHandler(oldDispatcher); + } + + private void ensureInitialized() throws IllegalArgumentException { + assert MonitorSupport.singleton().isLockedByCurrentThread(Target_jdk_internal_misc_Signal.class); + if (initialized) { + return; + } + + initSupportedSignals(); + + int code = CSunMiscSignal.open(); + if (code == 0) { + startDispatcherThread(); + initialized = true; + } else if (code == 1) { + throw new IllegalArgumentException("C signal handling mechanism is in use."); + } else { + int errno = LibC.errno(); + Log.log().string("CSunMiscSignal.open() failed.").string(" errno: ").signed(errno).string(" ").string(Errno.strerror(errno)).newline(); + throw VMError.shouldNotReachHere("CSunMiscSignal.open() failed."); + } + } + + /** May be executed by multiple threads but each thread will compute the same data. */ + private void initSupportedSignals() { + if (signalNameToSignalNum != null && supportedSignals != null) { + return; + } + + int maxSigNum = 0; + EconomicMap map = EconomicMap.create(); + for (SignalEnum value : SignalEnum.values()) { + maxSigNum = Math.max(value.getCValue(), maxSigNum); + map.put(getJavaSignalName(value.name()), value.getCValue()); + } + + if (Platform.includedIn(Platform.LINUX.class)) { + for (Signal.LinuxSignalEnum value : Signal.LinuxSignalEnum.values()) { + maxSigNum = Math.max(value.getCValue(), maxSigNum); + map.put(getJavaSignalName(value.name()), value.getCValue()); + } + } else if (Platform.includedIn(Platform.DARWIN.class)) { + for (Signal.DarwinSignalEnum value : Signal.DarwinSignalEnum.values()) { + maxSigNum = Math.max(value.getCValue(), maxSigNum); + map.put(getJavaSignalName(value.name()), value.getCValue()); + } + } + + boolean[] signals = new boolean[maxSigNum + 1]; + MapCursor cursor = map.getEntries(); + while (cursor.advance()) { + signals[cursor.getValue()] = true; + } + + MembarNode.memoryBarrier(MembarNode.FenceKind.STORE_STORE); + signalNameToSignalNum = map; + supportedSignals = signals; + } + + private static String getJavaSignalName(String name) { + assert name.startsWith("SIG"); + return name.substring(3); + } + + private void startDispatcherThread() { + dispatcherThread = new DispatcherThread(); + dispatcherThread.start(); + } + + @Override + public void stopDispatcherThread() { + if (!initialized) { + return; + } + + /* Notify the dispatcher thread that it should stop and wait until it exits. */ + dispatcherThread.stopped = true; + + int code = CSunMiscSignal.signalSemaphore(); + PosixUtils.checkStatusIs0(code, "CSunMiscSignal.signalSemaphore() failed."); + + try { + dispatcherThread.join(); + } catch (InterruptedException e) { + throw VMError.shouldNotReachHere(e); + } + } + + @Override + @Uninterruptible(reason = "The isolate teardown is in progress.") + public void onIsolateTeardown() { + if (!initialized) { + return; + } + + /* + * Unregister all Java signal handlers. Note that native signal handlers (e.g., segfault + * handler, SIGPIPE noop handler) remain installed though. This can cause problems when + * unloading shared libraries (see GR-57977). + */ + for (int i = 0; i < supportedSignals.length; i++) { + if (supportedSignals[i]) { + boolean hasJavaSignalHandler = isCurrentDispatcher(i, CSunMiscSignal.signalHandlerFunctionPointer()); + if (hasJavaSignalHandler) { + SignalDispatcher dispatcher = getDefaultDispatcher(i); + SignalDispatcher signalResult = PosixUtils.installSignalHandlerUnsafe(i, dispatcher, Signal.SA_RESTART(), true); + if (signalResult == Signal.SIG_ERR()) { + throw VMError.shouldNotReachHere("Failed to unregister Java signal handlers during isolate teardown."); + } + } + } + } + + /* Shut down the signal handling mechanism so that another isolate can use it. */ + int code = CSunMiscSignal.close(); + PosixUtils.checkStatusIs0(code, "CSunMiscSignal.close() failed."); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + static SignalDispatcher getDefaultDispatcher(int sigNum) { + if (sigNum == SignalEnum.SIGPIPE.getCValue() || sigNum == SignalEnum.SIGXFSZ.getCValue()) { + return IgnoreSignalsStartupHook.NOOP_SIGNAL_HANDLER.getFunctionPointer(); + } + return Signal.SIG_DFL(); + } + + /** Map Java handler numbers to C function pointers. */ + private static SignalDispatcher handlerToDispatcher(long nativeH) { + if (nativeH == DEFAULT_HANDLER) { + return Signal.SIG_DFL(); + } else if (nativeH == IGNORE_HANDLER) { + return Signal.SIG_IGN(); + } else if (nativeH == DISPATCH_HANDLER) { + return CSunMiscSignal.signalHandlerFunctionPointer(); + } else if (nativeH == ERROR_HANDLER) { + return Signal.SIG_ERR(); + } else { + return WordFactory.pointer(nativeH); + } + } + + /** Map C function pointers to the Java handler numbers. */ + private static long dispatcherToHandler(SignalDispatcher handler) { + if (handler == Signal.SIG_DFL()) { + return DEFAULT_HANDLER; + } else if (handler == Signal.SIG_IGN()) { + return IGNORE_HANDLER; + } else if (handler == CSunMiscSignal.signalHandlerFunctionPointer()) { + return DISPATCH_HANDLER; + } else if (handler == Signal.SIG_ERR()) { + return ERROR_HANDLER; + } else { + return handler.rawValue(); + } + } + + private static class DispatcherThread extends Thread { + private volatile boolean stopped; + + DispatcherThread() { + super(PlatformThreads.singleton().systemGroup, "Signal Dispatcher"); + this.setDaemon(true); + } + + @Override + public void run() { + while (!stopped) { + int code = CSunMiscSignal.awaitSemaphore(); + PosixUtils.checkStatusIs0(code, "CSunMiscSignal.awaitSemaphore() failed."); + + int sigNum = CSunMiscSignal.checkPendingSignal(); + if (sigNum >= 0) { + com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal.dispatch(sigNum); + } + } + } + } +} + +@AutomaticallyRegisteredFeature +class PosixSignalHandlerFeature implements InternalFeature { + @Override + public List> getRequiredFeatures() { + return List.of(RuntimeSupportFeature.class); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + RuntimeSupport.getRuntimeSupport().addStartupHook(new IgnoreSignalsStartupHook()); + } +} + +/** + * Ideally, this should be executed as an isolate initialization hook or even earlier during + * startup. However, this doesn't work because some Truffle code sets the runtime option + * {@link SubstrateOptions#EnableSignalHandling} after the isolate initialization already finished. + */ +final class IgnoreSignalsStartupHook implements RuntimeSupport.Hook { + static final CEntryPointLiteral NOOP_SIGNAL_HANDLER = CEntryPointLiteral.create(IgnoreSignalsStartupHook.class, "noopSignalHandler", int.class); + + @CEntryPoint(publishAs = Publish.NotPublished) + @CEntryPointOptions(prologue = NoPrologue.class, epilogue = NoEpilogue.class) + @Uninterruptible(reason = "empty signal handler, Isolate is not set up") + static void noopSignalHandler(@SuppressWarnings("unused") int sig) { + /* noop - so no need to save/restore errno because its value can't be destroyed. */ + } + + /** + * HotSpot ignores the SIGPIPE and SIGXFSZ signals (see signals_posix.cpp). + * When signal handling is enabled we do the same thing. + *

+ * Ignore SIGPIPE. Reading from a closed pipe, instead of delivering a process-wide signal whose + * default action is to terminate the process, will instead return an error code from the + * specific write operation. + *

+ * From pipe(7): If all file descriptors referring to the read end of a pipe have been closed, + * then a write(2) will cause a SIGPIPE signal to be generated for the calling process. If the + * calling process is ignoring this signal, then write(2) fails with the error EPIPE. + *

+ * Note that the handler must be an empty function and not SIG_IGN. The problem is SIG_IGN is + * inherited to subprocess but we only want to affect the current process. + *

+ * From signal(7): A child created via fork(2) inherits a copy of its parent's signal + * dispositions. During an execve(2), the dispositions of handled signals are reset to the + * default; the dispositions of ignored signals are left unchanged. + */ + @Override + public void execute(boolean isFirstIsolate) { + // TEMP (chaeubl): think about this - must be executed for each isolate + if (SubstrateOptions.EnableSignalHandling.getValue()) { + /* Synchronized to avoid races with Signal.handle0(...) */ + synchronized (Target_jdk_internal_misc_Signal.class) { + installNoopHandler(SignalEnum.SIGPIPE); + installNoopHandler(SignalEnum.SIGXFSZ); + } + } + } + + private static void installNoopHandler(SignalEnum signal) { + int signum = signal.getCValue(); + if (PosixSignalHandlerSupport.isCurrentDispatcher(signum, Signal.SIG_DFL())) { + /* Replace with no-op signal handler if a custom one has not already been installed. */ + SignalDispatcher dispatcher = PosixSignalHandlerSupport.getDefaultDispatcher(signum); + assert dispatcher == NOOP_SIGNAL_HANDLER.getFunctionPointer(); + SignalDispatcher signalResult = PosixUtils.installSignalHandler(signum, dispatcher, Signal.SA_RESTART()); + if (signalResult == Signal.SIG_ERR()) { + throw VMError.shouldNotReachHere("IgnoreSignalsStartupHook: Could not install signal: " + signal); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java index 2d5a8b327990..95fa5d76f09e 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSubstrateSigprofHandler.java @@ -37,6 +37,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.c.function.CEntryPointOptions; +import com.oracle.svm.core.headers.LibC; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; @@ -77,16 +78,21 @@ public abstract class PosixSubstrateSigprofHandler extends SubstrateSigprofHandl @Uninterruptible(reason = "Signal handler may only execute uninterruptible code.") private static void dispatch(@SuppressWarnings("unused") int signalNumber, @SuppressWarnings("unused") Signal.siginfo_t sigInfo, Signal.ucontext_t uContext) { /* We need to keep the code in this method to a minimum to avoid races. */ - if (tryEnterIsolate()) { - CodePointer ip = (CodePointer) RegisterDumper.singleton().getIP(uContext); - Pointer sp = (Pointer) RegisterDumper.singleton().getSP(uContext); - tryUninterruptibleStackWalk(ip, sp, true); + int savedErrno = LibC.errno(); + try { + if (tryEnterIsolate()) { + CodePointer ip = (CodePointer) RegisterDumper.singleton().getIP(uContext); + Pointer sp = (Pointer) RegisterDumper.singleton().getSP(uContext); + tryUninterruptibleStackWalk(ip, sp, true); + } + } finally { + LibC.setErrno(savedErrno); } } @Override protected void installSignalHandler() { - PosixUtils.installSignalHandler(Signal.SignalEnum.SIGPROF, advancedSignalDispatcher.getFunctionPointer(), Signal.SA_RESTART()); + PosixUtils.installSignalHandlerUnsafe(Signal.SignalEnum.SIGPROF, advancedSignalDispatcher.getFunctionPointer(), Signal.SA_RESTART(), SubstrateOptions.EnableSignalHandling.getValue()); } static boolean isSignalHandlerBasedExecutionSamplerEnabled() { diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java index cbdccc156553..b053a6f7c392 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core.posix; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; import static com.oracle.svm.core.posix.headers.Unistd._SC_GETPW_R_SIZE_MAX; import java.io.FileDescriptor; @@ -50,6 +51,7 @@ import com.oracle.svm.core.c.libc.LibCBase; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal; import com.oracle.svm.core.memory.NullableNativeMemory; import com.oracle.svm.core.nmt.NmtCategory; import com.oracle.svm.core.posix.headers.Dlfcn; @@ -64,7 +66,6 @@ import com.oracle.svm.core.posix.headers.Wait; import com.oracle.svm.core.posix.headers.darwin.DarwinTime; import com.oracle.svm.core.posix.headers.linux.LinuxTime; -import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.core.util.VMError; @@ -265,21 +266,37 @@ public static int readBytes(int fd, CCharPointer buffer, int bufferLen, int read return readBytes; } - public static Signal.SignalDispatcher installSignalHandler(Signal.SignalEnum signum, Signal.SignalDispatcher handler, int flags) { - return installSignalHandler(signum.getCValue(), handler, flags); - } - /** - * Emulates the deprecated {@code signal} function via its replacement {@code sigaction}, - * assuming BSD semantics (like glibc does, for example). + * Registers a native signal handler. This method ensures that no races can happen with the Java + * signal handling. However, there can still be races with native code that registers signals. * - * Use this or {@code sigaction} directly instead of calling {@code signal} or {@code sigset}: - * they are not portable and when running in HotSpot, signal chaining (libjsig) prints warnings. + * Note that signal handlers must be written in a way that they do NOT destroy the libc + * {@code errno} value (i.e., typically, each signal needs to save and restore errno). * - * Note that this method should not be called from an initialization hook: - * {@code EnableSignalHandling} may not be set correctly at the time initialization hooks run. + * WARNING: methods related to signal handling must not be called from an initialization hook + * because the runtime option {@code EnableSignalHandling} is not necessarily initialized yet + * when initialization hooks run. So, startup hooks are currently the earliest point where + * execution is possible. */ public static Signal.SignalDispatcher installSignalHandler(int signum, Signal.SignalDispatcher handler, int flags) { + synchronized (Target_jdk_internal_misc_Signal.class) { + return installSignalHandlerUnsafe(signum, handler, flags, SubstrateOptions.EnableSignalHandling.getValue()); + } + } + + /** See {@link #installSignalHandler(int, Signal.SignalDispatcher, int)}. */ + public static void installSignalHandler(Signal.SignalEnum signum, Signal.AdvancedSignalDispatcher handler, int flags) { + synchronized (Target_jdk_internal_misc_Signal.class) { + installSignalHandlerUnsafe(signum, handler, flags, SubstrateOptions.EnableSignalHandling.getValue()); + } + } + + /** + * See {@link #installSignalHandler(int, Signal.SignalDispatcher, int)}, except that this method + * does not do any synchronization, so races with the Java signal handling may occur. + */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static Signal.SignalDispatcher installSignalHandlerUnsafe(int signum, Signal.SignalDispatcher handler, int flags, boolean isSignalHandlingAllowed) { int structSigActionSize = SizeOf.get(Signal.sigaction.class); Signal.sigaction act = UnsafeStackValue.get(structSigActionSize); LibC.memset(act, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize)); @@ -288,49 +305,36 @@ public static Signal.SignalDispatcher installSignalHandler(int signum, Signal.Si Signal.sigaction old = UnsafeStackValue.get(Signal.sigaction.class); - int result = sigaction(signum, act, old); + int result = sigaction(signum, act, old, isSignalHandlingAllowed); if (result != 0) { return Signal.SIG_ERR(); } return old.sa_handler(); } - public static void installSignalHandler(Signal.SignalEnum signum, Signal.AdvancedSignalDispatcher handler, int flags) { - installSignalHandler(signum.getCValue(), handler, flags); + /** See {@link #installSignalHandlerUnsafe(int, Signal.SignalDispatcher, int, boolean)}. */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static void installSignalHandlerUnsafe(Signal.SignalEnum signum, Signal.AdvancedSignalDispatcher handler, int flags, boolean isSignalHandlingAllowed) { + installSignalHandlerUnsafe(signum.getCValue(), handler, flags, isSignalHandlingAllowed); } - public static void installSignalHandler(int signum, Signal.AdvancedSignalDispatcher handler, int flags) { + /** See {@link #installSignalHandlerUnsafe(int, Signal.SignalDispatcher, int, boolean)}. */ + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static void installSignalHandlerUnsafe(int signum, Signal.AdvancedSignalDispatcher handler, int flags, boolean isSignalHandlingAllowed) { int structSigActionSize = SizeOf.get(Signal.sigaction.class); Signal.sigaction act = UnsafeStackValue.get(structSigActionSize); LibC.memset(act, WordFactory.signed(0), WordFactory.unsigned(structSigActionSize)); act.sa_flags(Signal.SA_SIGINFO() | flags); act.sa_sigaction(handler); - int result = sigaction(signum, act, WordFactory.nullPointer()); + int result = sigaction(signum, act, WordFactory.nullPointer(), isSignalHandlingAllowed); PosixUtils.checkStatusIs0(result, "sigaction failed in installSignalHandler()."); } - /* - * Avoid races with logic within Util_jdk_internal_misc_Signal#handle0 which reads these - * signals. - */ - private static int sigaction(int signum, Signal.sigaction structSigAction, Signal.sigaction old) { - VMError.guarantee(SubstrateOptions.EnableSignalHandling.getValue(), "Trying to install a signal handler while signal handling is disabled."); - - if (VMOperation.isInProgress()) { - /* - * Note this can race with other signals being installed. However, using Java - * synchronization is disallowed within a VMOperation. If race-free execution becomes - * necessary, then a VMMutex will be needed and additional code will need to be - * made @Uninterruptible so that a thread owning the VMMutex cannot block at a - * safepoint. - */ - return Signal.sigaction(signum, structSigAction, old); - } else { - synchronized (Target_jdk_internal_misc_Signal.class) { - return Signal.sigaction(signum, structSigAction, old); - } - } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private static int sigaction(int signum, Signal.sigaction structSigAction, Signal.sigaction old, boolean isSignalHandlingAllowed) { + VMError.guarantee(isSignalHandlingAllowed, "Trying to install a signal handler while signal handling is disabled."); + return Signal.sigaction(signum, structSigAction, old); } // Checkstyle: stop diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/SunMiscSubstitutions.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/SunMiscSubstitutions.java deleted file mode 100644 index 6317561f9f50..000000000000 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/SunMiscSubstitutions.java +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright (c) 2017, 2017, 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 com.oracle.svm.core.posix; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.locks.ReentrantLock; - -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.c.function.CEntryPoint; -import org.graalvm.nativeimage.c.function.CEntryPoint.Publish; -import org.graalvm.nativeimage.c.function.CEntryPointLiteral; -import org.graalvm.nativeimage.c.type.CIntPointer; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.SubstrateSegfaultHandler; -import com.oracle.svm.core.Uninterruptible; -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.c.function.CEntryPointOptions; -import com.oracle.svm.core.c.function.CEntryPointOptions.NoEpilogue; -import com.oracle.svm.core.c.function.CEntryPointOptions.NoPrologue; -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; -import com.oracle.svm.core.headers.LibC; -import com.oracle.svm.core.jdk.RuntimeSupport; -import com.oracle.svm.core.log.Log; -import com.oracle.svm.core.posix.headers.CSunMiscSignal; -import com.oracle.svm.core.posix.headers.Errno; -import com.oracle.svm.core.posix.headers.Signal; -import com.oracle.svm.core.posix.headers.Signal.SignalDispatcher; -import com.oracle.svm.core.posix.headers.Time; -import com.oracle.svm.core.util.VMError; - -@TargetClass(className = "jdk.internal.misc.Signal") -final class Target_jdk_internal_misc_Signal { - - @Substitute - private static int findSignal0(String signalName) { - return Util_jdk_internal_misc_Signal.numberFromName(signalName); - } - - @Substitute - private static long handle0(int sig, long nativeH) { - if (!SubstrateOptions.EnableSignalHandling.getValue()) { - throw new IllegalArgumentException("Signal handlers can't be installed if signal handling is disabled, see option '" + SubstrateOptions.EnableSignalHandling.getName() + "'."); - } - return Util_jdk_internal_misc_Signal.handle0(sig, nativeH); - } - - /** The Java side of raising a signal calls the C side of raising a signal. */ - @Substitute - private static void raise0(int signalNumber) { - Signal.raise(signalNumber); - } - - /** - * Called by the VM to execute Java signal handlers. Except that in jdk.internal.misc.Signal, - * this method is private. - */ - @Alias - static native void dispatch(int number); -} - -/** Support for Target_jdk_internal_misc_Signal. */ -final class Util_jdk_internal_misc_Signal { - - /** A thread to dispatch signals as they are raised. */ - private static Thread dispatchThread = null; - - /** A lock to synchronize runtime initialization. */ - private static final ReentrantLock initializationLock = new ReentrantLock(); - /** An initialization flag. */ - private static volatile boolean initialized = false; - - /** A map from signal numbers to handlers. */ - private static SignalState[] signalState = null; - - private Util_jdk_internal_misc_Signal() { - /* All-static class. */ - } - - /** Constants for the longs from jdk.internal.misc.Signal. */ - private static final long sunMiscSignalDefaultHandler = 0; - private static final long sunMiscSignalIgnoreHandler = 1; - private static final long sunMiscSignalDispatchHandler = 2; - private static final long sunMiscSignalErrorHandler = -1; - - /** - * Returns whether the currently installed signal handler matched the passed dispatcher. - * - * Note this method can race with signal installation unless proper locking is used. - */ - static boolean isCurrentDispatcher(int sig, SignalDispatcher dispatcher) { - Signal.sigaction handler = UnsafeStackValue.get(Signal.sigaction.class); - Signal.sigaction(sig, WordFactory.nullPointer(), handler); - return handler.sa_handler() == dispatcher; - } - - /** - * Register a Java signal handler with the C signal handling mechanism. - * - * This code is only called from the substitute Target_jdk_internal_misc_Signal#handle0, which - * is called from within a static synchronized call to ensure race-free execution. - */ - static long handle0(int sig, long nativeH) { - ensureInitialized(); - final Signal.SignalDispatcher newDispatcher = nativeHToDispatcher(nativeH); - /* If the dispatcher is the CSunMiscSignal handler, then check if the signal is in range. */ - if ((newDispatcher == CSunMiscSignal.countingHandlerFunctionPointer()) && (CSunMiscSignal.signalRangeCheck(sig) != 1)) { - return sunMiscSignalErrorHandler; - } - - /* - * If the segfault handler is registered, then the user cannot override this handler within - * Java code. - */ - if (SubstrateSegfaultHandler.isInstalled() && (sig == Signal.SignalEnum.SIGSEGV.getCValue() || sig == Signal.SignalEnum.SIGBUS.getCValue())) { - return sunMiscSignalErrorHandler; - } - - /* - * If the following signals are ignored, then a handler should not be registered for them. - */ - if (sig == Signal.SignalEnum.SIGHUP.getCValue() || sig == Signal.SignalEnum.SIGINT.getCValue() || sig == Signal.SignalEnum.SIGTERM.getCValue()) { - if (isCurrentDispatcher(sig, Signal.SIG_IGN())) { - return sunMiscSignalIgnoreHandler; - } - } - - updateDispatcher(sig, newDispatcher); - final Signal.SignalDispatcher oldDispatcher = PosixUtils.installSignalHandler(sig, newDispatcher, Signal.SA_RESTART()); - CIntPointer sigset = UnsafeStackValue.get(CIntPointer.class); - sigset.write(1 << (sig - 1)); - Signal.sigprocmask(Signal.SIG_UNBLOCK(), (Signal.sigset_tPointer) sigset, WordFactory.nullPointer()); - return dispatcherToNativeH(oldDispatcher); - } - - /** Runtime initialization. */ - private static void ensureInitialized() throws IllegalArgumentException { - /* Ask if initialization is needed. */ - if (!initialized) { - /* - * Lock so initialization only happens once, and so initialization finishes before any - * uses, e.g., from other threads. - */ - initializationLock.lock(); - try { - /* Ask if initialization is *still* needed now that I have the lock. */ - if (!initialized) { - /* Open the C signal handling mechanism. */ - final int openResult = CSunMiscSignal.open(); - if (openResult != 0) { - final int openErrno = LibC.errno(); - /* Check for the C signal handling mechanism already being open. */ - if (openErrno == Errno.EBUSY()) { - throw new IllegalArgumentException("C signal handling mechanism is in use."); - } - /* Report other failure. */ - Log.log().string("Util_jdk_internal_misc_Signal.ensureInitialized: CSunMiscSignal.create() failed.") - .string(" errno: ").signed(openErrno).string(" ").string(Errno.strerror(openErrno)).newline(); - throw VMError.shouldNotReachHere("Util_jdk_internal_misc_Signal.ensureInitialized: CSunMiscSignal.open() failed."); - } - - /* Initialize the table of signal states. */ - signalState = createSignalStateTable(); - - /* Create and start a daemon thread to dispatch to Java signal handlers. */ - dispatchThread = new Thread(new DispatchThread()); - dispatchThread.setName("Signal Dispatcher"); - dispatchThread.setDaemon(true); - dispatchThread.start(); - RuntimeSupport.getRuntimeSupport().addTearDownHook(isFirstIsolate -> DispatchThread.interrupt(dispatchThread)); - - /* Initialization is complete. */ - initialized = true; - } - } finally { - initializationLock.unlock(); - } - } - } - - /** - * Create a table of signal states. This would be straightforward, except for the - * platform-specific signals. See GR-7858: @Platform @CEnum members. - */ - private static SignalState[] createSignalStateTable() { - /* Fill in the table. */ - List signalStateList = new ArrayList<>(); - for (Signal.SignalEnum value : Signal.SignalEnum.values()) { - signalStateList.add(new SignalState(value.name(), value.getCValue())); - } - if (Platform.includedIn(Platform.LINUX.class)) { - for (Signal.LinuxSignalEnum value : Signal.LinuxSignalEnum.values()) { - signalStateList.add(new SignalState(value.name(), value.getCValue())); - } - } else if (Platform.includedIn(Platform.DARWIN.class)) { - for (Signal.DarwinSignalEnum value : Signal.DarwinSignalEnum.values()) { - signalStateList.add(new SignalState(value.name(), value.getCValue())); - } - } - final SignalState[] result = signalStateList.toArray(new SignalState[0]); - return result; - } - - /** Map from a Java signal name to a signal number. */ - protected static int numberFromName(String javaSignalName) { - ensureInitialized(); - /* Java deals in signal names without the leading "SIG" prefix, but C uses it. */ - final String cSignalName = "SIG" + javaSignalName; - for (int index = 0; index < signalState.length; index += 1) { - final SignalState entry = signalState[index]; - if (entry.getName().equals(cSignalName)) { - return entry.getNumber(); - } - } - /* {@link jdk.internal.misc.Signal#findSignal(String)} expects a -1 on failure. */ - return -1; - } - - /** Update the dispatcher of an entry in the signal state table. */ - private static void updateDispatcher(int sig, Signal.SignalDispatcher dispatcher) { - for (int index = 0; index < signalState.length; index += 1) { - final SignalState entry = signalState[index]; - if (entry.getNumber() == sig) { - entry.setDispatcher(dispatcher); - return; - } - } - } - - /** Map from the handler numbers Java uses to the function pointers that C uses. */ - private static Signal.SignalDispatcher nativeHToDispatcher(long nativeH) { - final Signal.SignalDispatcher result; - if (nativeH == sunMiscSignalDefaultHandler) { - result = Signal.SIG_DFL(); - } else if (nativeH == sunMiscSignalIgnoreHandler) { - result = Signal.SIG_IGN(); - } else if (nativeH == sunMiscSignalDispatchHandler) { - result = CSunMiscSignal.countingHandlerFunctionPointer(); - } else if (nativeH == sunMiscSignalErrorHandler) { - result = Signal.SIG_ERR(); - } else { - result = WordFactory.pointer(nativeH); - } - return result; - } - - /** Map from the function pointers that C uses to the numbers that Java uses. */ - private static long dispatcherToNativeH(Signal.SignalDispatcher handler) { - final long result; - if (handler == Signal.SIG_DFL()) { - result = sunMiscSignalDefaultHandler; - } else if (handler == Signal.SIG_IGN()) { - result = sunMiscSignalIgnoreHandler; - } else if (handler == CSunMiscSignal.countingHandlerFunctionPointer()) { - result = sunMiscSignalDispatchHandler; - } else if (handler == Signal.SIG_ERR()) { - result = sunMiscSignalErrorHandler; - } else { - result = handler.rawValue(); - } - return result; - } - - /** A runnable to notice when signals have been raised. */ - protected static final class DispatchThread implements Runnable { - - protected DispatchThread() { - /* Nothing to do. */ - } - - static void interrupt(Thread thread) { - thread.interrupt(); - SignalState.wakeUp(); - } - - /** - * Wait to be notified that a signal has been raised in the C signal handler, then find any - * that were raised and dispatch to the Java signal handler. The C signal handler increments - * the counts and this method decrements them. - */ - @Override - public void run() { - while (!Thread.interrupted()) { - /* - * Block waiting for one or more signals to be raised. Or a wake up for termination. - */ - SignalState.await(); - if (Thread.interrupted()) { - /* Thread was interrupted for termination. */ - break; - } - /* Find any counters that are non-zero. */ - for (final SignalState entry : signalState) { - final SignalDispatcher dispatcher = entry.getDispatcher(); - /* If the handler is the Java signal handler ... */ - if (dispatcher.equal(CSunMiscSignal.countingHandlerFunctionPointer())) { - /* ... and if there are outstanding signals to be dispatched. */ - if (entry.decrementCount() > 0L) { - Target_jdk_internal_misc_Signal.dispatch(entry.getNumber()); - } - } - } - } - /* If this thread is exiting, then the C signal handling mechanism can be closed. */ - CSunMiscSignal.close(); - } - } - - /** - * An entry in a table of signal numbers and handlers. There is a parallel table of counts of - * outstanding signals maintained in C. There are convenience method here for accessing those - * counts. - */ - private static final class SignalState { - - /** The C signal name. */ - private final String name; - /** The C signal number. */ - private final int number; - /** The C signal handler. */ - private Signal.SignalDispatcher dispatcher; - - /** This just allocates an entry. The entry is initialized at runtime. */ - protected SignalState(String cName, int cValue) { - this.name = cName; - this.number = cValue; - this.dispatcher = Signal.SIG_DFL(); - } - - protected String getName() { - return name; - } - - protected int getNumber() { - return number; - } - - protected Signal.SignalDispatcher getDispatcher() { - return dispatcher; - } - - protected void setDispatcher(Signal.SignalDispatcher value) { - dispatcher = value; - } - - /* - * Convenient access to C functions, with checks for success. - */ - - protected static void await() { - final int awaitResult = CSunMiscSignal.await(); - PosixUtils.checkStatusIs0(awaitResult, "Util_jdk_internal_misc_Signal.SignalState.await(): CSunMiscSignal.await() failed."); - } - - protected static void wakeUp() { - final int awaitResult = CSunMiscSignal.post(); - PosixUtils.checkStatusIs0(awaitResult, "Util_jdk_internal_misc_Signal.SignalState.post(): CSunMiscSignal.post() failed."); - } - - /* - * Decrement a counter towards zero. Returns the original value, or -1 if the signal number - * is out of range. - */ - protected long decrementCount() { - /* Not checking the result. */ - return CSunMiscSignal.decrementCount(number); - } - } -} - -@AutomaticallyRegisteredFeature -class IgnoreSignalsFeature implements InternalFeature { - - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - RuntimeSupport.getRuntimeSupport().addStartupHook(new IgnoreSignalsStartupHook()); - } -} - -final class IgnoreSignalsStartupHook implements RuntimeSupport.Hook { - - @CEntryPoint(publishAs = Publish.NotPublished) - @CEntryPointOptions(prologue = NoPrologue.class, epilogue = NoEpilogue.class) - @Uninterruptible(reason = "empty signal handler, Isolate is not set up") - static void noopSignalHandler(@SuppressWarnings("unused") int sig) { - } - - private static final CEntryPointLiteral NOOP_SIGNAL_HANDLER = // - CEntryPointLiteral.create(IgnoreSignalsStartupHook.class, "noopSignalHandler", int.class); - - /** - * HotSpot ignores the SIGPIPE and SIGXFSZ signals (see signals_posix.cpp). - * When signal handling is enabled we do the same thing. - *

- * Ignore SIGPIPE. Reading from a closed pipe, instead of delivering a process-wide signal whose - * default action is to terminate the process, will instead return an error code from the - * specific write operation. - *

- * From pipe(7): If all file descriptors referring to the read end of a pipe have been closed, - * then a write(2) will cause a SIGPIPE signal to be generated for the calling process. If the - * calling process is ignoring this signal, then write(2) fails with the error EPIPE. - *

- * Note that the handler must be an empty function and not SIG_IGN. The problem is SIG_IGN is - * inherited to subprocess but we only want to affect the current process. - *

- * From signal(7): A child created via fork(2) inherits a copy of its parent's signal - * dispositions. During an execve(2), the dispositions of handled signals are reset to the - * default; the dispositions of ignored signals are left unchanged. - */ - @Override - public void execute(boolean isFirstIsolate) { - if (isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) { - synchronized (Target_jdk_internal_misc_Signal.class) { - installNoopHandler(Signal.SignalEnum.SIGPIPE); - installNoopHandler(Signal.SignalEnum.SIGXFSZ); - } - } - } - - private static void installNoopHandler(Signal.SignalEnum signal) { - int signum = signal.getCValue(); - if (Util_jdk_internal_misc_Signal.isCurrentDispatcher(signum, Signal.SIG_DFL())) { - /* - * Replace with no-op signal handler if a custom one has not already been installed. - */ - final SignalDispatcher signalResult = PosixUtils.installSignalHandler(signum, NOOP_SIGNAL_HANDLER.getFunctionPointer(), Signal.SA_RESTART()); - if (signalResult == Signal.SIG_ERR()) { - throw VMError.shouldNotReachHere(String.format("IgnoreSignalsStartupHook: Could not install signal: %s", signal)); - } - } - } -} - -@TargetClass(className = "jdk.internal.misc.VM") -final class Target_jdk_internal_misc_VM { - - /* Implementation from src/hotspot/share/prims/jvm.cpp#L286 translated to Java. */ - @Substitute - public static long getNanoTimeAdjustment(long offsetInSeconds) { - final long maxDiffSecs = 0x0100000000L; - final long minDiffSecs = -maxDiffSecs; - - Time.timeval tv = UnsafeStackValue.get(Time.timeval.class); - int status = Time.NoTransitions.gettimeofday(tv, WordFactory.nullPointer()); - assert status != -1 : "linux error"; - long seconds = tv.tv_sec(); - long nanos = tv.tv_usec() * 1000; - - long diff = seconds - offsetInSeconds; - if (diff >= maxDiffSecs || diff <= minDiffSecs) { - return -1; - } - return diff * 1000000000 + nanos; - } -} - -/** Dummy class to have a class with the file's name. */ -public final class SunMiscSubstitutions { -} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/CSunMiscSignal.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/CSunMiscSignal.java index ebf9563691fd..d4d4b348572d 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/CSunMiscSignal.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/CSunMiscSignal.java @@ -24,50 +24,42 @@ */ package com.oracle.svm.core.posix.headers; -import org.graalvm.nativeimage.c.CContext; +import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; + import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.c.function.CLibrary; -/** - * The interface that Java code needs to the C sun.miscSignal handler. - * - * With renames to translate C underscores to Java dots. - */ -@CContext(PosixDirectives.class) +/** See cSunMiscSignal.c for the C implementation. */ +@CLibrary(value = "libchelper", requireStatic = true) public class CSunMiscSignal { - - /* Open the C signal handler mechanism. */ + /** Open the C signal handler mechanism. */ @CFunction("cSunMiscSignal_open") public static native int open(); - /* Close the C signal handler mechanism. */ - @CFunction("cSunMiscSignal_close") + /** Close the C signal handler mechanism. */ + @CFunction(value = "cSunMiscSignal_close", transition = NO_TRANSITION) public static native int close(); - /* Wait for a notification on the semaphore. */ - @CFunction("cSunMiscSignal_await") - public static native int await(); - - /* Notify a thread waiting on the semaphore. */ - @CFunction("cSunMiscSignal_post") - public static native int post(); + /** Wait for a notification on the semaphore. Prone to spurious wake-ups. */ + @CFunction("cSunMiscSignal_awaitSemaphore") + public static native int awaitSemaphore(); - /* Returns 1 if the signal is in the range of the counters, 0 otherwise. */ - @CFunction("cSunMiscSignal_signalRangeCheck") - public static native int signalRangeCheck(int signal); + /** Notify a thread waiting on the semaphore. */ + @CFunction(value = "cSunMiscSignal_signalSemaphore", transition = NO_TRANSITION) + public static native int signalSemaphore(); - /* Return the count of outstanding signals. Returns -1 if the signal is out of bounds. */ - @CFunction("cSunMiscSignal_getCount") - public static native long getCount(int signal); + /** Returns true if the signal is in the range of the counters. */ + @CFunction(value = "cSunMiscSignal_signalRangeCheck", transition = NO_TRANSITION) + public static native boolean signalRangeCheck(int signal); - /* - * Decrement a counter towards zero, given a signal number. Returns the previous value, or -1 if - * the signal is out of bounds. + /** + * Returns the number of the first pending signal, or -1 if no signal is pending. May only be + * called by a single thread (i.e., the signal dispatcher thread). */ - @CFunction("cSunMiscSignal_decrementCount") - public static native long decrementCount(int signal); - - /* Return the address of the counting signal handler. */ - @CFunction("cSunMiscSignal_countingHandlerFunctionPointer") - public static native Signal.SignalDispatcher countingHandlerFunctionPointer(); + @CFunction(value = "cSunMiscSignal_checkPendingSignal", transition = NO_TRANSITION) + public static native int checkPendingSignal(); + /** Returns a function pointer to the C signal handler. */ + @CFunction(value = "cSunMiscSignal_signalHandlerFunctionPointer", transition = NO_TRANSITION) + public static native Signal.SignalDispatcher signalHandlerFunctionPointer(); } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java index 229cdb631918..e0dafd506c47 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/headers/Signal.java @@ -74,14 +74,19 @@ public class Signal { @CFunction public static native int sigprocmask(int how, sigset_tPointer set, sigset_tPointer oldset); + @CFunction + public static native int sigemptyset(sigset_tPointer set); + + @CFunction + public static native int sigaddset(sigset_tPointer set, int signum); + @CPointerTo(nameOfCType = "sigset_t") public interface sigset_tPointer extends PointerBase { } /** - * Warning: use {@link PosixUtils#installSignalHandler}. Do NOT introduce calls to - * {@code signal} or {@code sigset}, which are not portable, and when running in HotSpot, signal - * chaining (libjsig) will print warnings. + * WARNING: do NOT introduce direct calls to {@code signal} or {@code sigset} as they are not + * portable. Besides that, signal chaining (libjsig) in HotSpot would print warnings. */ public interface SignalDispatcher extends CFunctionPointer { @InvokeCFunctionPointer @@ -191,13 +196,10 @@ public interface sigevent extends PointerBase { void sigev_signo(int value); } - /** Don't call this function directly, see {@link PosixUtils#sigaction}. */ - @CFunction + /** Don't call this function directly, use {@link PosixUtils#sigaction} instead. */ + @CFunction(transition = NO_TRANSITION) public static native int sigaction(int signum, sigaction act, sigaction oldact); - @CConstant - public static native int SIGPROF(); - @CEnum @CContext(PosixDirectives.class) public enum SignalEnum { diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/Target_jdk_internal_misc_Signal.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/Target_jdk_internal_misc_Signal.java new file mode 100644 index 000000000000..72062f96f0ed --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/Target_jdk_internal_misc_Signal.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, 2024, 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 com.oracle.svm.core.posix.jdk; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.posix.PosixSignalHandlerSupport; +import com.oracle.svm.core.posix.headers.Signal; + +@TargetClass(className = "jdk.internal.misc.Signal") +final class Target_jdk_internal_misc_Signal { + @Substitute + private static int findSignal0(String signalName) { + return PosixSignalHandlerSupport.singleton().findSignal0(signalName); + } + + @Substitute + private static void raise0(int signalNumber) { + Signal.raise(signalNumber); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/Target_jdk_internal_misc_VM.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/Target_jdk_internal_misc_VM.java new file mode 100644 index 000000000000..3e98471c29b7 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/Target_jdk_internal_misc_VM.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, 2024, 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 com.oracle.svm.core.posix.jdk; + +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.InternalPlatform; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.PlatformTimeUtils; +import com.oracle.svm.core.util.PlatformTimeUtils.SecondsNanos; + +@TargetClass(className = "jdk.internal.misc.VM") +final class Target_jdk_internal_misc_VM { + @Substitute + @Platforms(InternalPlatform.NATIVE_ONLY.class) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/share/prims/jvm.cpp#L258-L291") + public static long getNanoTimeAdjustment(long offsetInSeconds) { + long maxDiffSecs = 0x0100000000L; + long minDiffSecs = -maxDiffSecs; + + SecondsNanos time = PlatformTimeUtils.singleton().javaTimeSystemUTC(); + + long diff = time.seconds() - offsetInSeconds; + if (diff >= maxDiffSecs || diff <= minDiffSecs) { + return -1; + } + return (diff * 1000000000) + time.nanos(); + } +} diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/package-info.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/package-info.java new file mode 100644 index 000000000000..0ca5d277fbb1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/jdk/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, 2024, 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. + */ + +@Platforms({Platform.LINUX.class, Platform.DARWIN.class}) +package com.oracle.svm.core.posix.jdk; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java index 326a4d12bd9e..50a2740c435a 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/linux/LinuxSubstrateSigprofHandler.java @@ -87,7 +87,7 @@ protected void install0(IsolateThread thread) { Signal.sigevent sigevent = StackValue.get(Signal.sigevent.class); sigevent.sigev_notify(Signal.SIGEV_SIGNAL()); - sigevent.sigev_signo(Signal.SIGPROF()); + sigevent.sigev_signo(Signal.SignalEnum.SIGPROF.getCValue()); WordPointer timerPointer = StackValue.get(WordPointer.class); timerPointer.write(WordFactory.zero()); diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/Target_jdk_internal_misc_Signal.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/Target_jdk_internal_misc_Signal.java deleted file mode 100644 index 23ccc34865d8..000000000000 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/Target_jdk_internal_misc_Signal.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2020, 2020, 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 com.oracle.svm.core.windows; - -import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.FromAlias; -import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; - -import java.util.Hashtable; -import java.util.Locale; - -import org.graalvm.nativeimage.c.function.CFunction; -import org.graalvm.word.WordFactory; - -import com.oracle.svm.core.Isolates; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.RecomputeFieldValue; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.Jvm; -import com.oracle.svm.core.jdk.RuntimeSupport; -import com.oracle.svm.core.thread.PlatformThreads; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.core.windows.headers.WinBase; - -@TargetClass(className = "jdk.internal.misc.Signal") -final class Target_jdk_internal_misc_Signal { - // Checkstyle: stop - @Alias @RecomputeFieldValue(kind = FromAlias)// - private static Hashtable handlers = new Hashtable<>(4); - @Alias @RecomputeFieldValue(kind = FromAlias)// - private static Hashtable signals = new Hashtable<>(4); - // Checkstyle: resume - - /** Called by the VM to execute Java signal handlers. */ - @Alias - static native void dispatch(int number); - - @Substitute - private static long handle0(int sig, long nativeH) { - if (!SubstrateOptions.EnableSignalHandling.getValue()) { - throw new IllegalArgumentException("Signal handlers can't be installed if signal handling is disabled, see option '" + SubstrateOptions.EnableSignalHandling.getName() + "'."); - } - if (!Isolates.isCurrentFirst()) { - throw new IllegalArgumentException("Only the first isolate can install signal handlers, as signal handling is global for the process."); - } - SignalDispatcher.ensureInitialized(); - return Jvm.JVM_RegisterSignal(sig, WordFactory.pointer(nativeH)).rawValue(); - } -} - -class SignalDispatcher implements Runnable { - private static final int NEAR_MAX_PRIORITY = Thread.MAX_PRIORITY - 1; - - /** A thread (in the image heap) to dispatch signals as they are raised. */ - private static final Thread signalDispatcherThread; - static { - signalDispatcherThread = new Thread(PlatformThreads.singleton().systemGroup, - new SignalDispatcher(), "Signal Dispatcher"); - signalDispatcherThread.setPriority(NEAR_MAX_PRIORITY); - signalDispatcherThread.setDaemon(true); - } - - /** - * Wait in native for a notification from the C signal handler that the signal has been raised - * and then dispatch it to the Java signal handler. - */ - @Override - public void run() { - while (true) { - int sig = osSignalWait(); - - if (sig == osSigexitnumPd()) { - /* Terminate the signal thread. */ - return; - } else { - /* Dispatch the signal to java. */ - Target_jdk_internal_misc_Signal.dispatch(sig); - } - } - } - - @CFunction("os__signal_wait") - private static native int osSignalWait(); - - @CFunction(value = "os__sigexitnum_pd", transition = NO_TRANSITION) - private static native int osSigexitnumPd(); - - /** Runtime initialization. */ - static void ensureInitialized() { - /* - * Usually an initialization like this would require explicit synchronization. However, in - * this case it can only be triggered from the `Signal.handle` method that is already - * synchronized, so we can do without it. - */ - if (signalDispatcherThread.getState() == Thread.State.NEW) { - if (!jdkMiscSignalInit()) { - VMError.shouldNotReachHere("Native state initialization for jdk.internal.misc.Signal failed with error code: 0x" + - Integer.toUnsignedString(WinBase.GetLastError(), 16).toUpperCase(Locale.ROOT)); - } - RuntimeSupport.getRuntimeSupport().addTearDownHook(isFirstIsolate -> osTerminateSignalThread()); - signalDispatcherThread.start(); - } - } - - @CFunction(value = "jdk_misc_signal_init", transition = NO_TRANSITION) - private static native boolean jdkMiscSignalInit(); - - @CFunction(value = "os__terminate_signal_thread", transition = NO_TRANSITION) - private static native void osTerminateSignalThread(); -} diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java index 2722ffaa18a4..4abac3e44faa 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsPlatformTimeUtils.java @@ -54,14 +54,13 @@ private static long windowsToTimeTicks(FILETIME wt) { @Override @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+3/src/hotspot/os/windows/os_windows.cpp#L1198-L1205") - protected SecondsNanos javaTimeSystemUTC() { + public SecondsNanos javaTimeSystemUTC() { FILETIME wt = StackValue.get(FILETIME.class); GetSystemTimeAsFileTime(wt); long ticks = windowsToTimeTicks(wt); // 10th of micros long secs = ticks / 10000000L; // 10000 * 1000 - long seconds = secs; long nanos = (ticks - (secs * 10000000L)) * 100L; - return new SecondsNanos(seconds, nanos); + return new SecondsNanos(secs, nanos); } } diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java new file mode 100644 index 000000000000..6e4658232ac0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsSignalHandlerSupport.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020, 2020, 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 com.oracle.svm.core.windows; + +import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; + +import java.util.Locale; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.Isolates; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.jdk.Jvm; +import com.oracle.svm.core.jdk.SignalHandlerSupport; +import com.oracle.svm.core.jdk.Target_jdk_internal_misc_Signal; +import com.oracle.svm.core.monitor.MonitorSupport; +import com.oracle.svm.core.thread.PlatformThreads; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.windows.headers.WinBase; + +@AutomaticallyRegisteredImageSingleton(SignalHandlerSupport.class) +public class WindowsSignalHandlerSupport implements SignalHandlerSupport { + private static final int NEAR_MAX_PRIORITY = Thread.MAX_PRIORITY - 1; + + private Thread dispatcherThread; + private boolean initialized; + + @Platforms(Platform.HOSTED_ONLY.class) + public WindowsSignalHandlerSupport() { + } + + @Override + public long installSignalHandler(int sig, long nativeH) { + assert MonitorSupport.singleton().isLockedByCurrentThread(Target_jdk_internal_misc_Signal.class); + ensureInitialized(); + + return Jvm.JVM_RegisterSignal(sig, WordFactory.pointer(nativeH)).rawValue(); + } + + private void ensureInitialized() { + if (initialized) { + return; + } + + if (!Isolates.isCurrentFirst()) { + /* This is a difference to the posix implementation, see GR-57722. */ + throw new IllegalArgumentException("Only the first isolate can install signal handlers, as signal handling is global for the process."); + } + + if (!jdkMiscSignalInit()) { + VMError.shouldNotReachHere("Native state initialization for jdk.internal.misc.Signal failed with error code: 0x" + + Integer.toUnsignedString(WinBase.GetLastError(), 16).toUpperCase(Locale.ROOT)); + } + + startDispatcherThread(); + initialized = true; + } + + private void startDispatcherThread() { + dispatcherThread = new DispatcherThread(); + dispatcherThread.start(); + } + + @Override + public void stopDispatcherThread() { + if (!initialized) { + return; + } + + osTerminateSignalThread(); + try { + dispatcherThread.join(); + } catch (InterruptedException e) { + throw VMError.shouldNotReachHere(e); + } + } + + @Override + @Uninterruptible(reason = "The isolate teardown is in progress.") + public void onIsolateTeardown() { + /* Teardown is not implemented at the moment, see GR-57722. */ + } + + @CFunction("os__signal_wait") + private static native int osSignalWait(); + + @CFunction(value = "os__sigexitnum_pd", transition = NO_TRANSITION) + private static native int osSigexitnumPd(); + + @CFunction(value = "jdk_misc_signal_init", transition = NO_TRANSITION) + private static native boolean jdkMiscSignalInit(); + + @CFunction(value = "os__terminate_signal_thread", transition = NO_TRANSITION) + private static native void osTerminateSignalThread(); + + private static class DispatcherThread extends Thread { + DispatcherThread() { + super(PlatformThreads.singleton().systemGroup, "Signal Dispatcher"); + this.setPriority(NEAR_MAX_PRIORITY); + this.setDaemon(true); + } + + @Override + public void run() { + while (true) { + int sig = osSignalWait(); + if (sig == osSigexitnumPd()) { + /* Terminate the signal thread. */ + return; + } + + Target_jdk_internal_misc_Signal.dispatch(sig); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java index 75760ec248ee..ee4eec597699 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateArgumentParser.java @@ -56,7 +56,7 @@ @AutomaticallyRegisteredImageSingleton public class IsolateArgumentParser { private static final RuntimeOptionKey[] OPTIONS = {SubstrateGCOptions.MinHeapSize, SubstrateGCOptions.MaxHeapSize, SubstrateGCOptions.MaxNewSize, SubstrateGCOptions.ReservedAddressSpaceSize, - SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling, SubstrateOptions.ConcealedOptions.UsePerfData, SubstrateOptions.MaxRAM}; + SubstrateOptions.ConcealedOptions.AutomaticReferenceHandling, SubstrateOptions.ConcealedOptions.UsePerfData, SubstrateOptions.ConcealedOptions.MaxRAM}; private static final int OPTION_COUNT = OPTIONS.length; private static final CGlobalData OPTION_NAMES = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNames); private static final CGlobalData OPTION_NAME_POSITIONS = CGlobalDataFactory.createBytes(IsolateArgumentParser::createOptionNamePosition); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java index 3798ab6fa429..2529b569eab4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/IsolateListenerSupport.java @@ -86,7 +86,8 @@ public interface IsolateListener { * isolate is still unattached when this method is called. */ @Uninterruptible(reason = "Thread state not yet set up.") - void afterCreateIsolate(Isolate isolate); + default void afterCreateIsolate(@SuppressWarnings("unused") Isolate isolate) { + } /** * Implementations must not throw any exceptions. Note that this method is called on diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index ce0885c9a1f6..7e9c0d67f368 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -535,9 +535,6 @@ protected void onValueUpdate(EconomicMap, Object> values, Boolean o }; } - @Option(help = "Physical memory size (in bytes). By default, the value is queried from the OS/container during VM startup.", type = OptionType.Expert)// - public static final RuntimeOptionKey MaxRAM = new RuntimeOptionKey<>(0L, Immutable); - @Option(help = "Enable detection and runtime container configuration support.")// public static final HostedOptionKey UseContainerSupport = new HostedOptionKey<>(true); @@ -1080,10 +1077,13 @@ protected void onValueUpdate(EconomicMap, Object> values, Integer o @APIOption(name = "install-exit-handlers")// @Option(help = "Provide java.lang.Terminator exit handlers", type = User)// protected static final HostedOptionKey InstallExitHandlers = new HostedOptionKey<>(false); + + @Option(help = "Physical memory size (in bytes). By default, the value is queried from the OS/container during VM startup.", type = OptionType.Expert)// + public static final RuntimeOptionKey MaxRAM = new RuntimeOptionKey<>(0L, Immutable); } @Fold - public static final boolean needsExitHandlers() { + public static boolean needsExitHandlers() { return ConcealedOptions.InstallExitHandlers.getValue() || VMInspectionOptions.hasJfrSupport() || VMInspectionOptions.hasNativeMemoryTrackingSupport(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java index 85daaba4f0d7..d936adc483a5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/PhysicalMemory.java @@ -81,7 +81,7 @@ private static boolean isInitialized() { */ public static void initialize() { assert !isInitialized() : "Physical memory already initialized."; - long memoryLimit = IsolateArgumentParser.getLongOptionValue(IsolateArgumentParser.getOptionIndex(SubstrateOptions.MaxRAM)); + long memoryLimit = IsolateArgumentParser.getLongOptionValue(IsolateArgumentParser.getOptionIndex(SubstrateOptions.ConcealedOptions.MaxRAM)); if (memoryLimit > 0) { cachedSize = WordFactory.unsigned(memoryLimit); } else if (Container.singleton().isContainerized()) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SignalHandlerSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SignalHandlerSupport.java new file mode 100644 index 000000000000..48377c2724be --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SignalHandlerSupport.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, 2024, 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 com.oracle.svm.core.jdk; + +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.IsolateListenerSupport; +import com.oracle.svm.core.IsolateListenerSupport.IsolateListener; +import com.oracle.svm.core.IsolateListenerSupportFeature; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; + +public interface SignalHandlerSupport extends IsolateListener { + @Fold + static SignalHandlerSupport singleton() { + return ImageSingletons.lookup(SignalHandlerSupport.class); + } + + long installSignalHandler(int sig, long nativeH); + + void stopDispatcherThread(); +} + +class NoSignalHandlerSupport implements SignalHandlerSupport { + @Override + public long installSignalHandler(int sig, long nativeH) { + throw new IllegalStateException("Signal handling is not supported."); + } + + @Override + public void stopDispatcherThread() { + throw VMError.shouldNotReachHere("Signal handling is not supported."); + } +} + +@AutomaticallyRegisteredFeature +class SignalHandlerFeature implements InternalFeature { + @Override + public List> getRequiredFeatures() { + return List.of(RuntimeSupportFeature.class, IsolateListenerSupportFeature.class); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + if (ImageSingletons.contains(SignalHandlerSupport.class)) { + SignalHandlerSupport support = SignalHandlerSupport.singleton(); + RuntimeSupport.getRuntimeSupport().addTearDownHook(new StopDispatcherThread()); + IsolateListenerSupport.singleton().register(support); + } else { + /* Fallback for platforms where there is no signal handling implementation. */ + ImageSingletons.add(SignalHandlerSupport.class, new NoSignalHandlerSupport()); + } + } +} + +class StopDispatcherThread implements RuntimeSupport.Hook { + @Override + public void execute(boolean isFirstIsolate) { + SignalHandlerSupport.singleton().stopDispatcherThread(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_Signal.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_Signal.java new file mode 100644 index 000000000000..6440a63432f5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_Signal.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017, 2024, 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 com.oracle.svm.core.jdk; + +import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.FromAlias; + +import java.util.Hashtable; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "jdk.internal.misc.Signal") +public final class Target_jdk_internal_misc_Signal { + // Checkstyle: stop + @Alias @RecomputeFieldValue(kind = FromAlias)// + private static Hashtable handlers = new Hashtable<>(4); + @Alias @RecomputeFieldValue(kind = FromAlias)// + private static Hashtable signals = new Hashtable<>(4); + // Checkstyle: resume + + @Substitute + private static long handle0(int sig, long nativeH) { + if (!SubstrateOptions.EnableSignalHandling.getValue()) { + throw new IllegalArgumentException("Signal handlers can't be installed if signal handling is disabled, see option '" + SubstrateOptions.EnableSignalHandling.getName() + "'."); + } + return SignalHandlerSupport.singleton().installSignalHandler(sig, nativeH); + } + + /** Called by the VM to execute Java signal handlers. */ + @Alias + public static native void dispatch(int number); + + /** Constants for the longs from {@link jdk.internal.misc.Signal}. */ + public static class Constants { + public static final long ERROR_HANDLER = -1; + public static final long DEFAULT_HANDLER = 0; + public static final long IGNORE_HANDLER = 1; + public static final long DISPATCH_HANDLER = 2; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java index 7ccfad3df0e7..bc1a30bf8567 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/PlatformTimeUtils.java @@ -64,5 +64,5 @@ public long nanosNow() { return last; } - protected abstract SecondsNanos javaTimeSystemUTC(); + public abstract SecondsNanos javaTimeSystemUTC(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java index 85625c6b5a86..ad4dfde9e0bd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/cenum/CEnumCallWrapperMethod.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.cenum; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import java.lang.reflect.Modifier; import org.graalvm.nativeimage.c.constant.CEnumLookup; @@ -32,13 +34,17 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.annotation.AnnotationValue; import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; +import com.oracle.svm.hosted.annotation.SubstrateAnnotationExtractor; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.EnumInfo; import com.oracle.svm.hosted.phases.CInterfaceEnumTool; import com.oracle.svm.hosted.phases.CInterfaceInvocationPlugin; import com.oracle.svm.hosted.phases.HostedGraphKit; +import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.nodes.StructuredGraph; @@ -52,6 +58,8 @@ * or {@link CEnumValue}. */ public class CEnumCallWrapperMethod extends CustomSubstitutionMethod { + private static final AnnotationValue[] INJECTED_ANNOTATIONS = SubstrateAnnotationExtractor.prepareInjectedAnnotations( + Uninterruptible.Utils.getAnnotation(ReflectionUtil.lookupMethod(CEnumCallWrapperMethod.class, "uninterruptibleAnnotationHolder"))); private final NativeLibraries nativeLibraries; @@ -66,8 +74,12 @@ public int getModifiers() { } @Override - public boolean isSynthetic() { - return true; + public AnnotationValue[] getInjectedAnnotations() { + /* Annotate @CEnumValue methods with @Uninterruptible. */ + if (original.getAnnotation(CEnumValue.class) != null) { + return INJECTED_ANNOTATIONS; + } + return null; } @Override @@ -99,4 +111,9 @@ private ValueNode createInvoke(AnalysisMethod method, HostedGraphKit kit, Analys throw VMError.shouldNotReachHereUnexpectedInput(method); // ExcludeFromJacocoGeneratedReport } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + @SuppressWarnings("unused") + private static void uninterruptibleAnnotationHolder() { + } } diff --git a/substratevm/src/com.oracle.svm.native.libchelper/src/cSunMiscSignal.c b/substratevm/src/com.oracle.svm.native.libchelper/src/cSunMiscSignal.c index d51cd302740b..dfa940c9ba86 100644 --- a/substratevm/src/com.oracle.svm.native.libchelper/src/cSunMiscSignal.c +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/cSunMiscSignal.c @@ -40,44 +40,24 @@ /** * Functions needed to handle signals for Java. * - * The signal handler itself, cSunMiscSignal_countingHandler, runs on a borrowed thread stack and + * The state for handling signals is global to a process and has no knowledge of isolates. That + * imposes the restriction that only one isolate at a time may register to receive signals. + * + * The signal handler itself, cSunMiscSignal_signalHandler(int), runs on a borrowed thread stack and * will not have access to any VM thread-local information or the Java heap base register. Therefore * it is written in C code rather than in Java code. * - * Any data the signal handler references must not be in the Java heap, so it is allocated here. The + * Any data that the signal handler references must not be in the Java heap, so it is allocated here. The * data consists of a table indexed by signal numbers of atomic counters, and a semaphore for * notifying of increments to the values of the counters. - * - * The other public functions here are operations on the data allocated here, for the convenience of - * the code in Target_jdk_internal_misc_Signal. - * - * The state for handling signals is global to a process, and has no knowledge of isolates. That - * imposes the restriction that only one isolate at a time may register to receive signals. - * Otherwise I would have to notify multiple isolates when a signal arrived, which violates the rule - * against per-isolate knowledge. */ /* * Forward declarations of functions. */ - -/* Public functions. */ -int cSunMiscSignal_open(); -int cSunMiscSignal_close(); -int cSunMiscSignal_await(); -int cSunMiscSignal_post(); -int cSunMiscSignal_signalRangeCheck(int const index); -long cSunMiscSignal_getCount(int const signal); -long cSunMiscSignal_decrementCount(int const signal); -sig_t cSunMiscSignal_countingHandlerFunctionPointer(); - -/* Private functions. */ -static void cSunMiscSignal_countingHandler(int const signal); -static int haveSemaphore(); -static long cSunMiscSignal_atomicIncrement(volatile long* const address); -static long cSunMiscSignal_atomicDecrementToZero(volatile long* const address); -static int cSunMiscSignal_atomicCompareAndSwap_int(volatile int* const ptr, int const oldval, int const newval); -static long cSunMiscSignal_atomicCompareAndSwap_long(volatile long* const ptr, long const oldval, long const newval); +static void cSunMiscSignal_signalHandler(int signal); +static long cSunMiscSignal_atomicDecrement(volatile long* address); +static int cSunMiscSignal_atomicCompareAndSwap_int(volatile int* ptr, int oldval, int newval); /* * State. @@ -101,180 +81,145 @@ static sem_t cSunMiscSignal_semaphore_value; * Public functions. */ -/* Open the C signal handler mechanism. */ +/* + * Open the C signal handler mechanism. Multiple isolates may execute this method in parallel. + * + * Returns 0 on success, 1 if the signal handling mechanism was already claimed by another isolate, + * or some other value if an error occurred during initialization. + */ int cSunMiscSignal_open() { - /* Claim ownership. */ - int const previousState = cSunMiscSignal_atomicCompareAndSwap_int(&cSunMiscSignal_state, cSunMiscSignal_CLOSED, cSunMiscSignal_OPEN); - if (previousState == cSunMiscSignal_CLOSED) { - /* Reset all signal counts */ - int i = 0; - while (i < NSIG) { - cSunMiscSignal_table[i] = 0; - i += 1; - } + /* Try to claim ownership over the signal handling mechanism. */ + int previousState = cSunMiscSignal_atomicCompareAndSwap_int(&cSunMiscSignal_state, cSunMiscSignal_CLOSED, cSunMiscSignal_OPEN); + if (previousState != cSunMiscSignal_CLOSED) { + /* Another isolate already owns the signal handling mechanism. */ + return 1; + } + + /* Reset all signal counts */ + for (int i = 0; i < NSIG; i++) { + cSunMiscSignal_table[i] = 0; + } + #ifdef __linux__ - /* - * Linux supports unnamed semaphore. - */ - cSunMiscSignal_semaphore = &cSunMiscSignal_semaphore_value; - if (sem_init(cSunMiscSignal_semaphore, 0, 0) != 0) { - cSunMiscSignal_semaphore = NULL; - return -1; - } -#else /* __linux__ */ - /* - * Use named semaphore elsewhere (e.g. OSX). - */ - /* Get a process-specific name for the semaphore. */ - char cSunMiscSignal_semaphoreName[NAME_MAX]; - const char* const nameFormat = "/cSunMiscSignal-%d"; - int const pid = getpid(); - int const snprintfResult = snprintf(cSunMiscSignal_semaphoreName, NAME_MAX, nameFormat, pid); - if ((snprintfResult <= 0) || (snprintfResult >= NAME_MAX)) { - return -1; - } - /* Initialize the semaphore. */ - int const oflag = O_CREAT; - int const mode = (S_IRUSR | S_IWUSR); - cSunMiscSignal_semaphore = sem_open(cSunMiscSignal_semaphoreName, oflag, mode, 0); - if (cSunMiscSignal_semaphore == SEM_FAILED) { - return -1; - } - /* Unlink the semaphore so it can be destroyed when it is closed. */ - int const unlinkResult = sem_unlink(cSunMiscSignal_semaphoreName); - if (unlinkResult != 0) { - return unlinkResult; - } -#endif /* __linux__ */ - return 0; + /* Linux supports unnamed semaphore. */ + cSunMiscSignal_semaphore = &cSunMiscSignal_semaphore_value; + if (sem_init(cSunMiscSignal_semaphore, 0, 0) != 0) { + return -1; } - errno = EBUSY; - return -1; +#else + /* On other platforms (e.g. macOS), use a named semaphore with a process-specific name. */ + char cSunMiscSignal_semaphoreName[NAME_MAX]; + const char* nameFormat = "/cSunMiscSignal-%d"; + int pid = getpid(); + int snprintfResult = snprintf(cSunMiscSignal_semaphoreName, NAME_MAX, nameFormat, pid); + if (snprintfResult <= 0 || snprintfResult >= NAME_MAX) { + return -1; + } + + /* Initialize the semaphore. */ + int oflag = O_CREAT; + int mode = (S_IRUSR | S_IWUSR); + cSunMiscSignal_semaphore = sem_open(cSunMiscSignal_semaphoreName, oflag, mode, 0); + if (cSunMiscSignal_semaphore == SEM_FAILED) { + return -1; + } + + /* Unlink the semaphore so it can be destroyed when it is closed. */ + int unlinkResult = sem_unlink(cSunMiscSignal_semaphoreName); + if (unlinkResult != 0) { + return -1; + } +#endif // __linux__ + return 0; } -/* Close the C signal handler mechanism. */ +/* Close the C signal handler mechanism. Returns 0 on success, or some non-zero value if an error occurred. */ int cSunMiscSignal_close() { - if (haveSemaphore()) { #ifdef __linux__ - int const semCloseResult = sem_destroy(cSunMiscSignal_semaphore); -#else /* __linux__ */ - int const semCloseResult = sem_close(cSunMiscSignal_semaphore); -#endif /* __linux__ */ - if (semCloseResult != 0) { - return semCloseResult; - } - cSunMiscSignal_semaphore = NULL; + int semCloseResult = sem_destroy(cSunMiscSignal_semaphore); +#else // __linux__ + int semCloseResult = sem_close(cSunMiscSignal_semaphore); +#endif // __linux__ + if (semCloseResult != 0) { + return semCloseResult; } + cSunMiscSignal_semaphore = NULL; cSunMiscSignal_state = cSunMiscSignal_CLOSED; return 0; } /* Wait for a notification on the semaphore. */ -int cSunMiscSignal_await() { - if (haveSemaphore()) { - int const semWaitResult = sem_wait(cSunMiscSignal_semaphore); - /* Treat interruption (by a signal handler) like a notification. */ - if (semWaitResult == -1 && errno == EINTR) { - return 0; - } - return semWaitResult; +int cSunMiscSignal_awaitSemaphore() { + int semWaitResult = sem_wait(cSunMiscSignal_semaphore); + /* Treat interruption (by a signal handler) like a notification. */ + if (semWaitResult == -1 && errno == EINTR) { + return 0; } - errno = EINVAL; - return -1; + return semWaitResult; } /* Notify a thread waiting on the semaphore. */ -int cSunMiscSignal_post() { - if (haveSemaphore()) { - int const semPostResult = sem_post(cSunMiscSignal_semaphore); - return semPostResult; - } - errno = EINVAL; - return -1; +int cSunMiscSignal_signalSemaphore() { + return sem_post(cSunMiscSignal_semaphore); } -/* Check that an index into the table is in (0 .. NSIG). */ -int cSunMiscSignal_signalRangeCheck(int const index) { - return ((index > 0) && (index < NSIG)); -} - -/* Return the count of outstanding signals. */ -long cSunMiscSignal_getCount(int const signal) { - if (cSunMiscSignal_signalRangeCheck(signal)) { - return cSunMiscSignal_table[signal]; - } - errno = EINVAL; - return -1; +/* Returns true if the signal is in the range of (0..NSIG). */ +bool cSunMiscSignal_signalRangeCheck(int index) { + return index > 0 && index < NSIG; } -/* Decrement a counter towards zero, given a signal number. - * Returns the previous value, or -1 if the signal is out of bounds. +/* + * Returns the number of the first pending signal, or -1 if no signal is pending. May only be + * called by a single thread (i.e., the signal dispatcher thread). */ -long cSunMiscSignal_decrementCount(int const signal) { - if (cSunMiscSignal_signalRangeCheck(signal)) { - return cSunMiscSignal_atomicDecrementToZero(&cSunMiscSignal_table[signal]); +int cSunMiscSignal_checkPendingSignal() { + for (int i = 0; i < NSIG; i++) { + if (cSunMiscSignal_table[i] > 0) { + cSunMiscSignal_atomicDecrement(&cSunMiscSignal_table[i]); + return i; + } } - errno = EINVAL; return -1; } -/* Return the address of the counting signal handler. */ -sig_t cSunMiscSignal_countingHandlerFunctionPointer() { - return cSunMiscSignal_countingHandler; +/* Returns a function pointer to the C signal handler. */ +sig_t cSunMiscSignal_signalHandlerFunctionPointer() { + return cSunMiscSignal_signalHandler; } /* * Private functions. - * - * The functions called from cSunMiscSignal_countingHandler could be macros - * if I were worried about using stack frames in the handler. - * Static methods seem to allow the compiler to inline the calls. */ -/* A signal handler that increments the count for a signal and notifies on the semaphore. */ -static void cSunMiscSignal_countingHandler(int signal) { - int const savedErrno = errno; - if (cSunMiscSignal_signalRangeCheck(signal)) { - long const previousValue = cSunMiscSignal_atomicIncrement(&cSunMiscSignal_table[signal]); - cSunMiscSignal_post(); - } - errno = savedErrno; -} - /* A wrapper around the gcc/clang built-in for atomic add. */ -static long cSunMiscSignal_atomicIncrement(volatile long* const address) { +static long cSunMiscSignal_atomicIncrement(volatile long* address) { return __sync_fetch_and_add(address, 1); } -/* Do I have a valid semaphore? */ -static int haveSemaphore() { - return ((cSunMiscSignal_semaphore != NULL) && (cSunMiscSignal_semaphore != SEM_FAILED)); -} - -/* Atomic subtract down to zero. Returns the previous value. */ -static long cSunMiscSignal_atomicDecrementToZero(volatile long* const address) { - long result = 0; - long sample; - do { - sample = *address; - /* Check if the decrement is possible. */ - if (sample <= 0) { - break; - } - /* Atomic decrement, returning the previous value. */ - result = cSunMiscSignal_atomicCompareAndSwap_long(address, sample, sample - 1); - } while (result != sample); - return result; +/* A wrapper around the gcc/clang built-in for atomic add. */ +static long cSunMiscSignal_atomicDecrement(volatile long* address) { + return __sync_fetch_and_add(address, -1); } /* A wrapper around the gcc/clang built-in for atomic int compare and exchange. */ -static int cSunMiscSignal_atomicCompareAndSwap_int(volatile int* const ptr, int const oldval, int const newval) { +static int cSunMiscSignal_atomicCompareAndSwap_int(volatile int* ptr, int oldval, int newval) { return __sync_val_compare_and_swap(ptr, oldval, newval); } /* A wrapper around the gcc/clang built-in for atomic long compare and exchange. */ -static long cSunMiscSignal_atomicCompareAndSwap_long(volatile long* const ptr, long const oldval, long const newval) { +static long cSunMiscSignal_atomicCompareAndSwap_long(volatile long* ptr, long oldval, long newval) { return __sync_val_compare_and_swap(ptr, oldval, newval); } -#endif + +/* A signal handler that increments the count for the received signal and notifies on the semaphore. */ +static void cSunMiscSignal_signalHandler(int signal) { + int savedErrno = errno; + if (cSunMiscSignal_signalRangeCheck(signal)) { + cSunMiscSignal_atomicIncrement(&cSunMiscSignal_table[signal]); + cSunMiscSignal_signalSemaphore(); + } + errno = savedErrno; +} + +#endif // !_WIN64