mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
Added --logging-level cmd line arg to set the logging level, and improved flow logging
This commit is contained in:
parent
48c65ac5d2
commit
40dde555e7
@ -6,17 +6,14 @@
|
|||||||
<Property name="log-name">node-${hostName}</Property>
|
<Property name="log-name">node-${hostName}</Property>
|
||||||
<Property name="archive">${sys:log-path}/archive</Property>
|
<Property name="archive">${sys:log-path}/archive</Property>
|
||||||
<Property name="consoleLogLevel">error</Property>
|
<Property name="consoleLogLevel">error</Property>
|
||||||
|
<Property name="defaultLogLevel">info</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|
||||||
<ThresholdFilter level="trace"/>
|
<ThresholdFilter level="trace"/>
|
||||||
|
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||||
<PatternLayout>
|
<PatternLayout pattern="%highlight{%level{length=1} %d{HH:mm:ss} [%t] %c{2}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red}" />
|
||||||
<pattern>
|
|
||||||
%highlight{%level{length=1} %d{HH:mm:ss} %T %c{1}.%M - %msg%n}{INFO=white,WARN=red,FATAL=bright red blink}
|
|
||||||
</pattern>>
|
|
||||||
</PatternLayout>
|
|
||||||
</Console>
|
</Console>
|
||||||
|
|
||||||
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
|
<!-- Will generate up to 10 log files for a given day. During every rollover it will delete
|
||||||
@ -25,7 +22,7 @@
|
|||||||
fileName="${sys:log-path}/${log-name}.log"
|
fileName="${sys:log-path}/${log-name}.log"
|
||||||
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
|
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
|
||||||
|
|
||||||
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{1} - %msg%n"/>
|
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{2}.%M - %msg%n"/>
|
||||||
|
|
||||||
<Policies>
|
<Policies>
|
||||||
<TimeBasedTriggeringPolicy/>
|
<TimeBasedTriggeringPolicy/>
|
||||||
@ -47,13 +44,9 @@
|
|||||||
</Appenders>
|
</Appenders>
|
||||||
|
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Root level="info">
|
<Root level="${sys:defaultLogLevel}">
|
||||||
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
|
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
|
||||||
<AppenderRef ref="RollingFile-Appender" level="info"/>
|
<AppenderRef ref="RollingFile-Appender" />
|
||||||
</Root>
|
</Root>
|
||||||
<Logger name="net.corda" level="info" additivity="false">
|
|
||||||
<AppenderRef ref="Console-Appender" level="${sys:consoleLogLevel}"/>
|
|
||||||
<AppenderRef ref="RollingFile-Appender"/>
|
|
||||||
</Logger>
|
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
@ -1,19 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Configuration status="info">
|
<Configuration status="info">
|
||||||
|
<Properties>
|
||||||
|
<Property name="defaultLogLevel">info</Property>
|
||||||
|
</Properties>
|
||||||
<Appenders>
|
<Appenders>
|
||||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||||
<PatternLayout>
|
<PatternLayout pattern="[%-5level] %d{HH:mm:ss.SSS} [%t] %c{2}.%M - %msg%n" />
|
||||||
<pattern>
|
|
||||||
[%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n
|
|
||||||
</pattern>>
|
|
||||||
</PatternLayout>
|
|
||||||
</Console>
|
</Console>
|
||||||
</Appenders>
|
</Appenders>
|
||||||
<Loggers>
|
<Loggers>
|
||||||
<Root level="info">
|
<Root level="info">
|
||||||
<AppenderRef ref="Console-Appender"/>
|
<AppenderRef ref="Console-Appender"/>
|
||||||
</Root>
|
</Root>
|
||||||
<Logger name="net.corda" level="info" additivity="false">
|
<Logger name="net.corda" level="${sys:defaultLogLevel}" additivity="false">
|
||||||
<AppenderRef ref="Console-Appender"/>
|
<AppenderRef ref="Console-Appender"/>
|
||||||
</Logger>
|
</Logger>
|
||||||
</Loggers>
|
</Loggers>
|
||||||
|
@ -23,5 +23,5 @@ import java.security.PublicKey
|
|||||||
class Party(val name: String, owningKey: CompositeKey) : AnonymousParty(owningKey) {
|
class Party(val name: String, owningKey: CompositeKey) : AnonymousParty(owningKey) {
|
||||||
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
|
/** A helper constructor that converts the given [PublicKey] in to a [CompositeKey] with a single node */
|
||||||
constructor(name: String, owningKey: PublicKey) : this(name, owningKey.composite)
|
constructor(name: String, owningKey: PublicKey) : this(name, owningKey.composite)
|
||||||
override fun toString() = "${owningKey.toBase58String()} (name)"
|
override fun toString() = "${owningKey.toBase58String()} ($name)"
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ import net.corda.core.node.ServiceHub
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.ProgressTracker
|
import net.corda.core.utilities.ProgressTracker
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
@ -135,7 +136,9 @@ abstract class FlowLogic<out T> {
|
|||||||
if (shareParentSessions) {
|
if (shareParentSessions) {
|
||||||
subLogic.sessionFlow = this
|
subLogic.sessionFlow = this
|
||||||
}
|
}
|
||||||
|
logger.debug { "Calling subflow: $subLogic" }
|
||||||
val result = subLogic.call()
|
val result = subLogic.call()
|
||||||
|
logger.debug { "Subflow finished with result $result" }
|
||||||
// It's easy to forget this when writing flows so we just step it to the DONE state when it completes.
|
// It's easy to forget this when writing flows so we just step it to the DONE state when it completes.
|
||||||
subLogic.progressTracker?.currentStep = ProgressTracker.DONE
|
subLogic.progressTracker?.currentStep = ProgressTracker.DONE
|
||||||
return result
|
return result
|
||||||
|
@ -44,12 +44,16 @@ abstract class BaseTransaction(
|
|||||||
if (timestamp != null) check(notary != null) { "If a timestamp is provided, there must be a notary." }
|
if (timestamp != null) check(notary != null) { "If a timestamp is provided, there must be a notary." }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?) =
|
override fun equals(other: Any?): Boolean {
|
||||||
other is BaseTransaction &&
|
if (other === this) return true
|
||||||
notary == other.notary &&
|
return other is BaseTransaction &&
|
||||||
mustSign == other.mustSign &&
|
notary == other.notary &&
|
||||||
type == other.type &&
|
mustSign == other.mustSign &&
|
||||||
timestamp == other.timestamp
|
type == other.type &&
|
||||||
|
timestamp == other.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
override fun hashCode() = Objects.hash(notary, mustSign, type, timestamp)
|
override fun hashCode() = Objects.hash(notary, mustSign, type, timestamp)
|
||||||
|
|
||||||
|
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
||||||
}
|
}
|
||||||
|
@ -143,4 +143,6 @@ data class SignedTransaction(val txBits: SerializedBytes<WireTransaction>,
|
|||||||
* @return a digital signature of the transaction.
|
* @return a digital signature of the transaction.
|
||||||
*/
|
*/
|
||||||
fun signWithECDSA(keyPair: KeyPair) = keyPair.signWithECDSA(this.id.bytes)
|
fun signWithECDSA(keyPair: KeyPair) = keyPair.signWithECDSA(this.id.bytes)
|
||||||
|
|
||||||
|
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import net.corda.core.contracts.Attachment
|
|||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.sha256
|
import net.corda.core.crypto.sha256
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,18 +15,19 @@ class FetchAttachmentsFlow(requests: Set<SecureHash>,
|
|||||||
|
|
||||||
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
|
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
|
||||||
|
|
||||||
override fun convert(wire: ByteArray): Attachment {
|
override fun convert(wire: ByteArray): Attachment = ByteArrayAttachment(wire)
|
||||||
return object : Attachment {
|
|
||||||
override fun open(): InputStream = ByteArrayInputStream(wire)
|
|
||||||
override val id: SecureHash = wire.sha256()
|
|
||||||
override fun equals(other: Any?) = (other is Attachment) && other.id == id
|
|
||||||
override fun hashCode(): Int = id.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun maybeWriteToDisk(downloaded: List<Attachment>) {
|
override fun maybeWriteToDisk(downloaded: List<Attachment>) {
|
||||||
for (attachment in downloaded) {
|
for (attachment in downloaded) {
|
||||||
serviceHub.storageService.attachments.importAttachment(attachment.open())
|
serviceHub.storageService.attachments.importAttachment(attachment.open())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ByteArrayAttachment(private val wire : ByteArray) : Attachment {
|
||||||
|
override val id: SecureHash by lazy { wire.sha256() }
|
||||||
|
override fun open(): InputStream = wire.inputStream()
|
||||||
|
override fun equals(other: Any?) = other === this || other is Attachment && other.id == this.id
|
||||||
|
override fun hashCode(): Int = id.hashCode()
|
||||||
|
override fun toString(): String = "${javaClass.simpleName}(id=$id)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ class ExampleNodeConfTest {
|
|||||||
baseDirectory,
|
baseDirectory,
|
||||||
ConfigHelper.loadConfig(
|
ConfigHelper.loadConfig(
|
||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
configFileOverride = Paths.get(configResource.toURI())
|
configFile = Paths.get(configResource.toURI())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// Force the config fields as they are resolved lazily
|
// Force the config fields as they are resolved lazily
|
||||||
|
@ -8,14 +8,11 @@ Jetty web server exposes the same interface over HTTP.
|
|||||||
Logging
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
In the default configuration logs are stored to the logs subdirectory of the node directory and are rotated from time to time. You can
|
By default the node log files are stored to the ``logs`` subdirectory of the working directory and are rotated from time
|
||||||
have logging printed to the console as well by passing the ``--log-to-console`` command line flag. Corda
|
to time. You can have logging printed to the console as well by passing the ``--log-to-console`` command line flag.
|
||||||
uses the SL4J logging façade which is configured with the log4j2 binding framework to manage its logging,
|
The default logging level is ``INFO`` which can be adjusted by the ``--logging-level`` command line argument. For more
|
||||||
so you can also configure it in more detail by writing a custom log4j2 logging configuration file and passing ``-Dlog4j.configurationFile=my-config-file.xml``
|
custom logging, the logger settings can be completely overridden with a `Log4j 2 <https://logging.apache.org/log4j/2.x>`_
|
||||||
on the command line as well. The default configuration is copied during the build from ``config/dev/log4j2.xml``, or for the test sourceSet from ``config/test/log4j2.xml``.
|
configuration file assigned to the ``log4j.configurationFile`` system property.
|
||||||
|
|
||||||
In corda code a logger is typically instantiated via the ``net.corda.core.utilities.loggerFor`` utility method which will create an SL4J ``Logger`` with a name based on the type parameter.
|
|
||||||
Also, available in ``net.corda.core.utilities``, are extension methods to take a lazily evaluated logging lambda for trace and debug level, which will not evaluate the lambda if the LogLevel threshold is higher.
|
|
||||||
|
|
||||||
Database access
|
Database access
|
||||||
---------------
|
---------------
|
||||||
|
@ -11,7 +11,6 @@ import net.corda.core.messaging.StateMachineUpdate
|
|||||||
import net.corda.core.messaging.startFlow
|
import net.corda.core.messaging.startFlow
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.transactions.SignedTransaction
|
|
||||||
import net.corda.flows.CashIssueFlow
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.flows.CashPaymentFlow
|
import net.corda.flows.CashPaymentFlow
|
||||||
import net.corda.node.driver.DriverBasedTest
|
import net.corda.node.driver.DriverBasedTest
|
||||||
|
@ -2,8 +2,10 @@ package net.corda.node
|
|||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionParser
|
||||||
|
import joptsimple.util.EnumConverter
|
||||||
import net.corda.core.div
|
import net.corda.core.div
|
||||||
import net.corda.node.services.config.ConfigHelper
|
import net.corda.node.services.config.ConfigHelper
|
||||||
|
import org.slf4j.event.Level
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@ -20,6 +22,11 @@ class ArgsParser {
|
|||||||
.accepts("config-file", "The path to the config file")
|
.accepts("config-file", "The path to the config file")
|
||||||
.withRequiredArg()
|
.withRequiredArg()
|
||||||
.defaultsTo("node.conf")
|
.defaultsTo("node.conf")
|
||||||
|
private val loggerLevel = optionParser
|
||||||
|
.accepts("logging-level", "Enable logging at this level and higher")
|
||||||
|
.withRequiredArg()
|
||||||
|
.withValuesConvertedBy(object : EnumConverter<Level>(Level::class.java) {})
|
||||||
|
.defaultsTo(Level.INFO)
|
||||||
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
|
private val logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
|
||||||
private val isWebserverArg = optionParser.accepts("webserver")
|
private val isWebserverArg = optionParser.accepts("webserver")
|
||||||
private val helpArg = optionParser.accepts("help").forHelp()
|
private val helpArg = optionParser.accepts("help").forHelp()
|
||||||
@ -31,14 +38,22 @@ class ArgsParser {
|
|||||||
}
|
}
|
||||||
val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath()
|
val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath()
|
||||||
val configFile = baseDirectory / optionSet.valueOf(configFileArg)
|
val configFile = baseDirectory / optionSet.valueOf(configFileArg)
|
||||||
|
val help = optionSet.has(helpArg)
|
||||||
|
val loggingLevel = optionSet.valueOf(loggerLevel)
|
||||||
|
val logToConsole = optionSet.has(logToConsoleArg)
|
||||||
val isWebserver = optionSet.has(isWebserverArg)
|
val isWebserver = optionSet.has(isWebserverArg)
|
||||||
return CmdLineOptions(baseDirectory, configFile, optionSet.has(helpArg), optionSet.has(logToConsoleArg), isWebserver)
|
return CmdLineOptions(baseDirectory, configFile, help, loggingLevel, logToConsole, isWebserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CmdLineOptions(val baseDirectory: Path, val configFile: Path?, val help: Boolean, val logToConsole: Boolean, val isWebserver: Boolean) {
|
data class CmdLineOptions(val baseDirectory: Path,
|
||||||
|
val configFile: Path,
|
||||||
|
val help: Boolean,
|
||||||
|
val loggingLevel: Level,
|
||||||
|
val logToConsole: Boolean,
|
||||||
|
val isWebserver: Boolean) {
|
||||||
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
|
fun loadConfig(allowMissingConfig: Boolean = false, configOverrides: Map<String, Any?> = emptyMap()): Config {
|
||||||
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
return ConfigHelper.loadConfig(baseDirectory, configFile, allowMissingConfig, configOverrides)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
import com.typesafe.config.ConfigException
|
import com.typesafe.config.ConfigException
|
||||||
|
import joptsimple.OptionException
|
||||||
import net.corda.core.*
|
import net.corda.core.*
|
||||||
import net.corda.core.utilities.Emoji
|
import net.corda.core.utilities.Emoji
|
||||||
import net.corda.node.internal.Node
|
import net.corda.node.internal.Node
|
||||||
@ -14,7 +15,6 @@ import org.slf4j.LoggerFactory
|
|||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
private var renderBasicInfoToConsole = true
|
private var renderBasicInfoToConsole = true
|
||||||
@ -38,8 +38,9 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
val cmdlineOptions = try {
|
val cmdlineOptions = try {
|
||||||
argsParser.parse(*args)
|
argsParser.parse(*args)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: OptionException) {
|
||||||
println("Unknown command line arguments: ${ex.message}")
|
println("Invalid command line arguments: ${ex.message}")
|
||||||
|
argsParser.printHelp(System.out)
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,10 +50,10 @@ fun main(args: Array<String>) {
|
|||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up logging.
|
// Set up logging. These properties are referenced from the XML config file.
|
||||||
|
System.setProperty("defaultLogLevel", cmdlineOptions.loggingLevel.name.toLowerCase())
|
||||||
if (cmdlineOptions.logToConsole) {
|
if (cmdlineOptions.logToConsole) {
|
||||||
// This property is referenced from the XML config file.
|
System.setProperty("consoleLogLevel", cmdlineOptions.loggingLevel.name.toLowerCase())
|
||||||
System.setProperty("consoleLogLevel", "info")
|
|
||||||
renderBasicInfoToConsole = false
|
renderBasicInfoToConsole = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ sealed class PortAllocation {
|
|||||||
* @param portAllocation The port allocation strategy to use for the messaging and the web server addresses. Defaults to incremental.
|
* @param portAllocation The port allocation strategy to use for the messaging and the web server addresses. Defaults to incremental.
|
||||||
* @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental.
|
* @param debugPortAllocation The port allocation strategy to use for jvm debugging. Defaults to incremental.
|
||||||
* @param useTestClock If true the test clock will be used in Node.
|
* @param useTestClock If true the test clock will be used in Node.
|
||||||
* @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode.
|
* @param isDebug Indicates whether the spawned nodes should start in jdwt debug mode and have debug level logging.
|
||||||
* @param dsl The dsl itself.
|
* @param dsl The dsl itself.
|
||||||
* @return The value returned in the [dsl] closure.
|
* @return The value returned in the [dsl] closure.
|
||||||
*/
|
*/
|
||||||
@ -502,19 +502,19 @@ open class DriverDSL(
|
|||||||
else
|
else
|
||||||
""
|
""
|
||||||
|
|
||||||
val additionalKeys = listOf("amq.delivery.delay.ms")
|
val inheritedProperties = listOf("amq.delivery.delay.ms")
|
||||||
|
|
||||||
val systemArgs = mutableMapOf(
|
val systemArgs = mutableMapOf(
|
||||||
"name" to nodeConf.myLegalName,
|
"name" to nodeConf.myLegalName,
|
||||||
"visualvm.display.name" to "Corda"
|
"visualvm.display.name" to "Corda"
|
||||||
)
|
)
|
||||||
|
|
||||||
for (key in additionalKeys) {
|
inheritedProperties.forEach {
|
||||||
if (System.getProperty(key) != null) {
|
val property = System.getProperty(it)
|
||||||
systemArgs.set(key, System.getProperty(key))
|
if (property != null) systemArgs[it] = property
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val loggingLevel = if (debugPort == null) "INFO" else "DEBUG"
|
||||||
val javaArgs = listOf(path) +
|
val javaArgs = listOf(path) +
|
||||||
systemArgs.map { "-D${it.key}=${it.value}" } +
|
systemArgs.map { "-D${it.key}=${it.value}" } +
|
||||||
listOf(
|
listOf(
|
||||||
@ -524,7 +524,8 @@ open class DriverDSL(
|
|||||||
"-XX:+UseG1GC",
|
"-XX:+UseG1GC",
|
||||||
"-cp", classpath,
|
"-cp", classpath,
|
||||||
className,
|
className,
|
||||||
"--base-directory=${nodeConf.baseDirectory}"
|
"--base-directory=${nodeConf.baseDirectory}",
|
||||||
|
"--logging-level=$loggingLevel"
|
||||||
).filter(String::isNotEmpty)
|
).filter(String::isNotEmpty)
|
||||||
val builder = ProcessBuilder(javaArgs)
|
val builder = ProcessBuilder(javaArgs)
|
||||||
builder.redirectError(Paths.get("error.$className.log").toFile())
|
builder.redirectError(Paths.get("error.$className.log").toFile())
|
||||||
|
@ -28,19 +28,16 @@ object ConfigHelper {
|
|||||||
private val log = loggerFor<ConfigHelper>()
|
private val log = loggerFor<ConfigHelper>()
|
||||||
|
|
||||||
fun loadConfig(baseDirectory: Path,
|
fun loadConfig(baseDirectory: Path,
|
||||||
configFileOverride: Path? = null,
|
configFile: Path = baseDirectory / "node.conf",
|
||||||
allowMissingConfig: Boolean = false,
|
allowMissingConfig: Boolean = false,
|
||||||
configOverrides: Map<String, Any?> = emptyMap()): Config {
|
configOverrides: Map<String, Any?> = emptyMap()): Config {
|
||||||
val defaultConfig = ConfigFactory.parseResources("reference.conf", ConfigParseOptions.defaults().setAllowMissing(false))
|
val parseOptions = ConfigParseOptions.defaults()
|
||||||
|
val defaultConfig = ConfigFactory.parseResources("reference.conf", parseOptions.setAllowMissing(false))
|
||||||
val configFile = configFileOverride ?: baseDirectory / "node.conf"
|
val appConfig = ConfigFactory.parseFile(configFile.toFile(), parseOptions.setAllowMissing(allowMissingConfig))
|
||||||
val appConfig = ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(allowMissingConfig))
|
|
||||||
|
|
||||||
val overrideConfig = ConfigFactory.parseMap(configOverrides + mapOf(
|
val overrideConfig = ConfigFactory.parseMap(configOverrides + mapOf(
|
||||||
// Add substitution values here
|
// Add substitution values here
|
||||||
"basedir" to baseDirectory.toString())
|
"basedir" to baseDirectory.toString())
|
||||||
)
|
)
|
||||||
|
|
||||||
val finalConfig = overrideConfig
|
val finalConfig = overrideConfig
|
||||||
.withFallback(appConfig)
|
.withFallback(appConfig)
|
||||||
.withFallback(defaultConfig)
|
.withFallback(defaultConfig)
|
||||||
|
@ -67,7 +67,7 @@ class NodeMessagingClient(override val config: NodeConfiguration,
|
|||||||
// confusion.
|
// confusion.
|
||||||
const val TOPIC_PROPERTY = "platform-topic"
|
const val TOPIC_PROPERTY = "platform-topic"
|
||||||
const val SESSION_ID_PROPERTY = "session-id"
|
const val SESSION_ID_PROPERTY = "session-id"
|
||||||
val AMQ_DELAY = Integer.valueOf(System.getProperty("amq.delivery.delay.ms", "0"))
|
private val AMQ_DELAY: Int = Integer.valueOf(System.getProperty("amq.delivery.delay.ms", "0"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InnerState {
|
private class InnerState {
|
||||||
@ -88,12 +88,16 @@ class NodeMessagingClient(override val config: NodeConfiguration,
|
|||||||
val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration
|
val callback: (ReceivedMessage, MessageHandlerRegistration) -> Unit) : MessageHandlerRegistration
|
||||||
|
|
||||||
/** An executor for sending messages */
|
/** An executor for sending messages */
|
||||||
private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("${config.myLegalName} Messaging", 1)
|
private val messagingExecutor = AffinityExecutor.ServiceAffinityExecutor("Messaging", 1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apart from the NetworkMapService this is the only other address accessible to the node outside of lookups against the NetworkMapCache.
|
* Apart from the NetworkMapService this is the only other address accessible to the node outside of lookups against the NetworkMapCache.
|
||||||
*/
|
*/
|
||||||
override val myAddress: SingleMessageRecipient = if (myIdentity != null) NodeAddress.asPeer(myIdentity, serverHostPort) else NetworkMapAddress(serverHostPort)
|
override val myAddress: SingleMessageRecipient = if (myIdentity != null) {
|
||||||
|
NodeAddress.asPeer(myIdentity, serverHostPort)
|
||||||
|
} else {
|
||||||
|
NetworkMapAddress(serverHostPort)
|
||||||
|
}
|
||||||
|
|
||||||
private val state = ThreadBox(InnerState())
|
private val state = ThreadBox(InnerState())
|
||||||
private val handlers = CopyOnWriteArrayList<Handler>()
|
private val handlers = CopyOnWriteArrayList<Handler>()
|
||||||
@ -108,7 +112,8 @@ class NodeMessagingClient(override val config: NodeConfiguration,
|
|||||||
override fun addElementToInsert(insert: InsertStatement, entry: UUID, finalizables: MutableList<() -> Unit>) {
|
override fun addElementToInsert(insert: InsertStatement, entry: UUID, finalizables: MutableList<() -> Unit>) {
|
||||||
insert[table.uuid] = entry
|
insert[table.uuid] = entry
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
fun start(rpcOps: RPCOps, userService: RPCUserService) {
|
fun start(rpcOps: RPCOps, userService: RPCUserService) {
|
||||||
state.locked {
|
state.locked {
|
||||||
@ -257,7 +262,7 @@ class NodeMessagingClient(override val config: NodeConfiguration,
|
|||||||
// Use the magic deduplication property built into Artemis as our message identity too
|
// Use the magic deduplication property built into Artemis as our message identity too
|
||||||
val uuid = UUID.fromString(message.getStringProperty(HDR_DUPLICATE_DETECTION_ID))
|
val uuid = UUID.fromString(message.getStringProperty(HDR_DUPLICATE_DETECTION_ID))
|
||||||
val user = requireNotNull(message.getStringProperty(HDR_VALIDATED_USER)) { "Message is not authenticated" }
|
val user = requireNotNull(message.getStringProperty(HDR_VALIDATED_USER)) { "Message is not authenticated" }
|
||||||
log.info("Received message from: ${message.address} user: $user topic: $topic sessionID: $sessionID uuid: $uuid")
|
log.trace { "Received message from: ${message.address} user: $user topic: $topic sessionID: $sessionID uuid: $uuid" }
|
||||||
|
|
||||||
val body = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }
|
val body = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }
|
||||||
|
|
||||||
@ -372,11 +377,11 @@ class NodeMessagingClient(override val config: NodeConfiguration,
|
|||||||
|
|
||||||
// For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended
|
// For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended
|
||||||
if (AMQ_DELAY > 0 && message.topicSession.topic == StateMachineManager.sessionTopic.topic) {
|
if (AMQ_DELAY > 0 && message.topicSession.topic == StateMachineManager.sessionTopic.topic) {
|
||||||
putLongProperty(HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + AMQ_DELAY);
|
putLongProperty(HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + AMQ_DELAY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("Send to: $mqAddress topic: ${message.topicSession.topic} sessionID: ${message.topicSession.sessionID} " +
|
log.trace { "Send to: $mqAddress topic: ${message.topicSession.topic} " +
|
||||||
"uuid: ${message.uniqueMessageId}")
|
"sessionID: ${message.topicSession.sessionID} uuid: ${message.uniqueMessageId}" }
|
||||||
producer!!.send(mqAddress, artemisMessage)
|
producer!!.send(mqAddress, artemisMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.corda.node.services.statemachine
|
package net.corda.node.services.statemachine
|
||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.node.services.statemachine.StateMachineManager.FlowSession
|
|
||||||
|
|
||||||
// TODO revisit when Kotlin 1.1 is released and data classes can extend other classes
|
// TODO revisit when Kotlin 1.1 is released and data classes can extend other classes
|
||||||
interface FlowIORequest {
|
interface FlowIORequest {
|
||||||
@ -22,17 +21,20 @@ interface SendRequest : SessionedFlowIORequest {
|
|||||||
|
|
||||||
interface ReceiveRequest<T : SessionMessage> : SessionedFlowIORequest, WaitingRequest {
|
interface ReceiveRequest<T : SessionMessage> : SessionedFlowIORequest, WaitingRequest {
|
||||||
val receiveType: Class<T>
|
val receiveType: Class<T>
|
||||||
|
val userReceiveType: Class<*>?
|
||||||
}
|
}
|
||||||
|
|
||||||
data class SendAndReceive<T : SessionMessage>(override val session: FlowSession,
|
data class SendAndReceive<T : SessionMessage>(override val session: FlowSession,
|
||||||
override val message: SessionMessage,
|
override val message: SessionMessage,
|
||||||
override val receiveType: Class<T>) : SendRequest, ReceiveRequest<T> {
|
override val receiveType: Class<T>,
|
||||||
|
override val userReceiveType: Class<*>?) : SendRequest, ReceiveRequest<T> {
|
||||||
@Transient
|
@Transient
|
||||||
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ReceiveOnly<T : SessionMessage>(override val session: FlowSession,
|
data class ReceiveOnly<T : SessionMessage>(override val session: FlowSession,
|
||||||
override val receiveType: Class<T>) : ReceiveRequest<T> {
|
override val receiveType: Class<T>,
|
||||||
|
override val userReceiveType: Class<*>?) : ReceiveRequest<T> {
|
||||||
@Transient
|
@Transient
|
||||||
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package net.corda.node.services.statemachine
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Party
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.node.services.statemachine.FlowSessionState.Initiated
|
||||||
|
import net.corda.node.services.statemachine.FlowSessionState.Initiating
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
class FlowSession(
|
||||||
|
val flow: FlowLogic<*>,
|
||||||
|
val ourSessionId: Long,
|
||||||
|
val initiatingParty: Party?,
|
||||||
|
var state: FlowSessionState)
|
||||||
|
{
|
||||||
|
val receivedMessages = ConcurrentLinkedQueue<ReceivedSessionMessage<*>>()
|
||||||
|
val fiber: FlowStateMachineImpl<*> get() = flow.stateMachine as FlowStateMachineImpl<*>
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "${javaClass.simpleName}(flow=$flow, ourSessionId=$ourSessionId, initiatingParty=$initiatingParty, state=$state)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [FlowSessionState] describes the session's state.
|
||||||
|
*
|
||||||
|
* [Initiating] is pre-handshake. [Initiating.otherParty] at this point holds a [Party] corresponding to either a
|
||||||
|
* specific peer or a service.
|
||||||
|
* [Initiated] is post-handshake. At this point [Initiating.otherParty] will have been resolved to a specific peer
|
||||||
|
* [Initiated.peerParty], and the peer's sessionId has been initialised.
|
||||||
|
*/
|
||||||
|
sealed class FlowSessionState {
|
||||||
|
abstract val sendToParty: Party
|
||||||
|
|
||||||
|
/** [otherParty] may be a specific peer or a service party */
|
||||||
|
class Initiating(val otherParty: Party) : FlowSessionState() {
|
||||||
|
override val sendToParty: Party get() = otherParty
|
||||||
|
override fun toString(): String = "${javaClass.simpleName}($otherParty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Initiated(val peerParty: Party, val peerSessionId: Long) : FlowSessionState() {
|
||||||
|
override val sendToParty: Party get() = peerParty
|
||||||
|
override fun toString(): String = "${javaClass.simpleName}($peerParty, $peerSessionId)"
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import co.paralleluniverse.fibers.Suspendable
|
|||||||
import co.paralleluniverse.strands.Strand
|
import co.paralleluniverse.strands.Strand
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
import net.corda.core.abbreviate
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
@ -15,10 +16,9 @@ import net.corda.core.flows.StateMachineRunId
|
|||||||
import net.corda.core.random63BitValue
|
import net.corda.core.random63BitValue
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.core.utilities.trace
|
import net.corda.core.utilities.trace
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.statemachine.StateMachineManager.FlowSession
|
|
||||||
import net.corda.node.services.statemachine.StateMachineManager.FlowSessionState
|
|
||||||
import net.corda.node.utilities.StrandLocalTransactionManager
|
import net.corda.node.utilities.StrandLocalTransactionManager
|
||||||
import net.corda.node.utilities.createDatabaseTransaction
|
import net.corda.node.utilities.createDatabaseTransaction
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
@ -28,11 +28,10 @@ import org.slf4j.Logger
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.sql.SQLException
|
import java.sql.SQLException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
|
|
||||||
class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
||||||
val logic: FlowLogic<R>,
|
val logic: FlowLogic<R>,
|
||||||
scheduler: FiberScheduler) : Fiber<Unit>("flow", scheduler), FlowStateMachine<R> {
|
scheduler: FiberScheduler) : Fiber<Unit>(id.toString(), scheduler), FlowStateMachine<R> {
|
||||||
companion object {
|
companion object {
|
||||||
// Used to work around a small limitation in Quasar.
|
// Used to work around a small limitation in Quasar.
|
||||||
private val QUASAR_UNBLOCKER = run {
|
private val QUASAR_UNBLOCKER = run {
|
||||||
@ -56,9 +55,13 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
@Transient private var txTrampoline: Transaction? = null
|
@Transient private var txTrampoline: Transaction? = null
|
||||||
|
|
||||||
@Transient private var _logger: Logger? = null
|
@Transient private var _logger: Logger? = null
|
||||||
|
/**
|
||||||
|
* Return the logger for this state machine. The logger name incorporates [id] and so including this in the log
|
||||||
|
* message is not necessary.
|
||||||
|
*/
|
||||||
override val logger: Logger get() {
|
override val logger: Logger get() {
|
||||||
return _logger ?: run {
|
return _logger ?: run {
|
||||||
val l = LoggerFactory.getLogger(id.toString())
|
val l = LoggerFactory.getLogger("net.corda.flow.$id")
|
||||||
_logger = l
|
_logger = l
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
@ -80,12 +83,12 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
logic.stateMachine = this
|
logic.stateMachine = this
|
||||||
name = id.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun run() {
|
override fun run() {
|
||||||
createTransaction()
|
createTransaction()
|
||||||
|
logger.debug { "Calling flow: $logic" }
|
||||||
val result = try {
|
val result = try {
|
||||||
logic.call()
|
logic.call()
|
||||||
} catch (e: FlowException) {
|
} catch (e: FlowException) {
|
||||||
@ -93,11 +96,13 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
val propagated = e.stackTrace[0].className == javaClass.name
|
val propagated = e.stackTrace[0].className == javaClass.name
|
||||||
actionOnEnd(e, propagated)
|
actionOnEnd(e, propagated)
|
||||||
_resultFuture?.setException(e)
|
_resultFuture?.setException(e)
|
||||||
|
logger.debug(if (propagated) "Flow ended due to receiving exception" else "Flow finished with exception", e)
|
||||||
return
|
return
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
logger.warn("Terminated by unexpected exception", t)
|
||||||
actionOnEnd(t, false)
|
actionOnEnd(t, false)
|
||||||
_resultFuture?.setException(t)
|
_resultFuture?.setException(t)
|
||||||
throw ExecutionException(t)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only sessions which have a single send and nothing else will block here
|
// Only sessions which have a single send and nothing else will block here
|
||||||
@ -107,6 +112,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
// This is to prevent actionOnEnd being called twice if it throws an exception
|
// This is to prevent actionOnEnd being called twice if it throws an exception
|
||||||
actionOnEnd(null, false)
|
actionOnEnd(null, false)
|
||||||
_resultFuture?.set(result)
|
_resultFuture?.set(result)
|
||||||
|
logger.debug { "Flow finished with result $result" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTransaction() {
|
private fun createTransaction() {
|
||||||
@ -134,27 +140,35 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
otherParty: Party,
|
otherParty: Party,
|
||||||
payload: Any,
|
payload: Any,
|
||||||
sessionFlow: FlowLogic<*>): UntrustworthyData<T> {
|
sessionFlow: FlowLogic<*>): UntrustworthyData<T> {
|
||||||
|
logger.debug { "sendAndReceive(${receiveType.name}, $otherParty, ${payload.toString().abbreviate(300)}) ..." }
|
||||||
val session = getConfirmedSession(otherParty, sessionFlow)
|
val session = getConfirmedSession(otherParty, sessionFlow)
|
||||||
return if (session == null) {
|
val sessionData = if (session == null) {
|
||||||
val newSession = startNewSession(otherParty, sessionFlow, payload, waitForConfirmation = true)
|
val newSession = startNewSession(otherParty, sessionFlow, payload, waitForConfirmation = true)
|
||||||
// Only do a receive here as the session init has carried the payload
|
// Only do a receive here as the session init has carried the payload
|
||||||
receiveInternal<SessionData>(newSession, receiveType)
|
receiveInternal<SessionData>(newSession, receiveType)
|
||||||
} else {
|
} else {
|
||||||
sendAndReceiveInternal<SessionData>(session, createSessionData(session, payload), receiveType)
|
val sendData = createSessionData(session, payload)
|
||||||
}.checkPayloadIs(receiveType)
|
sendAndReceiveInternal<SessionData>(session, sendData, receiveType)
|
||||||
|
}
|
||||||
|
logger.debug { "Received ${sessionData.message.payload.toString().abbreviate(300)}" }
|
||||||
|
return sessionData.checkPayloadIs(receiveType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun <T : Any> receive(receiveType: Class<T>,
|
override fun <T : Any> receive(receiveType: Class<T>,
|
||||||
otherParty: Party,
|
otherParty: Party,
|
||||||
sessionFlow: FlowLogic<*>): UntrustworthyData<T> {
|
sessionFlow: FlowLogic<*>): UntrustworthyData<T> {
|
||||||
|
logger.debug { "receive(${receiveType.name}, $otherParty) ..." }
|
||||||
val session = getConfirmedSession(otherParty, sessionFlow) ?:
|
val session = getConfirmedSession(otherParty, sessionFlow) ?:
|
||||||
startNewSession(otherParty, sessionFlow, null, waitForConfirmation = true)
|
startNewSession(otherParty, sessionFlow, null, waitForConfirmation = true)
|
||||||
return receiveInternal<SessionData>(session, receiveType).checkPayloadIs(receiveType)
|
val sessionData = receiveInternal<SessionData>(session, receiveType)
|
||||||
|
logger.debug { "Received ${sessionData.message.payload.toString().abbreviate(300)}" }
|
||||||
|
return sessionData.checkPayloadIs(receiveType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) {
|
override fun send(otherParty: Party, payload: Any, sessionFlow: FlowLogic<*>) {
|
||||||
|
logger.debug { "send($otherParty, ${payload.toString().abbreviate(300)})" }
|
||||||
val session = getConfirmedSession(otherParty, sessionFlow)
|
val session = getConfirmedSession(otherParty, sessionFlow)
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
// Don't send the payload again if it was already piggy-backed on a session init
|
// Don't send the payload again if it was already piggy-backed on a session init
|
||||||
@ -164,6 +178,27 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction {
|
||||||
|
logger.debug { "waitForLedgerCommit($hash) ..." }
|
||||||
|
suspend(WaitForLedgerCommit(hash, sessionFlow.stateMachine as FlowStateMachineImpl<*>))
|
||||||
|
val stx = serviceHub.storageService.validatedTransactions.getTransaction(hash)
|
||||||
|
if (stx != null) {
|
||||||
|
logger.debug { "Transaction $hash committed to ledger" }
|
||||||
|
return stx
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the tx isn't committed then we may have been resumed due to an session ending in an error
|
||||||
|
for (session in openSessions.values) {
|
||||||
|
for (receivedMessage in session.receivedMessages) {
|
||||||
|
if (receivedMessage.message is ErrorSessionEnd) {
|
||||||
|
session.erroredEnd(receivedMessage.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw IllegalStateException("We were resumed after waiting for $hash but it wasn't found in our local storage")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will suspend the state machine and wait for incoming session init response from other party.
|
* This method will suspend the state machine and wait for incoming session init response from other party.
|
||||||
*/
|
*/
|
||||||
@ -178,23 +213,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
|
||||||
override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction {
|
|
||||||
logger.info("Waiting for transaction $hash to commit")
|
|
||||||
suspend(WaitForLedgerCommit(hash, sessionFlow.stateMachine as FlowStateMachineImpl<*>))
|
|
||||||
val stx = serviceHub.storageService.validatedTransactions.getTransaction(hash)
|
|
||||||
if (stx != null) return stx
|
|
||||||
// If the tx isn't committed then we may have been resumed due to an session ending in an error
|
|
||||||
for (session in openSessions.values) {
|
|
||||||
for (receivedMessage in session.receivedMessages) {
|
|
||||||
if (receivedMessage.message is ErrorSessionEnd) {
|
|
||||||
session.erroredEnd(receivedMessage.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw IllegalStateException("We were resumed after waiting for $hash but it wasn't found in our local storage")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSessionData(session: FlowSession, payload: Any): SessionData {
|
private fun createSessionData(session: FlowSession, payload: Any): SessionData {
|
||||||
val sessionState = session.state
|
val sessionState = session.state
|
||||||
val peerSessionId = when (sessionState) {
|
val peerSessionId = when (sessionState) {
|
||||||
@ -212,14 +230,14 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
private inline fun <reified M : ExistingSessionMessage> receiveInternal(
|
private inline fun <reified M : ExistingSessionMessage> receiveInternal(
|
||||||
session: FlowSession,
|
session: FlowSession,
|
||||||
userReceiveType: Class<*>?): ReceivedSessionMessage<M> {
|
userReceiveType: Class<*>?): ReceivedSessionMessage<M> {
|
||||||
return waitForMessage(ReceiveOnly(session, M::class.java), userReceiveType)
|
return waitForMessage(ReceiveOnly(session, M::class.java, userReceiveType))
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified M : ExistingSessionMessage> sendAndReceiveInternal(
|
private inline fun <reified M : ExistingSessionMessage> sendAndReceiveInternal(
|
||||||
session: FlowSession,
|
session: FlowSession,
|
||||||
message: SessionMessage,
|
message: SessionMessage,
|
||||||
userReceiveType: Class<*>?): ReceivedSessionMessage<M> {
|
userReceiveType: Class<*>?): ReceivedSessionMessage<M> {
|
||||||
return waitForMessage(SendAndReceive(session, message, M::class.java), userReceiveType)
|
return waitForMessage(SendAndReceive(session, message, M::class.java, userReceiveType))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -253,11 +271,9 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun <M : ExistingSessionMessage> waitForMessage(
|
|
||||||
receiveRequest: ReceiveRequest<M>,
|
private fun <M : ExistingSessionMessage> waitForMessage(receiveRequest: ReceiveRequest<M>): ReceivedSessionMessage<M> {
|
||||||
userReceiveType: Class<*>?): ReceivedSessionMessage<M> {
|
return receiveRequest.suspendAndExpectReceive().confirmReceiveType(receiveRequest)
|
||||||
val receivedMessage = receiveRequest.suspendAndExpectReceive()
|
|
||||||
return receivedMessage.confirmReceiveType(receiveRequest, userReceiveType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
@ -280,8 +296,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun <M : ExistingSessionMessage> ReceivedSessionMessage<*>.confirmReceiveType(
|
private fun <M : ExistingSessionMessage> ReceivedSessionMessage<*>.confirmReceiveType(
|
||||||
receiveRequest: ReceiveRequest<M>,
|
receiveRequest: ReceiveRequest<M>): ReceivedSessionMessage<M> {
|
||||||
userReceiveType: Class<*>?): ReceivedSessionMessage<M> {
|
|
||||||
val session = receiveRequest.session
|
val session = receiveRequest.session
|
||||||
val receiveType = receiveRequest.receiveType
|
val receiveType = receiveRequest.receiveType
|
||||||
if (receiveType.isInstance(message)) {
|
if (receiveType.isInstance(message)) {
|
||||||
@ -292,7 +307,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
if (message is ErrorSessionEnd) {
|
if (message is ErrorSessionEnd) {
|
||||||
session.erroredEnd(message)
|
session.erroredEnd(message)
|
||||||
} else {
|
} else {
|
||||||
val expectedType = userReceiveType?.name ?: receiveType.simpleName
|
val expectedType = receiveRequest.userReceiveType?.name ?: receiveType.simpleName
|
||||||
throw FlowSessionException("Counterparty flow on ${session.state.sendToParty} has completed without " +
|
throw FlowSessionException("Counterparty flow on ${session.state.sendToParty} has completed without " +
|
||||||
"sending a $expectedType")
|
"sending a $expectedType")
|
||||||
}
|
}
|
||||||
@ -310,7 +325,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
throw FlowSessionException("Counterparty flow on ${state.sendToParty} had an internal error and has terminated")
|
throw FlowSessionException("Counterparty flow on ${state.sendToParty} had an internal error and has terminated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suspendable
|
@Suspendable
|
||||||
private fun suspend(ioRequest: FlowIORequest) {
|
private fun suspend(ioRequest: FlowIORequest) {
|
||||||
// We have to pass the thread local database transaction across via a transient field as the fiber park
|
// We have to pass the thread local database transaction across via a transient field as the fiber park
|
||||||
@ -321,7 +336,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
waitingForResponse = ioRequest
|
waitingForResponse = ioRequest
|
||||||
|
|
||||||
var exceptionDuringSuspend: Throwable? = null
|
var exceptionDuringSuspend: Throwable? = null
|
||||||
parkAndSerialize { fiber, serializer ->
|
parkAndSerialize { f, s ->
|
||||||
logger.trace { "Suspended on $ioRequest" }
|
logger.trace { "Suspended on $ioRequest" }
|
||||||
// restore the Tx onto the ThreadLocal so that we can commit the ensuing checkpoint to the DB
|
// restore the Tx onto the ThreadLocal so that we can commit the ensuing checkpoint to the DB
|
||||||
try {
|
try {
|
||||||
@ -332,6 +347,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
// Quasar does not terminate the fiber properly if an exception occurs during a suspend. We have to
|
// Quasar does not terminate the fiber properly if an exception occurs during a suspend. We have to
|
||||||
// resume the fiber just so that we can throw it when it's running.
|
// resume the fiber just so that we can throw it when it's running.
|
||||||
exceptionDuringSuspend = t
|
exceptionDuringSuspend = t
|
||||||
|
logger.trace("Resuming so fiber can it terminate with the exception thrown during suspend process", t)
|
||||||
resume(scheduler)
|
resume(scheduler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,7 +369,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
|
|||||||
logger.trace("Started")
|
logger.trace("Started")
|
||||||
start()
|
start()
|
||||||
} else {
|
} else {
|
||||||
logger.trace("Resumed")
|
|
||||||
Fiber.unpark(this, QUASAR_UNBLOCKER)
|
Fiber.unpark(this, QUASAR_UNBLOCKER)
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.node.services.statemachine
|
package net.corda.node.services.statemachine
|
||||||
|
|
||||||
import net.corda.core.abbreviate
|
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.flows.FlowException
|
import net.corda.core.flows.FlowException
|
||||||
import net.corda.core.utilities.UntrustworthyData
|
import net.corda.core.utilities.UntrustworthyData
|
||||||
@ -14,9 +13,7 @@ interface ExistingSessionMessage : SessionMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class SessionData(override val recipientSessionId: Long, val payload: Any) : ExistingSessionMessage {
|
data class SessionData(override val recipientSessionId: Long, val payload: Any) : ExistingSessionMessage {
|
||||||
override fun toString(): String {
|
override fun toString(): String = "${javaClass.simpleName}(recipientSessionId=$recipientSessionId, payload=$payload)"
|
||||||
return "${javaClass.simpleName}(recipientSessionId=$recipientSessionId, payload=${payload.toString().abbreviate(100)})"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SessionInitResponse : ExistingSessionMessage {
|
interface SessionInitResponse : ExistingSessionMessage {
|
||||||
|
@ -31,8 +31,6 @@ import net.corda.core.utilities.trace
|
|||||||
import net.corda.node.services.api.Checkpoint
|
import net.corda.node.services.api.Checkpoint
|
||||||
import net.corda.node.services.api.CheckpointStorage
|
import net.corda.node.services.api.CheckpointStorage
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
import net.corda.node.services.api.ServiceHubInternal
|
||||||
import net.corda.node.services.statemachine.StateMachineManager.FlowSessionState.Initiated
|
|
||||||
import net.corda.node.services.statemachine.StateMachineManager.FlowSessionState.Initiating
|
|
||||||
import net.corda.node.utilities.*
|
import net.corda.node.utilities.*
|
||||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
@ -40,8 +38,6 @@ import rx.Observable
|
|||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
import java.util.concurrent.ExecutionException
|
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +75,12 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<StateMachineManager>()
|
private val logger = loggerFor<StateMachineManager>()
|
||||||
internal val sessionTopic = TopicSession("platform.session")
|
internal val sessionTopic = TopicSession("platform.session")
|
||||||
|
init {
|
||||||
|
Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable ->
|
||||||
|
(fiber as FlowStateMachineImpl<*>).logger.error("Caught exception from flow", throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val scheduler = FiberScheduler()
|
val scheduler = FiberScheduler()
|
||||||
@ -148,12 +150,6 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
*/
|
*/
|
||||||
val changes: Observable<Change> = mutex.content.changesPublisher.wrapWithDatabaseTransaction()
|
val changes: Observable<Change> = mutex.content.changesPublisher.wrapWithDatabaseTransaction()
|
||||||
|
|
||||||
init {
|
|
||||||
Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable ->
|
|
||||||
(fiber as FlowStateMachineImpl<*>).logger.error("Caught exception from flow", throwable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
restoreFibersFromCheckpoints()
|
restoreFibersFromCheckpoints()
|
||||||
listenToLedgerTransactions()
|
listenToLedgerTransactions()
|
||||||
@ -168,7 +164,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
if (fibers.isNotEmpty()) {
|
if (fibers.isNotEmpty()) {
|
||||||
executor.executeASAP {
|
executor.executeASAP {
|
||||||
for (fiber in fibers) {
|
for (fiber in fibers) {
|
||||||
fiber.logger.info("Transaction $hash has committed to the ledger, resuming")
|
fiber.logger.trace { "Transaction $hash has committed to the ledger, resuming" }
|
||||||
fiber.waitingForResponse = null
|
fiber.waitingForResponse = null
|
||||||
resumeFiber(fiber)
|
resumeFiber(fiber)
|
||||||
}
|
}
|
||||||
@ -246,7 +242,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
serviceHub.storageService.validatedTransactions.getTransaction(waitingForResponse.hash)
|
serviceHub.storageService.validatedTransactions.getTransaction(waitingForResponse.hash)
|
||||||
}
|
}
|
||||||
if (stx != null) {
|
if (stx != null) {
|
||||||
fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed.")
|
fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed")
|
||||||
fiber.waitingForResponse = null
|
fiber.waitingForResponse = null
|
||||||
resumeFiber(fiber)
|
resumeFiber(fiber)
|
||||||
} else {
|
} else {
|
||||||
@ -288,14 +284,14 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
// before then.
|
// before then.
|
||||||
session.fiber.waitingForResponse = null
|
session.fiber.waitingForResponse = null
|
||||||
updateCheckpoint(session.fiber)
|
updateCheckpoint(session.fiber)
|
||||||
session.fiber.logger.debug { "About to resume due to $message" }
|
session.fiber.logger.trace { "Resuming due to $message" }
|
||||||
resumeFiber(session.fiber)
|
resumeFiber(session.fiber)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val peerParty = recentlyClosedSessions.remove(message.recipientSessionId)
|
val peerParty = recentlyClosedSessions.remove(message.recipientSessionId)
|
||||||
if (peerParty != null) {
|
if (peerParty != null) {
|
||||||
if (message is SessionConfirm) {
|
if (message is SessionConfirm) {
|
||||||
logger.debug { "Received session confirmation but associated fiber has already terminated, so sending session end" }
|
logger.trace { "Received session confirmation but associated fiber has already terminated, so sending session end" }
|
||||||
sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId))
|
sendSessionMessage(peerParty, NormalSessionEnd(message.initiatedSessionId))
|
||||||
} else {
|
} else {
|
||||||
logger.trace { "Ignoring session end message for already closed session: $message" }
|
logger.trace { "Ignoring session end message for already closed session: $message" }
|
||||||
@ -315,7 +311,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onSessionInit(sessionInit: SessionInit, sender: Party) {
|
private fun onSessionInit(sessionInit: SessionInit, sender: Party) {
|
||||||
logger.trace { "Received $sessionInit $sender" }
|
logger.trace { "Received $sessionInit from $sender" }
|
||||||
val otherPartySessionId = sessionInit.initiatorSessionId
|
val otherPartySessionId = sessionInit.initiatorSessionId
|
||||||
|
|
||||||
fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(otherPartySessionId, message))
|
fun sendSessionReject(message: String) = sendSessionMessage(sender, SessionReject(otherPartySessionId, message))
|
||||||
@ -347,14 +343,15 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
updateCheckpoint(fiber)
|
updateCheckpoint(fiber)
|
||||||
session
|
session
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn("Couldn't start session for $sessionInit", e)
|
logger.warn("Couldn't start flow session from $sessionInit", e)
|
||||||
sendSessionReject("Unable to establish session")
|
sendSessionReject("Unable to establish session")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSessionMessage(sender, SessionConfirm(otherPartySessionId, session.ourSessionId), session.fiber)
|
sendSessionMessage(sender, SessionConfirm(otherPartySessionId, session.ourSessionId), session.fiber)
|
||||||
session.fiber.logger.debug { "Initiated from $sessionInit on $session" }
|
session.fiber.logger.debug { "Initiated by $sender using marker ${markerClass.name}" }
|
||||||
startFiber(session.fiber)
|
session.fiber.logger.trace { "Initiated from $sessionInit on $session" }
|
||||||
|
resumeFiber(session.fiber)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes<FlowStateMachineImpl<*>> {
|
private fun serializeFiber(fiber: FlowStateMachineImpl<*>): SerializedBytes<FlowStateMachineImpl<*>> {
|
||||||
@ -426,7 +423,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun FlowSession.endSession(exception: Throwable?, propagated: Boolean) {
|
private fun FlowSession.endSession(exception: Throwable?, propagated: Boolean) {
|
||||||
val initiatedState = state as? Initiated ?: return
|
val initiatedState = state as? FlowSessionState.Initiated ?: return
|
||||||
val sessionEnd = if (exception == null) {
|
val sessionEnd = if (exception == null) {
|
||||||
NormalSessionEnd(initiatedState.peerSessionId)
|
NormalSessionEnd(initiatedState.peerSessionId)
|
||||||
} else {
|
} else {
|
||||||
@ -443,25 +440,6 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
recentlyClosedSessions[ourSessionId] = initiatedState.peerParty
|
recentlyClosedSessions[ourSessionId] = initiatedState.peerParty
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startFiber(fiber: FlowStateMachineImpl<*>) {
|
|
||||||
try {
|
|
||||||
resumeFiber(fiber)
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
// There are two ways we can take exceptions in this method:
|
|
||||||
//
|
|
||||||
// 1) A bug in the SMM code itself whilst setting up the new flow. In that case the exception will
|
|
||||||
// propagate out of this method as it would for any method.
|
|
||||||
//
|
|
||||||
// 2) An exception in the first part of the fiber after it's been invoked for the first time via
|
|
||||||
// fiber.start(). In this case the exception will be caught and stashed in the flow logic future,
|
|
||||||
// then sent to the unhandled exception handler above which logs it, and is then rethrown wrapped
|
|
||||||
// in an ExecutionException or RuntimeException+EE so we can just catch it here and ignore it.
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
if (e.cause !is ExecutionException)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kicks off a brand new state machine of the given class.
|
* Kicks off a brand new state machine of the given class.
|
||||||
* The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is
|
* The state machine will be persisted when it suspends, with automated restart if the StateMachineManager is
|
||||||
@ -484,7 +462,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
// If we are not started then our checkpoint will be picked up during start
|
// If we are not started then our checkpoint will be picked up during start
|
||||||
mutex.locked {
|
mutex.locked {
|
||||||
if (started) {
|
if (started) {
|
||||||
startFiber(fiber)
|
resumeFiber(fiber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fiber
|
return fiber
|
||||||
@ -509,7 +487,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
fiber.resume(scheduler)
|
fiber.resume(scheduler)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fiber.logger.debug("Not resuming as SMM is stopping.")
|
fiber.logger.trace("Not resuming as SMM is stopping.")
|
||||||
decrementLiveFibers()
|
decrementLiveFibers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -551,40 +529,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
|
|||||||
?: throw IllegalArgumentException("Don't know about party $party")
|
?: throw IllegalArgumentException("Don't know about party $party")
|
||||||
val address = serviceHub.networkService.getAddressOfParty(partyInfo)
|
val address = serviceHub.networkService.getAddressOfParty(partyInfo)
|
||||||
val logger = fiber?.logger ?: logger
|
val logger = fiber?.logger ?: logger
|
||||||
logger.debug { "Sending $message to party $party, address: $address" }
|
logger.trace { "Sending $message to party $party @ $address" }
|
||||||
serviceHub.networkService.send(sessionTopic, message, address)
|
serviceHub.networkService.send(sessionTopic, message, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* [FlowSessionState] describes the session's state.
|
|
||||||
*
|
|
||||||
* [Initiating] is pre-handshake. [Initiating.otherParty] at this point holds a [Party] corresponding to either a
|
|
||||||
* specific peer or a service.
|
|
||||||
* [Initiated] is post-handshake. At this point [Initiating.otherParty] will have been resolved to a specific peer
|
|
||||||
* [Initiated.peerParty], and the peer's sessionId has been initialised.
|
|
||||||
*/
|
|
||||||
sealed class FlowSessionState {
|
|
||||||
abstract val sendToParty: Party
|
|
||||||
class Initiating(
|
|
||||||
val otherParty: Party /** This may be a specific peer or a service party */
|
|
||||||
) : FlowSessionState() {
|
|
||||||
override val sendToParty: Party get() = otherParty
|
|
||||||
}
|
|
||||||
class Initiated(
|
|
||||||
val peerParty: Party, /** This must be a peer party */
|
|
||||||
val peerSessionId: Long
|
|
||||||
) : FlowSessionState() {
|
|
||||||
override val sendToParty: Party get() = peerParty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class FlowSession(
|
|
||||||
val flow: FlowLogic<*>,
|
|
||||||
val ourSessionId: Long,
|
|
||||||
val initiatingParty: Party?,
|
|
||||||
var state: FlowSessionState)
|
|
||||||
{
|
|
||||||
val receivedMessages = ConcurrentLinkedQueue<ReceivedSessionMessage<*>>()
|
|
||||||
val fiber: FlowStateMachineImpl<*> get() = flow.stateMachine as FlowStateMachineImpl<*>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.div
|
|||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.slf4j.event.Level
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
class ArgsParserTest {
|
class ArgsParserTest {
|
||||||
@ -18,65 +19,59 @@ class ArgsParserTest {
|
|||||||
configFile = workingDirectory / "node.conf",
|
configFile = workingDirectory / "node.conf",
|
||||||
help = false,
|
help = false,
|
||||||
logToConsole = false,
|
logToConsole = false,
|
||||||
|
loggingLevel = Level.INFO,
|
||||||
isWebserver = false))
|
isWebserver = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `just base-directory with relative path`() {
|
fun `base-directory with relative path`() {
|
||||||
val expectedBaseDir = Paths.get("tmp").normalize().toAbsolutePath()
|
val expectedBaseDir = Paths.get("tmp").normalize().toAbsolutePath()
|
||||||
val cmdLineOptions = parser.parse("--base-directory", "tmp")
|
val cmdLineOptions = parser.parse("--base-directory", "tmp")
|
||||||
assertThat(cmdLineOptions).isEqualTo(CmdLineOptions(
|
assertThat(cmdLineOptions.baseDirectory).isEqualTo(expectedBaseDir)
|
||||||
baseDirectory = expectedBaseDir,
|
assertThat(cmdLineOptions.configFile).isEqualTo(expectedBaseDir / "node.conf")
|
||||||
configFile = expectedBaseDir / "node.conf",
|
|
||||||
help = false,
|
|
||||||
logToConsole = false,
|
|
||||||
isWebserver = false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `just base-directory with absolute path`() {
|
fun `base-directory with absolute path`() {
|
||||||
val baseDirectory = Paths.get("tmp").normalize().toAbsolutePath()
|
val baseDirectory = Paths.get("tmp").normalize().toAbsolutePath()
|
||||||
val cmdLineOptions = parser.parse("--base-directory", baseDirectory.toString())
|
val cmdLineOptions = parser.parse("--base-directory", baseDirectory.toString())
|
||||||
assertThat(cmdLineOptions).isEqualTo(CmdLineOptions(
|
assertThat(cmdLineOptions.baseDirectory).isEqualTo(baseDirectory)
|
||||||
baseDirectory = baseDirectory,
|
assertThat(cmdLineOptions.configFile).isEqualTo(baseDirectory / "node.conf")
|
||||||
configFile = baseDirectory / "node.conf",
|
|
||||||
help = false,
|
|
||||||
logToConsole = false,
|
|
||||||
isWebserver = false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `just config-file with relative path`() {
|
fun `config-file with relative path`() {
|
||||||
val cmdLineOptions = parser.parse("--config-file", "different.conf")
|
val cmdLineOptions = parser.parse("--config-file", "different.conf")
|
||||||
assertThat(cmdLineOptions).isEqualTo(CmdLineOptions(
|
assertThat(cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
|
||||||
baseDirectory = workingDirectory,
|
assertThat(cmdLineOptions.configFile).isEqualTo(workingDirectory / "different.conf")
|
||||||
configFile = workingDirectory / "different.conf",
|
|
||||||
help = false,
|
|
||||||
logToConsole = false,
|
|
||||||
isWebserver = false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `just config-file with absolute path`() {
|
fun `config-file with absolute path`() {
|
||||||
val configFile = Paths.get("tmp", "a.conf").normalize().toAbsolutePath()
|
val configFile = Paths.get("tmp", "a.conf").normalize().toAbsolutePath()
|
||||||
val cmdLineOptions = parser.parse("--config-file", configFile.toString())
|
val cmdLineOptions = parser.parse("--config-file", configFile.toString())
|
||||||
assertThat(cmdLineOptions).isEqualTo(CmdLineOptions(
|
assertThat(cmdLineOptions.baseDirectory).isEqualTo(workingDirectory)
|
||||||
baseDirectory = workingDirectory,
|
assertThat(cmdLineOptions.configFile).isEqualTo(configFile)
|
||||||
configFile = configFile,
|
|
||||||
help = false,
|
|
||||||
logToConsole = false,
|
|
||||||
isWebserver = false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `just webserver `() {
|
fun `log-to-console`() {
|
||||||
|
val cmdLineOptions = parser.parse("--log-to-console")
|
||||||
|
assertThat(cmdLineOptions.logToConsole).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `logging-level`() {
|
||||||
|
for (level in Level.values()) {
|
||||||
|
val cmdLineOptions = parser.parse("--logging-level", level.name)
|
||||||
|
assertThat(cmdLineOptions.loggingLevel).isEqualTo(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `webserver`() {
|
||||||
val cmdLineOptions = parser.parse("--webserver")
|
val cmdLineOptions = parser.parse("--webserver")
|
||||||
assertThat(cmdLineOptions).isEqualTo(CmdLineOptions(
|
assertThat(cmdLineOptions.isWebserver).isTrue()
|
||||||
baseDirectory = workingDirectory,
|
|
||||||
configFile = workingDirectory / "node.conf",
|
|
||||||
help = false,
|
|
||||||
logToConsole = false,
|
|
||||||
isWebserver = true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -99,4 +94,18 @@ class ArgsParserTest {
|
|||||||
parser.parse("--config-file")
|
parser.parse("--config-file")
|
||||||
}.withMessageContaining("config-file")
|
}.withMessageContaining("config-file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `logging-level without argument`() {
|
||||||
|
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
|
||||||
|
parser.parse("--logging-level")
|
||||||
|
}.withMessageContaining("logging-level")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `logging-level with invalid argument`() {
|
||||||
|
assertThatExceptionOfType(OptionException::class.java).isThrownBy {
|
||||||
|
parser.parse("--logging-level", "not-a-level")
|
||||||
|
}.withMessageContaining("logging-level")
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ package net.corda.node.services.statemachine
|
|||||||
|
|
||||||
import co.paralleluniverse.fibers.Fiber
|
import co.paralleluniverse.fibers.Fiber
|
||||||
import co.paralleluniverse.fibers.Suspendable
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import co.paralleluniverse.strands.Strand.UncaughtExceptionHandler
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import net.corda.core.contracts.DOLLARS
|
import net.corda.core.contracts.DOLLARS
|
||||||
import net.corda.core.contracts.DummyState
|
import net.corda.core.contracts.DummyState
|
||||||
@ -17,11 +16,11 @@ import net.corda.core.messaging.MessageRecipients
|
|||||||
import net.corda.core.node.services.PartyInfo
|
import net.corda.core.node.services.PartyInfo
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
import net.corda.core.random63BitValue
|
import net.corda.core.random63BitValue
|
||||||
import net.corda.core.rootCause
|
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
|
import net.corda.core.utilities.LogHelper
|
||||||
import net.corda.core.utilities.unwrap
|
import net.corda.core.utilities.unwrap
|
||||||
import net.corda.flows.CashIssueFlow
|
import net.corda.flows.CashIssueFlow
|
||||||
import net.corda.flows.CashPaymentFlow
|
import net.corda.flows.CashPaymentFlow
|
||||||
@ -53,6 +52,12 @@ import kotlin.test.assertFailsWith
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class StateMachineManagerTests {
|
class StateMachineManagerTests {
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
LogHelper.setLevel("+net.corda.flow")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
|
private val net = MockNetwork(servicePeerAllocationStrategy = RoundRobin())
|
||||||
private val sessionTransfers = ArrayList<SessionTransfer>()
|
private val sessionTransfers = ArrayList<SessionTransfer>()
|
||||||
private lateinit var node1: MockNode
|
private lateinit var node1: MockNode
|
||||||
@ -111,17 +116,11 @@ class StateMachineManagerTests {
|
|||||||
fiber.actionOnSuspend = {
|
fiber.actionOnSuspend = {
|
||||||
throw exceptionDuringSuspend
|
throw exceptionDuringSuspend
|
||||||
}
|
}
|
||||||
var uncaughtException: Throwable? = null
|
|
||||||
fiber.uncaughtExceptionHandler = UncaughtExceptionHandler { f, e ->
|
|
||||||
uncaughtException = e
|
|
||||||
}
|
|
||||||
net.runNetwork()
|
net.runNetwork()
|
||||||
assertThatThrownBy {
|
assertThatThrownBy {
|
||||||
fiber.resultFuture.getOrThrow()
|
fiber.resultFuture.getOrThrow()
|
||||||
}.isSameAs(exceptionDuringSuspend)
|
}.isSameAs(exceptionDuringSuspend)
|
||||||
assertThat(node1.smm.allStateMachines).isEmpty()
|
assertThat(node1.smm.allStateMachines).isEmpty()
|
||||||
// Make sure it doesn't get swallowed up
|
|
||||||
assertThat(uncaughtException?.rootCause).isSameAs(exceptionDuringSuspend)
|
|
||||||
// Make sure the fiber does actually terminate
|
// Make sure the fiber does actually terminate
|
||||||
assertThat(fiber.isTerminated).isTrue()
|
assertThat(fiber.isTerminated).isTrue()
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<Console name="Console-Appender" target="SYSTEM_OUT">
|
<Console name="Console-Appender" target="SYSTEM_OUT">
|
||||||
<PatternLayout>
|
<PatternLayout>
|
||||||
<pattern>
|
<pattern>
|
||||||
[%-5level] %d{HH:mm:ss.SSS} [%t] %c{1}.%M - %msg%n
|
[%-5level] %d{HH:mm:ss.SSS} [%t] %c{2}.%M - %msg%n
|
||||||
</pattern>>
|
</pattern>>
|
||||||
</PatternLayout>
|
</PatternLayout>
|
||||||
</Console>
|
</Console>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
fileName="${log-path}/${log-name}.log"
|
fileName="${log-path}/${log-name}.log"
|
||||||
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
|
filePattern="${archive}/${log-name}.%d{yyyy-MM-dd}-%i.log.gz">
|
||||||
|
|
||||||
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{1} - %msg%n"/>
|
<PatternLayout pattern="[%-5level] %d{ISO8601}{GMT+0} [%t] %c{2} - %msg%n"/>
|
||||||
|
|
||||||
<Policies>
|
<Policies>
|
||||||
<TimeBasedTriggeringPolicy/>
|
<TimeBasedTriggeringPolicy/>
|
||||||
|
@ -98,26 +98,24 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
* Because this executor is shared, we need to be careful about nodes shutting it down.
|
* Because this executor is shared, we need to be careful about nodes shutting it down.
|
||||||
*/
|
*/
|
||||||
private val sharedUserCount = AtomicInteger(0)
|
private val sharedUserCount = AtomicInteger(0)
|
||||||
private val sharedServerThread =
|
private val sharedServerThread = object : ServiceAffinityExecutor("Mock network", 1) {
|
||||||
object : ServiceAffinityExecutor("Mock network shared node thread", 1) {
|
override fun shutdown() {
|
||||||
|
// We don't actually allow the shutdown of the network-wide shared thread pool until all references to
|
||||||
override fun shutdown() {
|
// it have been shutdown.
|
||||||
// We don't actually allow the shutdown of the network-wide shared thread pool until all references to
|
if (sharedUserCount.decrementAndGet() == 0) {
|
||||||
// it have been shutdown.
|
super.shutdown()
|
||||||
if (sharedUserCount.decrementAndGet() == 0) {
|
|
||||||
super.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
|
||||||
if (!isShutdown) {
|
|
||||||
flush()
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return super.awaitTermination(timeout, unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
|
||||||
|
if (!isShutdown) {
|
||||||
|
flush()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return super.awaitTermination(timeout, unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param overrideServices a set of service entries to use in place of the node's default service entries,
|
* @param overrideServices a set of service entries to use in place of the node's default service entries,
|
||||||
|
Loading…
Reference in New Issue
Block a user