Merge branch 'release/os/4.4' of github.com:corda/corda into new_checkpoint_schema

This commit is contained in:
stefano 2020-02-10 11:25:29 +00:00
commit 546166e057
24 changed files with 129 additions and 2519 deletions

View File

@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline { pipeline {
agent { label 'k8s' } agent { label 'local-k8s' }
options { timestamps() } options { timestamps() }
environment { environment {

View File

@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline { pipeline {
agent { label 'k8s' } agent { label 'local-k8s' }
options { options {
timestamps() timestamps()
overrideIndexTriggers(false) overrideIndexTriggers(false)

View File

@ -3,4 +3,4 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
onDemandTestPipeline('k8s', '.ci/dev/on-demand-tests/commentMappings.yml') onDemandTestPipeline('local-k8s', '.ci/dev/on-demand-tests/commentMappings.yml')

View File

@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline { pipeline {
agent { label 'gke' } agent { label 'local-k8s' }
options { options {
timestamps() timestamps()
buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7')) buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7'))

View File

@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline { pipeline {
agent { label 'k8s' } agent { label 'local-k8s' }
options { timestamps() options { timestamps()
overrideIndexTriggers(false) } overrideIndexTriggers(false) }

View File

@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger()) killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline { pipeline {
agent { label 'k8s' } agent { label 'local-k8s' }
options { timestamps() } options { timestamps() }
environment { environment {

View File

@ -193,7 +193,7 @@ plugins {
id 'com.github.johnrengelman.shadow' version '2.0.4' apply false id 'com.github.johnrengelman.shadow' version '2.0.4' apply false
id "com.gradle.build-scan" version "2.2.1" id "com.gradle.build-scan" version "2.2.1"
id "org.ajoberstar.grgit" version "4.0.0" id "org.ajoberstar.grgit" version "4.0.0"
} }
apply plugin: 'project-report' apply plugin: 'project-report'
apply plugin: 'com.github.ben-manes.versions' apply plugin: 'com.github.ben-manes.versions'
@ -660,9 +660,9 @@ task allParallelUnitAndIntegrationTest(type: ParallelTestGroup) {
} }
task parallelRegressionTest(type: ParallelTestGroup) { task parallelRegressionTest(type: ParallelTestGroup) {
testGroups "test", "integrationTest", "slowIntegrationTest", "smokeTest" testGroups "test", "integrationTest", "slowIntegrationTest", "smokeTest"
numberOfShards 6 numberOfShards 15
streamOutput false streamOutput false
coresPerFork 6 coresPerFork 2
memoryInGbPerFork 10 memoryInGbPerFork 10
distribute DistributeTestsBy.METHOD distribute DistributeTestsBy.METHOD
nodeTaints "big" nodeTaints "big"

View File

@ -7,7 +7,6 @@ import net.corda.client.rpc.GracefulReconnect
import net.corda.client.rpc.MaxRpcRetryException import net.corda.client.rpc.MaxRpcRetryException
import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCException
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps import net.corda.client.rpc.internal.ReconnectingCordaRPCOps
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.startTrackedFlow import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
@ -26,7 +25,6 @@ import net.corda.testing.node.internal.FINANCE_CORDAPPS
import net.corda.testing.node.internal.rpcDriver import net.corda.testing.node.internal.rpcDriver
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.ClassRule
import org.junit.Test import org.junit.Test
import java.lang.Thread.sleep import java.lang.Thread.sleep
import java.time.Duration import java.time.Duration
@ -41,6 +39,10 @@ class CordaRPCClientReconnectionTest {
private val portAllocator = incrementalPortAllocation() private val portAllocator = incrementalPortAllocation()
private val gracefulReconnect = GracefulReconnect() private val gracefulReconnect = GracefulReconnect()
private val config = CordaRPCClientConfiguration.DEFAULT.copy(
connectionRetryInterval = Duration.ofSeconds(1),
connectionRetryIntervalMultiplier = 1.0
)
companion object { companion object {
val rpcUser = User("user1", "test", permissions = setOf(Permissions.all())) val rpcUser = User("user1", "test", permissions = setOf(Permissions.all()))
@ -61,7 +63,7 @@ class CordaRPCClientReconnectionTest {
} }
val node = startNode() val node = startNode()
val client = CordaRPCClient(node.rpcAddress) val client = CordaRPCClient(node.rpcAddress, config)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -99,7 +101,7 @@ class CordaRPCClientReconnectionTest {
} }
val node = startNode() val node = startNode()
val client = CordaRPCClient(node.rpcAddress) val client = CordaRPCClient(node.rpcAddress, config)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -138,7 +140,7 @@ class CordaRPCClientReconnectionTest {
val addresses = listOf(NetworkHostAndPort("localhost", portAllocator.nextPort()), NetworkHostAndPort("localhost", portAllocator.nextPort())) val addresses = listOf(NetworkHostAndPort("localhost", portAllocator.nextPort()), NetworkHostAndPort("localhost", portAllocator.nextPort()))
val node = startNode(addresses[0]) val node = startNode(addresses[0])
val client = CordaRPCClient(addresses) val client = CordaRPCClient(addresses, config)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = gracefulReconnect)).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -175,7 +177,7 @@ class CordaRPCClientReconnectionTest {
} }
val node = startNode() val node = startNode()
val client = CordaRPCClient(node.rpcAddress) val client = CordaRPCClient(node.rpcAddress, config)
(client.start(rpcUser.username, rpcUser.password, gracefulReconnect = GracefulReconnect(maxAttempts = 1))).use { (client.start(rpcUser.username, rpcUser.password, gracefulReconnect = GracefulReconnect(maxAttempts = 1))).use {
val rpcOps = it.proxy as ReconnectingCordaRPCOps val rpcOps = it.proxy as ReconnectingCordaRPCOps
@ -189,11 +191,11 @@ class CordaRPCClientReconnectionTest {
} }
} }
@Test @Test(timeout = 60_000)
fun `establishing an RPC connection fails if there is no node listening to the specified address`() { fun `establishing an RPC connection fails if there is no node listening to the specified address`() {
rpcDriver { rpcDriver {
assertThatThrownBy { assertThatThrownBy {
CordaRPCClient(NetworkHostAndPort("localhost", portAllocator.nextPort())) CordaRPCClient(NetworkHostAndPort("localhost", portAllocator.nextPort()), config)
.start(rpcUser.username, rpcUser.password, GracefulReconnect()) .start(rpcUser.username, rpcUser.password, GracefulReconnect())
}.isInstanceOf(RPCException::class.java) }.isInstanceOf(RPCException::class.java)
.hasMessage("Cannot connect to server(s). Tried with all available servers.") .hasMessage("Cannot connect to server(s). Tried with all available servers.")
@ -213,7 +215,7 @@ class CordaRPCClientReconnectionTest {
} }
val node = startNode() val node = startNode()
CordaRPCClient(node.rpcAddress).start(rpcUser.username, rpcUser.password, gracefulReconnect).use { CordaRPCClient(node.rpcAddress, config).start(rpcUser.username, rpcUser.password, gracefulReconnect).use {
node.stop() node.stop()
thread() { thread() {
it.proxy.startTrackedFlow( it.proxy.startTrackedFlow(
@ -230,4 +232,23 @@ class CordaRPCClientReconnectionTest {
} }
} }
@Test
fun `RPC connection stops reconnecting after config number of retries`() {
driver(DriverParameters(cordappsForAllNodes = emptyList())) {
val address = NetworkHostAndPort("localhost", portAllocator.nextPort())
val conf = config.copy(maxReconnectAttempts = 2)
fun startNode(): NodeHandle = startNode(
providedName = CHARLIE_NAME,
rpcUsers = listOf(CordaRPCClientTest.rpcUser),
customOverrides = mapOf("rpcSettings.address" to address.toString())
).getOrThrow()
val node = startNode()
val connection = CordaRPCClient(node.rpcAddress, conf).start(rpcUser.username, rpcUser.password, gracefulReconnect)
node.stop()
// After two tries we throw RPCException
assertThatThrownBy { connection.proxy.isWaitingForShutdown() }
.isInstanceOf(RPCException::class.java)
}
}
} }

View File

@ -285,8 +285,8 @@ open class CordaRPCClientConfiguration @JvmOverloads constructor(
* *
* @param onDisconnect implement this callback to perform logic when the RPC disconnects on connection disconnect * @param onDisconnect implement this callback to perform logic when the RPC disconnects on connection disconnect
* @param onReconnect implement this callback to perform logic when the RPC has reconnected after connection disconnect * @param onReconnect implement this callback to perform logic when the RPC has reconnected after connection disconnect
* @param maxAttempts the maximum number of attempts per each individual RPC call. A negative number indicates infinite number of retries. * @param maxAttempts the maximum number of attempts per each individual RPC call. A negative number indicates infinite
* The default value is 5. * number of retries. The default value is 5.
*/ */
class GracefulReconnect(val onDisconnect: () -> Unit = {}, val onReconnect: () -> Unit = {}, val maxAttempts: Int = 5) { class GracefulReconnect(val onDisconnect: () -> Unit = {}, val onReconnect: () -> Unit = {}, val maxAttempts: Int = 5) {
@Suppress("unused") // constructor for java @Suppress("unused") // constructor for java

View File

@ -1,6 +1,7 @@
package net.corda.client.rpc package net.corda.client.rpc
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import java.lang.reflect.Method
/** /**
* Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked method. * Thrown to indicate a fatal error in the RPC system itself, as opposed to an error generated by the invoked method.
@ -19,8 +20,8 @@ open class UnrecoverableRPCException(message: String?, cause: Throwable? = null)
* @param maxNumberOfRetries the number of retries that had been performed. * @param maxNumberOfRetries the number of retries that had been performed.
* @param cause the cause of the last failed attempt. * @param cause the cause of the last failed attempt.
*/ */
class MaxRpcRetryException(maxNumberOfRetries: Int, cause: Throwable?): class MaxRpcRetryException(maxNumberOfRetries: Int, method: Method, cause: Throwable?):
RPCException("Max number of retries ($maxNumberOfRetries) was reached.", cause) RPCException("Max number of retries ($maxNumberOfRetries) for this RPC operation (${method.name}) was reached.", cause)
/** /**
* Signals that the underlying [RPCConnection] dropped. * Signals that the underlying [RPCConnection] dropped.

View File

@ -16,7 +16,6 @@ import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConn
import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConnection.CurrentState.UNCONNECTED import net.corda.client.rpc.internal.ReconnectingCordaRPCOps.ReconnectingRPCConnection.CurrentState.UNCONNECTED
import net.corda.client.rpc.reconnect.CouldNotStartFlowException import net.corda.client.rpc.reconnect.CouldNotStartFlowException
import net.corda.core.flows.StateMachineRunId import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.div
import net.corda.core.internal.messaging.InternalCordaRPCOps import net.corda.core.internal.messaging.InternalCordaRPCOps
import net.corda.core.internal.times import net.corda.core.internal.times
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
@ -154,7 +153,7 @@ class ReconnectingCordaRPCOps private constructor(
@Synchronized get() = when (currentState) { @Synchronized get() = when (currentState) {
// The first attempt to establish a connection will try every address only once. // The first attempt to establish a connection will try every address only once.
UNCONNECTED -> UNCONNECTED ->
connect(infiniteRetries = false) ?: throw IllegalArgumentException("The ReconnectingRPCConnection has been closed.") connect(nodeHostAndPorts.size) ?: throw IllegalArgumentException("The ReconnectingRPCConnection has been closed.")
CONNECTED -> CONNECTED ->
currentRPCConnection!! currentRPCConnection!!
CLOSED -> CLOSED ->
@ -180,7 +179,7 @@ class ReconnectingCordaRPCOps private constructor(
//TODO - handle error cases //TODO - handle error cases
log.warn("Reconnecting to ${this.nodeHostAndPorts} due to error: ${e.message}") log.warn("Reconnecting to ${this.nodeHostAndPorts} due to error: ${e.message}")
log.debug("", e) log.debug("", e)
connect(infiniteRetries = true) connect(rpcConfiguration.maxReconnectAttempts)
previousConnection?.forceClose() previousConnection?.forceClose()
gracefulReconnect.onReconnect.invoke() gracefulReconnect.onReconnect.invoke()
} }
@ -192,14 +191,13 @@ class ReconnectingCordaRPCOps private constructor(
val previousConnection = currentRPCConnection val previousConnection = currentRPCConnection
doReconnect(e, previousConnection) doReconnect(e, previousConnection)
} }
private fun connect(infiniteRetries: Boolean): CordaRPCConnection? { private fun connect(maxConnectAttempts: Int): CordaRPCConnection? {
currentState = CONNECTING currentState = CONNECTING
synchronized(this) { synchronized(this) {
currentRPCConnection = if (infiniteRetries) { currentRPCConnection = establishConnectionWithRetry(
establishConnectionWithRetry(rpcConfiguration.connectionRetryInterval) rpcConfiguration.connectionRetryInterval,
} else { retries = maxConnectAttempts
establishConnectionWithRetry(rpcConfiguration.connectionRetryInterval, retries = nodeHostAndPorts.size) )
}
// It's possible we could get closed while waiting for the connection to establish. // It's possible we could get closed while waiting for the connection to establish.
if (!isClosed()) { if (!isClosed()) {
currentState = CONNECTED currentState = CONNECTED
@ -257,17 +255,17 @@ class ReconnectingCordaRPCOps private constructor(
log.warn("Unknown exception [${ex.javaClass.name}] upon establishing connection.", ex) log.warn("Unknown exception [${ex.javaClass.name}] upon establishing connection.", ex)
} }
} }
if (retries == 0) {
throw RPCException("Cannot connect to server(s). Tried with all available servers.", ex)
} }
val remainingRetries = if (retries < 0) retries else (retries - 1)
if (remainingRetries == 0) {
throw RPCException("Cannot connect to server(s). Tried with all available servers.")
} }
// Could not connect this time round - pause before giving another try. // Could not connect this time round - pause before giving another try.
Thread.sleep(retryInterval.toMillis()) Thread.sleep(retryInterval.toMillis())
// TODO - make the exponential retry factor configurable. // TODO - make the exponential retry factor configurable.
val nextRoundRobinIndex = (roundRobinIndex + 1) % nodeHostAndPorts.size val nextRoundRobinIndex = (roundRobinIndex + 1) % nodeHostAndPorts.size
val remainingRetries = if (retries < 0) retries else (retries - 1) val nextInterval = retryInterval * rpcConfiguration.connectionRetryIntervalMultiplier
return establishConnectionWithRetry((retryInterval * 10) / 9, nextRoundRobinIndex, remainingRetries) return establishConnectionWithRetry(nextInterval, nextRoundRobinIndex, remainingRetries)
} }
override val proxy: CordaRPCOps override val proxy: CordaRPCOps
get() = current.proxy get() = current.proxy
@ -346,7 +344,7 @@ class ReconnectingCordaRPCOps private constructor(
} }
} }
throw MaxRpcRetryException(maxNumberOfAttempts, lastException) throw MaxRpcRetryException(maxNumberOfAttempts, method, lastException)
} }
private fun checkIfClosed() { private fun checkIfClosed() {

View File

@ -17,7 +17,7 @@ guavaVersion=28.0-jre
quasarVersion=0.7.12_r3 quasarVersion=0.7.12_r3
quasarClassifier=jdk8 quasarClassifier=jdk8
# Quasar version to use with Java 11: # Quasar version to use with Java 11:
quasarVersion11=0.8.0_r3_rc1 quasarVersion11=0.8.0_r3
jdkClassifier11=jdk11 jdkClassifier11=jdk11
proguardVersion=6.1.1 proguardVersion=6.1.1
bouncycastleVersion=1.60 bouncycastleVersion=1.60
@ -30,7 +30,7 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0 caffeineVersion=2.7.0
metricsVersion=4.1.0 metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1 metricsNewRelicVersion=1.1.1
djvmVersion=1.0-RC08 djvmVersion=1.0-RC09
deterministicRtVersion=1.0-RC02 deterministicRtVersion=1.0-RC02
openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4 openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4 openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4

View File

@ -15,6 +15,7 @@ import net.corda.core.utilities.seconds
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
import rx.Observer import rx.Observer
import rx.observers.Subscribers
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import rx.subjects.UnicastSubject import rx.subjects.UnicastSubject
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@ -55,6 +56,7 @@ import java.util.zip.Deflater
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import kotlin.collections.LinkedHashSet import kotlin.collections.LinkedHashSet
import kotlin.math.roundToLong
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance import kotlin.reflect.full.createInstance
@ -75,6 +77,7 @@ infix fun Temporal.until(endExclusive: Temporal): Duration = Duration.between(th
operator fun Duration.div(divider: Long): Duration = dividedBy(divider) operator fun Duration.div(divider: Long): Duration = dividedBy(divider)
operator fun Duration.times(multiplicand: Long): Duration = multipliedBy(multiplicand) operator fun Duration.times(multiplicand: Long): Duration = multipliedBy(multiplicand)
operator fun Duration.times(multiplicand: Double): Duration = Duration.ofNanos((toNanos() * multiplicand).roundToLong())
/** /**
* Returns the single element matching the given [predicate], or `null` if the collection is empty, or throws exception * Returns the single element matching the given [predicate], or `null` if the collection is empty, or throws exception
@ -170,8 +173,8 @@ fun <T> Observable<T>.bufferUntilSubscribed(): Observable<T> {
@DeleteForDJVM @DeleteForDJVM
fun <T> Observer<T>.tee(vararg teeTo: Observer<T>): Observer<T> { fun <T> Observer<T>.tee(vararg teeTo: Observer<T>): Observer<T> {
val subject = PublishSubject.create<T>() val subject = PublishSubject.create<T>()
subject.subscribe(this) subject.unsafeSubscribe(Subscribers.from(this))
teeTo.forEach { subject.subscribe(it) } teeTo.forEach { subject.unsafeSubscribe(Subscribers.from(it)) }
return subject return subject
} }

File diff suppressed because one or more lines are too long

View File

@ -34,10 +34,13 @@ has a stable API.
Non-public API (experimental) Non-public API (experimental)
----------------------------- -----------------------------
The following modules are not part of the Corda's public API and no backwards compatibility guarantees are provided. They are further categorized in 2 classes: The following are not part of the Corda's public API and no backwards compatibility guarantees are provided:
* the incubating modules, for which we will do our best to minimise disruption to developers using them until we are able to graduate them into the public API * Incubating modules, for which we will do our best to minimise disruption to developers using them until we are able to graduate them into the public API
* the internal modules, which are not to be used, and will change without notice * Internal modules, which are not to be used, and will change without notice
* Anything defined in a package containing ``.internal`` (for example, ``net.corda.core.internal`` and sub-packages should
not be used)
* Any interfaces, classes or methods whose name contains the word ``internal`` or ``Internal``
The **finance module** was the first CorDapp ever written and is a legacy module. Although it is not a part of our API guarantees, we also The **finance module** was the first CorDapp ever written and is a legacy module. Although it is not a part of our API guarantees, we also
don't anticipate much future change to it. Users should use the tokens SDK instead. don't anticipate much future change to it. Users should use the tokens SDK instead.
@ -53,8 +56,7 @@ Corda incubating modules
Corda internal modules Corda internal modules
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
Everything else is internal and will change without notice, even deleted, and should not be used. This also includes any package that has Every other module is internal and will change without notice, even deleted, and should not be used.
``.internal`` in it. So for example, ``net.corda.core.internal`` and sub-packages should not be used.
Some of the public modules may depend on internal modules, so be careful to not rely on these transitive dependencies. In particular, the Some of the public modules may depend on internal modules, so be careful to not rely on these transitive dependencies. In particular, the
testing modules depend on the node module and so you may end having the node in your test classpath. testing modules depend on the node module and so you may end having the node in your test classpath.

View File

@ -362,6 +362,7 @@ A more graceful form of reconnection is also available. This will:
- reconnect any existing ``Observable``\s after a reconnection, so that they keep emitting events to the existing subscriptions. - reconnect any existing ``Observable``\s after a reconnection, so that they keep emitting events to the existing subscriptions.
- block any RPC calls that arrive during a reconnection or any RPC calls that were not acknowledged at the point of reconnection and will execute them after the connection is re-established. - block any RPC calls that arrive during a reconnection or any RPC calls that were not acknowledged at the point of reconnection and will execute them after the connection is re-established.
- by default continue retrying indefinitely until the connection is re-established. See ``CordaRPCClientConfiguration.maxReconnectAttempts`` for adjusting the number of retries.
More specifically, the behaviour in the second case is a bit more subtle: More specifically, the behaviour in the second case is a bit more subtle:
@ -377,7 +378,7 @@ You can enable this graceful form of reconnection by using the ``gracefulReconne
* ``onDisconnect``: A callback handler that will be invoked every time the connection is disconnected. * ``onDisconnect``: A callback handler that will be invoked every time the connection is disconnected.
* ``onReconnect``: A callback handler that will be invoked every time the connection is established again after a disconnection. * ``onReconnect``: A callback handler that will be invoked every time the connection is established again after a disconnection.
* ``maxAttempts``: The maximum number of attempts that will be performed per RPC operation. A negative value implies infinite retries. The default value is 5. * ``maxAttempts``: The maximum number of attempts that will be performed per *RPC operation*. A negative value implies infinite retries. The default value is 5.
This can be used in the following way: This can be used in the following way:

View File

@ -36,6 +36,10 @@ This prevents configuration errors when mixing keys containing ``.`` wrapped wit
By default the node will fail to start in presence of unknown property keys. By default the node will fail to start in presence of unknown property keys.
To alter this behaviour, the ``on-unknown-config-keys`` command-line argument can be set to ``IGNORE`` (default is ``FAIL``). To alter this behaviour, the ``on-unknown-config-keys`` command-line argument can be set to ``IGNORE`` (default is ``FAIL``).
.. note:: As noted in the HOCON documentation, the default behaviour for resources referenced within a config file is to silently
ignore them if missing. Therefore it is strongly recommended to utilise the ``required`` syntax for includes. See HOCON documentation
for more information.
Overriding configuration values Overriding configuration values
------------------------------- -------------------------------

View File

@ -135,16 +135,9 @@ dependencies {
// Manifests: for reading stuff from the manifest file // Manifests: for reading stuff from the manifest file
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
compile("com.intellij:forms_rt:7.0.3") {
exclude group: "asm"
}
// Coda Hale's Metrics: for monitoring of key statistics // Coda Hale's Metrics: for monitoring of key statistics
compile "io.dropwizard.metrics:metrics-jmx:$metrics_version" compile "io.dropwizard.metrics:metrics-jmx:$metrics_version"
// JimFS: in memory java.nio filesystem. Used for test and simulation utilities.
compile "com.google.jimfs:jimfs:1.1"
// TypeSafe Config: for simple and human friendly config files. // TypeSafe Config: for simple and human friendly config files.
compile "com.typesafe:config:$typesafe_config_version" compile "com.typesafe:config:$typesafe_config_version"

View File

@ -134,7 +134,10 @@ class RpcReconnectTests {
Unit Unit
} }
val reconnect = GracefulReconnect(onDisconnect = { numDisconnects++ }, onReconnect = onReconnect) val reconnect = GracefulReconnect(onDisconnect = { numDisconnects++ }, onReconnect = onReconnect)
val config = CordaRPCClientConfiguration.DEFAULT.copy(connectionRetryInterval = 1.seconds) val config = CordaRPCClientConfiguration.DEFAULT.copy(
connectionRetryInterval = 1.seconds,
connectionRetryIntervalMultiplier = 1.0
)
val client = CordaRPCClient(addressesForRpc, configuration = config) val client = CordaRPCClient(addressesForRpc, configuration = config)
val bankAReconnectingRPCConnection = client.start(demoUser.username, demoUser.password, gracefulReconnect = reconnect) val bankAReconnectingRPCConnection = client.start(demoUser.username, demoUser.password, gracefulReconnect = reconnect)
val bankAReconnectingRpc = bankAReconnectingRPCConnection.proxy as ReconnectingCordaRPCOps val bankAReconnectingRpc = bankAReconnectingRPCConnection.proxy as ReconnectingCordaRPCOps

View File

@ -2,6 +2,7 @@ package net.corda.node.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.* import net.corda.core.flows.*
@ -42,6 +43,8 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
class FlowRetryTest { class FlowRetryTest {
val config = CordaRPCClientConfiguration.DEFAULT.copy(connectionRetryIntervalMultiplier = 1.1)
@Before @Before
fun resetCounters() { fun resetCounters() {
InitiatorFlow.seen.clear() InitiatorFlow.seen.clear()
@ -69,7 +72,7 @@ class FlowRetryTest {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { val result = CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
it.proxy.startFlow(::InitiatorFlow, numSessions, numIterations, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow() it.proxy.startFlow(::InitiatorFlow, numSessions, numIterations, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow()
} }
result result
@ -87,7 +90,7 @@ class FlowRetryTest {
)) { )) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
it.proxy.startFlow(::AsyncRetryFlow).returnValue.getOrThrow() it.proxy.startFlow(::AsyncRetryFlow).returnValue.getOrThrow()
} }
} }
@ -103,7 +106,7 @@ class FlowRetryTest {
)) { )) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { val result = CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
it.proxy.startFlow(::RetryFlow).returnValue.getOrThrow() it.proxy.startFlow(::RetryFlow).returnValue.getOrThrow()
} }
result result
@ -121,7 +124,7 @@ class FlowRetryTest {
)) { )) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { val result = CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
it.proxy.startFlow(::ThrowingFlow).returnValue.getOrThrow() it.proxy.startFlow(::ThrowingFlow).returnValue.getOrThrow()
} }
result result
@ -136,7 +139,7 @@ class FlowRetryTest {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
assertFailsWith<TimeoutException> { assertFailsWith<TimeoutException> {
it.proxy.startFlow(::TransientConnectionFailureFlow, nodeBHandle.nodeInfo.singleIdentity()) it.proxy.startFlow(::TransientConnectionFailureFlow, nodeBHandle.nodeInfo.singleIdentity())
.returnValue.getOrThrow(Duration.of(10, ChronoUnit.SECONDS)) .returnValue.getOrThrow(Duration.of(10, ChronoUnit.SECONDS))
@ -155,7 +158,7 @@ class FlowRetryTest {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
assertFailsWith<TimeoutException> { assertFailsWith<TimeoutException> {
it.proxy.startFlow(::WrappedTransientConnectionFailureFlow, nodeBHandle.nodeInfo.singleIdentity()) it.proxy.startFlow(::WrappedTransientConnectionFailureFlow, nodeBHandle.nodeInfo.singleIdentity())
.returnValue.getOrThrow(Duration.of(10, ChronoUnit.SECONDS)) .returnValue.getOrThrow(Duration.of(10, ChronoUnit.SECONDS))
@ -175,7 +178,7 @@ class FlowRetryTest {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeBHandle = startNode(providedName = BOB_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
assertFailsWith<CordaRuntimeException> { assertFailsWith<CordaRuntimeException> {
it.proxy.startFlow(::GeneralExternalFailureFlow, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow() it.proxy.startFlow(::GeneralExternalFailureFlow, nodeBHandle.nodeInfo.singleIdentity()).returnValue.getOrThrow()
} }
@ -193,7 +196,7 @@ class FlowRetryTest {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow() val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use { CordaRPCClient(nodeAHandle.rpcAddress, config).start(user.username, user.password).use {
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy { assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
it.proxy.startFlow(::AsyncRetryFlow).returnValue.getOrThrow() it.proxy.startFlow(::AsyncRetryFlow).returnValue.getOrThrow()
}.withMessageStartingWith("User not authorized to perform RPC call") }.withMessageStartingWith("User not authorized to perform RPC call")

View File

@ -74,15 +74,16 @@ open class SharedNodeCmdLineOptions {
} }
errors.forEach { error -> errors.forEach { error ->
when (error) { when (error) {
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile)) is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile, error.cause))
else -> logger.error(error.message) else -> logger.error(error.message)
} }
} }
} }
private fun configFileNotFoundMessage(configFile: Path): String { private fun configFileNotFoundMessage(configFile: Path, cause: Throwable?): String {
return """ return """
Unable to load the node config file from '$configFile'. Unable to load the node config file from '$configFile'.
${cause?.message?.let { "Cause: $it" } ?: ""}
Try setting the --base-directory flag to change which directory the node Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly. is looking in, or use the --config-file flag to specify it explicitly.

View File

@ -40,7 +40,7 @@ class NetworkParametersReader(private val trustRoot: X509Certificate,
val advertisedParametersHash = try { val advertisedParametersHash = try {
networkMapClient?.getNetworkMap()?.payload?.networkParameterHash networkMapClient?.getNetworkMap()?.payload?.networkParameterHash
} catch (e: Exception) { } catch (e: Exception) {
logger.info("Unable to download network map", e) logger.warn("Unable to download network map. Node will attempt to start using network-parameters file: $e")
// If NetworkMap is down while restarting the node, we should be still able to continue with parameters from file // If NetworkMap is down while restarting the node, we should be still able to continue with parameters from file
null null
} }

View File

@ -10,9 +10,13 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.After import org.junit.After
import org.junit.Test import org.junit.Test
import rx.Observable import rx.Observable
import rx.observers.Subscribers
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.io.Closeable import java.io.Closeable
import java.lang.RuntimeException
import java.util.* import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class ObservablesTests { class ObservablesTests {
private fun isInDatabaseTransaction() = contextTransactionOrNull != null private fun isInDatabaseTransaction() = contextTransactionOrNull != null
@ -186,6 +190,30 @@ class ObservablesTests {
assertThat(source3.hasCompleted()).isTrue() assertThat(source3.hasCompleted()).isTrue()
} }
/**
* tee combines [PublishSubject]s under one PublishSubject. We need to make sure that they are not wrapped with a [SafeSubscriber].
* Otherwise, if a non Rx exception gets thrown from a subscriber under one of the PublishSubject it will get caught by the
* SafeSubscriber wrapping that PublishSubject and will call [PublishSubject.PublishSubjectState.onError], which will
* eventually shut down all of the subscribers under that PublishSubjectState.
*/
@Test
fun `error in unsafe subscriber won't shutdown subscribers under same publish subject, after tee`() {
val source1 = PublishSubject.create<Int>()
val source2 = PublishSubject.create<Int>()
var count = 0
source1.subscribe { count += it } // safe subscriber
source1.unsafeSubscribe(Subscribers.create { throw RuntimeException() }) // this subscriber should not shut down the above subscriber
assertFailsWith<RuntimeException> {
source1.tee(source2).onNext(1)
}
assertFailsWith<RuntimeException> {
source1.tee(source2).onNext(1)
}
assertEquals(2, count)
}
@Test @Test
fun `combine tee and bufferUntilDatabaseCommit`() { fun `combine tee and bufferUntilDatabaseCommit`() {
val database = createDatabase() val database = createDatabase()

View File

@ -28,6 +28,9 @@ dependencies {
compile "com.squareup.okhttp3:okhttp:$okhttp_version" compile "com.squareup.okhttp3:okhttp:$okhttp_version"
compile project(':confidential-identities') compile project(':confidential-identities')
// JimFS: in memory java.nio filesystem. Used for test and simulation utilities.
compile "com.google.jimfs:jimfs:1.1"
testCompile "org.apache.commons:commons-lang3:3.9" testCompile "org.apache.commons:commons-lang3:3.9"
} }