8000 Use reflection to install system signal handlers in JVM applications by kyri-petrou · Pull Request #9251 · zio/zio · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Use reflection to install system signal handlers in JVM applications #9251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Args = -H:DynamicProxyConfigurationResources=${.}/sun-misc-signal-proxy-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"interfaces": [
"sun.misc.SignalHandler"
]
}
]
24 changes: 2 additions & 22 deletions core/jvm/src/main/scala/zio/internal/PlatformSpecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,8 @@ private[zio] trait PlatformSpecific {
* Adds a signal handler for the specified signal (e.g. "INFO"). This method
* never fails even if adding the handler fails.
*/
final def addSignalHandler(signal: String, action: () => Unit)(implicit unsafe: zio.Unsafe): Unit = {
import sun.misc.Signal
import sun.misc.SignalHandler

final case class ZIOSignalHandler(action: () => Unit) extends SignalHandler {
private[zio] var signalHandler: SignalHandler = null
override def handle(signal: Signal): Unit = {
action()
if (signalHandler != SignalHandler.SIG_DFL && signalHandler != SignalHandler.SIG_IGN) {
signalHandler.handle(signal)
}
}
}

try {
val zioSignal = new Signal(signal)
val zioSignalHandler = ZIOSignalHandler(action)
zioSignalHandler.signalHandler = Signal.handle(zioSignal, zioSignalHandler)
} catch {
case _: Throwable => ()
}
}
final def addSignalHandler(signal: String, action: () => Unit)(implicit unsafe: zio.Unsafe): Unit =
Signal.handle(signal, _ => action())

// Check the classpath to see if we're running in an unforked sbt environment.
private val isUnforkedInSbt =
Expand Down
124 changes: 124 additions & 0 deletions core/jvm/src/main/scala/zio/internal/Signal.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package zio.internal

import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.{InvocationHandler, Method, Proxy}
import java.util.function.Consumer
import java.util.logging.Logger

/**
* * This class is used to handle system signals in a platform-specific way. It
* attemps to use the sun.misc.Signal and sun.misc.SignalHandler classes if they
* are available. If they are not available, it will gracefully degrade to a
* no-op.
*
* '''IMPLEMENTATION NOTE''': This class uses reflection to access the
* sun.misc.Signal and sun.misc.SignalHandler. This is necessary because the
* sun.misc.Signal and sun.misc.SignalHandler classes might not be available in
* some JVMs.
*/
private object Signal {

private val signalHandler: Signal.SignalHandler = {
val logger = Logger.getLogger("zio.internal.Signal")
val signalClass = findClass("sun.misc.Signal")
val signalHandlerClass = findClass("sun.misc.SignalHandler")

var constructorHandle: MethodHandle = null
var staticMethodHandle: MethodHandle = null

if ((signalClass ne null) && (signalHandlerClass ne null)) {
val lookup = MethodHandles.lookup()
constructorHandle = initSignalConstructorMethodHandle(lookup, signalClass)
staticMethodHandle = initHandleStaticMethodHandle(lookup, signalClass, signalHandlerClass)
} else {
constructorHandle = null
staticMethodHandle = null
}

if ((signalHandlerClass ne null) && (constructorHandle ne null) && (staticMethodHandle ne null))
new SignalHandler.SunMiscSignalHandler(signalHandlerClass, constructorHandle, staticMethodHandle)
else {
logger.warning(
"sun.misc.Signal and sun.misc.SignalHandler are not available on this platform. Defaulting to no-op signal handling implementation; ZIO fiber dump functionality might not work as expected."
)
new SignalHandler.NoOpSignalHandler
}
}

private[internal] def handle(signal: String, handler: Consumer[AnyRef]): Unit =
signalHandler.handle(signal, handler)

private def findClass(name: String): Class[?] =
try {
Class.forName(name)
} catch {
case _: ClassNotFoundException => null
}

private def initSignalConstructorMethodHandle(
lookup: MethodHandles.Lookup,
signalClass: Class[?]
): MethodHandle = {
val signalConstructorMethodType = MethodType.methodType(classOf[Unit], classOf[String])
try {
lookup.findConstructor(signalClass, signalConstructorMethodType)
} catch {
case _: Exception => null
}
}

private def initHandleStaticMethodHandle(
lookup: MethodHandles.Lookup,
signalClass: Class[?],
signalHandlerClass: Class[?]
) =
try {
val handleStaticMethodType = MethodType.methodType(signalHandlerClass, signalClass, signalHandlerClass)
lookup.findStatic(signalClass, "handle", handleStaticMethodType)
} catch {
case _: Exception => null
}

private abstract sealed class SignalHandler {
def handle(signal: String, handler: Consumer[AnyRef]): Unit
}

private object SignalHandler {

final class SunMiscSignalHandler(
signalHandlerClass: Class[?],
constuctorMethodHandle: MethodHandle,
staticMethodHandle: MethodHandle
) extends Signal.SignalHandler {

override def handle(signal: String, handler: Consumer[AnyRef]): Unit = {
val invocationHandler = initInvocationHandler(handler)
val proxy = Proxy.newProxyInstance(
Signal.getClass.getClassLoader,
Array(signalHandlerClass),
invocationHandler
)
try {
val s = constuctorMethodHandle.invoke(signal)
staticMethodHandle.invoke(s, proxy)
} catch {
// Gracefully degrade to a no-op
case _: Throwable => ()
}
}

private def initInvocationHandler(handler: Consumer[AnyRef]): InvocationHandler =
(proxy: Any, method: Method, args: Array[AnyRef]) => {
if (args.nonEmpty) handler.accept(args(0))
null
}

}

final class NoOpSignalHandler extends SignalHandler {
override def handle(signal: String, handler: Consumer[AnyRef]): Unit = ()
}
}
}
Loading
0