mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
Fix coin selection with Flow-friendly sleep (#1847)
This commit is contained in:
@ -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")
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
Reference in New Issue
Block a user