Fix coin selection with Flow-friendly sleep (#1847)

This commit is contained in:
Rick Parker
2017-10-11 14:33:20 +01:00
committed by GitHub
parent d0d0f132df
commit 3fdc69e541
10 changed files with 191 additions and 95 deletions

View File

@ -2,6 +2,7 @@ package net.corda.node.services.statemachine
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.crypto.SecureHash
import java.time.Instant
interface FlowIORequest {
// This is used to identify where we suspended, in case of message mismatch errors and other things where we
@ -112,4 +113,9 @@ data class WaitForLedgerCommit(val hash: SecureHash, val fiber: FlowStateMachine
override fun shouldResume(message: ExistingSessionMessage, session: FlowSessionInternal): Boolean = message is ErrorSessionEnd
}
data class Sleep(val until: Instant, val fiber: FlowStateMachineImpl<*>) : FlowIORequest {
@Transient
override val stackTraceInCaseOfProblems: StackSnapshot = StackSnapshot()
}
class StackSnapshot : Throwable("This is a stack trace to help identify the source of the underlying problem")

View File

@ -12,13 +12,9 @@ import net.corda.core.crypto.random63BitValue
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.abbreviate
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.staticField
import net.corda.core.internal.uncheckedCast
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.*
import net.corda.node.services.api.FlowAppAuditEvent
@ -32,13 +28,15 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.file.Paths
import java.sql.SQLException
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.concurrent.TimeUnit
class FlowPermissionException(message: String) : FlowException(message)
class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
val logic: FlowLogic<R>,
override val logic: FlowLogic<R>,
scheduler: FiberScheduler,
override val flowInitiator: FlowInitiator,
// Store the Party rather than the full cert path with PartyAndCertificate
@ -52,23 +50,6 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
* Return the current [FlowStateMachineImpl] or null if executing outside of one.
*/
fun currentStateMachine(): FlowStateMachineImpl<*>? = Strand.currentStrand() as? FlowStateMachineImpl<*>
/**
* Provide a mechanism to sleep within a Strand without locking any transactional state
*/
// TODO: inlined due to an intermittent Quasar error (to be fully investigated)
@Suppress("NOTHING_TO_INLINE")
@Suspendable
inline fun sleep(millis: Long) {
if (currentStateMachine() != null) {
val db = DatabaseTransactionManager.dataSource
DatabaseTransactionManager.current().commit()
DatabaseTransactionManager.current().close()
Strand.sleep(millis)
DatabaseTransactionManager.dataSource = db
DatabaseTransactionManager.newTransaction()
} else Strand.sleep(millis)
}
}
// These fields shouldn't be serialised, so they are marked @Transient.
@ -259,6 +240,13 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
throw IllegalStateException("We were resumed after waiting for $hash but it wasn't found in our local storage")
}
// Provide a mechanism to sleep within a Strand without locking any transactional state.
// This checkpoints, since we cannot undo any database writes up to this point.
@Suspendable
override fun sleepUntil(until: Instant) {
suspend(Sleep(until, this))
}
// TODO Dummy implementation of access to application specific permission controls and audit logging
override fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>) {
val permissionGranted = true // TODO define permission control service on ServiceHubInternal and actually check authorization.
@ -494,6 +482,10 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
}
}
if (exceptionDuringSuspend == null && ioRequest is Sleep) {
// Sleep on the fiber. This will not sleep if it's in the past.
Strand.sleep(Duration.between(Instant.now(), ioRequest.until).toNanos(), TimeUnit.NANOSECONDS)
}
createTransaction()
// TODO Now that we're throwing outside of the suspend the FlowLogic can catch it. We need Quasar to terminate
// the fiber when exceptions occur inside a suspend.

View File

@ -584,6 +584,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
when (ioRequest) {
is SendRequest -> processSendRequest(ioRequest)
is WaitForLedgerCommit -> processWaitForCommitRequest(ioRequest)
is Sleep -> processSleepRequest(ioRequest)
}
}
@ -621,6 +622,11 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
}
}
private fun processSleepRequest(ioRequest: Sleep) {
// Resume the fiber now we have checkpointed, so we can sleep on the Fiber.
resumeFiber(ioRequest.fiber)
}
private fun sendSessionMessage(party: Party, message: SessionMessage, fiber: FlowStateMachineImpl<*>? = null, retryId: Long? = null) {
val partyInfo = serviceHub.networkMapCache.getPartyInfo(party)
?: throw IllegalArgumentException("Don't know about party $party")

View File

@ -52,7 +52,7 @@ class InteractiveShellTest {
private fun check(input: String, expected: String) {
var output: DummyFSM? = null
InteractiveShell.runFlowFromString({ DummyFSM(it as FlowA).apply { output = this } }, input, FlowA::class.java, om)
assertEquals(expected, output!!.logic.a, input)
assertEquals(expected, output!!.flowA.a, input)
}
@Test
@ -83,5 +83,5 @@ class InteractiveShellTest {
@Test
fun party() = check("party: \"${MEGA_CORP.name}\"", MEGA_CORP.name.toString())
class DummyFSM(val logic: FlowA) : FlowStateMachine<Any?> by mock()
class DummyFSM(val flowA: FlowA) : FlowStateMachine<Any?> by mock()
}