[EG-3461] removed dependency from tools.jar (#6631)

* removed dependency from tools.jar

I removed the log line in /node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt because I felt it was not so important
and I modified the checkpoint agent detection simply using a static field (I tested both with and without the checkpoint agent running and detection works correctly)

* move method to node-api to address review comments

Co-authored-by: Walter Oggioni <walter.oggioni@r3.com>
This commit is contained in:
Stefano Franz 2020-08-14 11:56:37 +02:00 committed by GitHub
parent bf53e47f0d
commit 205ce84033
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 108 additions and 63 deletions

View File

@ -0,0 +1,26 @@
package net.corda.nodeapi.internal
import net.corda.core.internal.VisibleForTesting
class JVMAgentUtilities {
companion object {
@VisibleForTesting
@Suppress("NestedBlockDepth")
fun parseDebugPort(args: Iterable<String>): Short? {
val debugArgumentPrefix = "-agentlib:jdwp="
for (arg in args) {
if (arg.startsWith(debugArgumentPrefix)) {
for (keyValuePair in arg.substring(debugArgumentPrefix.length + 1).split(",")) {
val equal = keyValuePair.indexOf('=')
if (equal >= 0 && keyValuePair.startsWith("address")) {
val portBegin = (keyValuePair.lastIndexOf(':').takeUnless { it < 0 } ?: equal) + 1
return keyValuePair.substring(portBegin).toShort()
}
}
}
}
return null
}
}
}

View File

@ -0,0 +1,32 @@
package net.corda.nodeapi.internal
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
class ParseDebugPortTest(private val args: Iterable<String>,
private val expectedPort: Short?,
@Suppress("unused_parameter") description : String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{2}")
fun load() = arrayOf(
arrayOf(emptyList<String>(), null, "No arguments"),
arrayOf(listOf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1234"), 1234.toShort(), "Debug argument"),
arrayOf(listOf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0.0.0.0:7777"), 7777.toShort(), "Debug argument with bind address"),
arrayOf(listOf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y"), null, "Debug argument without port"),
arrayOf(listOf("-version", "-Dmy.jvm.property=someValue"), null, "Unrelated arguments"),
arrayOf(listOf("-Dcapsule.jvm.args=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=4321",
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1234"), 1234.toShort(), "Debug argument and capsule arguments")
)
}
@Test(timeout = 10_000)
fun test() {
val port = JVMAgentUtilities.parseDebugPort(args)
Assert.assertEquals(expectedPort, port)
}
}

View File

@ -226,9 +226,6 @@ dependencies {
// Adding native SSL library to allow using native SSL with Artemis and AMQP
compile "io.netty:netty-tcnative-boringssl-static:$tcnative_version"
// Required by JVMAgentUtil (x-compatible java 8 & 11 agent lookup mechanism)
compile files("${System.properties['java.home']}/../lib/tools.jar")
// Byteman for runtime (termination) rules injection on the running node
// Submission tool allowing to install rules on running nodes
slowIntegrationTestCompile "org.jboss.byteman:byteman-submit:4.0.11"

View File

@ -28,8 +28,8 @@ import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.lo
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.utilities.JVMAgentUtil.getJvmAgentProperties
import net.corda.node.utilities.registration.NodeRegistrationException
import net.corda.nodeapi.internal.JVMAgentUtilities
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
@ -154,6 +154,8 @@ open class NodeStartup : NodeStartupLogging {
const val LOGS_DIRECTORY_NAME = "logs"
const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
const val ERROR_CODE_RESOURCE_LOCATION = "error-codes"
}
lateinit var cmdLineOptions: SharedNodeCmdLineOptions
@ -270,10 +272,10 @@ open class NodeStartup : NodeStartupLogging {
logger.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}")
logger.info("Machine: ${lookupMachineNameAndMaybeWarn()}")
logger.info("Working Directory: ${cmdLineOptions.baseDirectory}")
val agentProperties = getJvmAgentProperties(logger)
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
JVMAgentUtilities.parseDebugPort(info.inputArguments) ?.let {
logger.info("Debug port: $it")
}
var nodeStartedMessage = "Starting as node on ${conf.p2pAddress}"
if (conf.extraNetworkMapKeys.isNotEmpty()) {
nodeStartedMessage = "$nodeStartedMessage with additional Network Map keys ${conf.extraNetworkMapKeys.joinToString(prefix = "[", postfix = "]", separator = ", ")}"

View File

@ -33,7 +33,6 @@ import net.corda.core.flows.FlowSession
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.AppServiceHub.Companion.SERVICE_PRIORITY_NORMAL
import net.corda.core.internal.FlowAsyncOperation
import net.corda.core.internal.FlowIORequest
import net.corda.core.internal.WaitForStateConsumption
@ -43,6 +42,7 @@ import net.corda.core.internal.exists
import net.corda.core.internal.objectOrNewInstance
import net.corda.core.internal.outputStream
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.AppServiceHub.Companion.SERVICE_PRIORITY_NORMAL
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes
@ -54,9 +54,6 @@ import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver.Companion.reportSuccess
import net.corda.node.internal.NodeStartup
import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.statemachine.Checkpoint
@ -68,7 +65,9 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.SessionId
import net.corda.node.services.statemachine.SessionState
import net.corda.node.services.statemachine.SubFlow
import net.corda.node.utilities.JVMAgentUtil.getJvmAgentProperties
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver
import net.corda.nodeapi.internal.lifecycle.NodeLifecycleObserver.Companion.reportSuccess
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl
import net.corda.serialization.internal.withTokenContext
@ -77,21 +76,24 @@ import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter
import java.util.UUID
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.reflect.KProperty1
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.memberProperties
class CheckpointDumperImpl(private val checkpointStorage: CheckpointStorage, private val database: CordaPersistence,
private val serviceHub: ServiceHub, val baseDirectory: Path) : NodeLifecycleObserver {
private val serviceHub: ServiceHub, val baseDirectory: Path) : NodeLifecycleObserver {
companion object {
internal val TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss").withZone(UTC)
private val log = contextLogger()
private val DUMPABLE_CHECKPOINTS = setOf(
Checkpoint.FlowStatus.RUNNABLE,
Checkpoint.FlowStatus.HOSPITALIZED,
Checkpoint.FlowStatus.PAUSED
Checkpoint.FlowStatus.RUNNABLE,
Checkpoint.FlowStatus.HOSPITALIZED,
Checkpoint.FlowStatus.PAUSED
)
}
@ -102,12 +104,10 @@ class CheckpointDumperImpl(private val checkpointStorage: CheckpointStorage, pri
private lateinit var checkpointSerializationContext: CheckpointSerializationContext
private lateinit var writer: ObjectWriter
private val isCheckpointAgentRunning by lazy {
checkpointAgentRunning()
}
private val isCheckpointAgentRunning by lazy(::checkpointAgentRunning)
override fun update(nodeLifecycleEvent: NodeLifecycleEvent): Try<String> {
return when(nodeLifecycleEvent) {
return when (nodeLifecycleEvent) {
is NodeLifecycleEvent.AfterNodeStart<*> -> Try.on {
checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
CheckpointSerializeAsTokenContextImpl(
@ -190,13 +190,20 @@ class CheckpointDumperImpl(private val checkpointStorage: CheckpointStorage, pri
}
}
private fun checkpointAgentRunning(): Boolean {
val agentProperties = getJvmAgentProperties(log)
val pattern = "(.+)?checkpoint-agent(-.+)?\\.jar.*".toRegex()
return agentProperties.values.any { value ->
value is String && value.contains(pattern)
}
}
/**
* Note that this method dynamically uses [net.corda.tools.CheckpointAgent.running], make sure to keep it up to date with
* the checkpoint agent source code
*/
private fun checkpointAgentRunning() = try {
javaClass.classLoader.loadClass("net.corda.tools.CheckpointAgent").kotlin.companionObject
} catch (e: ClassNotFoundException) {
null
}?.let { cls ->
@Suppress("UNCHECKED_CAST")
cls.memberProperties.find { it.name == "running"}
?.let {it as KProperty1<Any, Boolean>}
?.get(cls.objectInstance!!)
} ?: false
private fun Checkpoint.toJson(id: UUID, now: Instant): CheckpointJson {
val (fiber, flowLogic) = when (flowState) {
@ -402,6 +409,7 @@ class CheckpointDumperImpl(private val checkpointStorage: CheckpointStorage, pri
private interface FlowAsyncOperationMixin {
@get:JsonIgnore
val serviceHub: ServiceHub
// [Any] used so this single mixin can serialize [FlowExternalOperation] and [FlowExternalAsyncOperation]
@get:JsonUnwrapped
val operation: Any

View File

@ -1,25 +0,0 @@
package net.corda.node.utilities
import com.sun.tools.attach.VirtualMachine
import org.slf4j.Logger
import java.lang.management.ManagementFactory
import java.util.*
object JVMAgentUtil {
/**
* Utility to attach to own VM at run-time and obtain agent details.
* In Java 9 this requires setting the following run-time jvm flag: -Djdk.attach.allowAttachSelf=true
* This mechanism supersedes the usage of VMSupport which is not available from Java 9 onwards.
*/
fun getJvmAgentProperties(log: Logger): Properties {
val jvmPid = ManagementFactory.getRuntimeMXBean().name.substringBefore('@')
return try {
val vm = VirtualMachine.attach(jvmPid)
return vm.agentProperties
} catch (e: Throwable) {
log.warn("Unable to determine whether agent is running: ${e.message}.\n" +
"You may need to pass in -Djdk.attach.allowAttachSelf=true if running on a Java 9 or later VM")
Properties()
}
}
}

View File

@ -31,23 +31,22 @@ apply plugin: 'com.jfrog.artifactory'
description 'A javaagent to allow hooking into Kryo checkpoints'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "javassist:javassist:$javaassist_version"
compile "com.esotericsoftware:kryo:$kryo_version"
compile "co.paralleluniverse:quasar-core:$quasar_version"
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compileOnly "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compileOnly "javassist:javassist:$javaassist_version"
compileOnly "com.esotericsoftware:kryo:$kryo_version"
compileOnly "co.paralleluniverse:quasar-core:$quasar_version"
compile (project(':core')) {
compileOnly (project(':core')) {
transitive = false
}
// Unit testing helpers.
compile "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
compile "junit:junit:$junit_version"
testCompile "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
testCompile "junit:junit:$junit_version"
// SLF4J: commons-logging bindings for a SLF4J back end
compile "org.slf4j:jcl-over-slf4j:$slf4j_version"
compile "org.slf4j:slf4j-api:$slf4j_version"
compileOnly "org.slf4j:slf4j-api:$slf4j_version"
}
jar {

View File

@ -51,8 +51,14 @@ class CheckpointAgent {
LoggerFactory.getLogger("CheckpointAgent")
}
val running by lazy {
premainExecuted
}
private var premainExecuted = false
@JvmStatic
fun premain(argumentsString: String?, instrumentation: Instrumentation) {
premainExecuted = true
parseArguments(argumentsString)
instrumentation.addTransformer(CheckpointHook)
}