【问题标题】:Memory leak inside CorDapp flow testsCorDapp 流测试中的内存泄漏
【发布时间】:2020-04-18 03:26:32
【问题描述】:

在我的 CorDapp 中,我编写了一个自定义的 generateSpend 函数(附​​加在一个 sn-p 中)。它使用tokenSelection.attemptSpendgenerateExitaddTokensToRedeem 结合使用来自不同发行者的代币来支付一笔款项。

示例:[成本为 3 Wood],使用 [Austin 发行的 Nick 持有的 2 Wood]、[Nick 发行的 Nick 持有的 1 Wood] 支付]

我的实现有什么明显的错误吗?它导致了一个非常奇怪的内存泄漏错误: Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

测试在单独运行时通过,但在一次运行时失败。请注意,这里还有一百万个可能出错的地方——我使用的是最新的 Corda 4.4。

我尝试过的事情:

  • 调整 gradle.properties 参数以添加以下 JVM Args(无影响)
    • Xmx2g
    • XX:MaxPermSize=512m
    • XX:+HeapDumpOnOutOfMemoryError
    • XX:HeapDumpPath=/tmp/heapdump -XX:+UseG1GC

代码和错误:

/**
 * When a player spends resources in-game, those resources are consumed as inputs to a transaction. The generateInGameSpend
 * method leverages the token-SDK to facilitate the building of transaction proposing the consumption of tokens when they are
 * spent (burned) and not transferred to a counter-party.
 *
 * This method uses the generateExit functionality from the tokenSelection and mutates an input transaction builder in place.
 */
@Suspendable
fun generateInGameSpend(
        serviceHub: ServiceHub,
        tb: TransactionBuilder,
        costs: Map<TokenType, Long>,
        holder: Party,
        changeOwner: Party,
        additionalQueryCriteria: QueryCriteria? = null,
        messageToLog: String = "",
        logger: Logger? = null
): TransactionBuilder {
    // Create a tokenSelector
    val tokenSelection = TokenSelection(serviceHub)
    // Generate exits for tokens of the appropriate type
    costs.filter { it.value > 0 }.forEach { (type, amount) ->
        val baseCriteria = heldTokenAmountCriteria(type, holder)
        val queryCriteria = additionalQueryCriteria?.let { baseCriteria.and(it) } ?: baseCriteria
        // Get a list of tokens satisfying the costs
        val tokensToSpend = tokenSelection
                .attemptSpend(amount of type, tb.lockId, queryCriteria)
        // Run checks on the tokens to ensure the proposed transaction is valid
        val notaryToCheck = tokensToSpend.first().state.notary
        check(tokensToSpend.all { it.state.notary == notaryToCheck }) { "You are trying to spend assets with different notaries." }
        check(tokensToSpend.isNotEmpty()) { "Received empty list of states to spend." }
        // Begin to spend tokens to satisfy costs
        var spentAmount = Amount(0, type)
        tokensToSpend
                .groupBy { it.state.data.issuer }
                .forEach {
                    val amountOfTokens = it.value.sumTokenStateAndRefs().withoutIssuer()
                    spentAmount = spentAmount.plus(amountOfTokens)
                    val (exitStates, change) = tokenSelection.generateExit(
                    it.value,
                    if (spentAmount.quantity > costs[type]!!) Amount(amount, type) else amountOfTokens,
                    changeOwner)
                    addTokensToRedeem(tb, exitStates, change)
                }
    }
    // Return the mutated transaction builder
    return tb
}
?[m io.github.classgraph.ClassGraphException: Uncaught exception during scan
    at io.github.classgraph.ClassGraphException.newClassGraphException(ClassGraphException.java:89) ~[classgraph-4.8.41.jar:4.8.41]
    at io.github.classgraph.ClassGraph.scan(ClassGraph.java:1183) ~[classgraph-4.8.41.jar:4.8.41]
    at io.github.classgraph.ClassGraph.scan(ClassGraph.java:1201) ~[classgraph-4.8.41.jar:4.8.41]
    at io.github.classgraph.ClassGraph.scan(ClassGraph.java:1214) ~[classgraph-4.8.41.jar:4.8.41]
    at net.corda.core.internal.ClassGraphUtilsKt.pooledScan(ClassGraphUtils.kt:18) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.internal.ClassLoadingUtilsKt.createInstancesOfClassesImplementing(ClassLoadingUtils.kt:22) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder$withAttachmentsClassloaderContext$serializationContext$1.apply(AttachmentsClassLoader.kt:325) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder$withAttachmentsClassloaderContext$serializationContext$1.apply(AttachmentsClassLoader.kt:297) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at java.util.HashMap.computeIfAbsent(HashMap.java:1127) ~[?:1.8.0_211]
    at java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2672) ~[?:1.8.0_211]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(AttachmentsClassLoader.kt:322) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext$default(AttachmentsClassLoader.kt:318) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.transactions.LedgerTransaction.internalPrepareVerify$core(LedgerTransaction.kt:217) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.transactions.LedgerTransaction.verify(LedgerTransaction.kt:207) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.transactions.TransactionBuilder.verify(TransactionBuilder.kt:559) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at com.flows.SetupGameBoardFlow.call(SetupGameBoardFlow.kt:131) ~[workflows-0.1.jar:?]
    at com.flows.SetupGameBoardFlow.call(SetupGameBoardFlow.kt:33) ~[workflows-0.1.jar:?]
    at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:270) ~[corda-node-4.4-SNAPSHOT.jar:?]
    at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:46) ~[corda-node-4.4-SNAPSHOT.jar:?]
    at co.paralleluniverse.fibers.Fiber.run1(Fiber.java:1092) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at co.paralleluniverse.fibers.Fiber.exec(Fiber.java:788) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at co.paralleluniverse.fibers.RunnableFiberTask.doExec(RunnableFiberTask.java:100) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at co.paralleluniverse.fibers.RunnableFiberTask.run(RunnableFiberTask.java:91) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:1.8.0_211]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:1.8.0_211]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[?:1.8.0_211]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[?:1.8.0_211]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[?:1.8.0_211]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[?:1.8.0_211]
    at net.corda.node.utilities.AffinityExecutor$ServiceAffinityExecutor$1$thread$1.run(AffinityExecutor.kt:63) ~[corda-node-4.4-SNAPSHOT.jar:?]
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
?[37m[INFO] 16:44:20,066 [Mock network] statemachine.StaffedFlowHospital. - Flow error allowed to propagate {actor_id=Only For Testing, actor_owning_identity=O=Mock Company 1, L=London, C=GB, actor_store_id=TEST, fiber-id=10000775, flow-id=e2b0e443-f602-46ff-ad4d-93f666cca877, invocation_id=602cdfcf-3c0b-4537-a8a8-c8101c1dfe22, invocation_timestamp=2019-11-13T21:43:08.318Z, origin=Only For Testing, session_id=602cdfcf-3c0b-4537-a8a8-c8101c1dfe22, session_timestamp=2019-11-13T21:43:08.318Z, thread-id=7658}
?[m io.github.classgraph.ClassGraphException: Uncaught exception during scan
    at io.github.classgraph.ClassGraphException.newClassGraphException(ClassGraphException.java:89) ~[classgraph-4.8.41.jar:4.8.41]
    at io.github.classgraph.ClassGraph.scan(ClassGraph.java:1183) ~[classgraph-4.8.41.jar:4.8.41]
    at io.github.classgraph.ClassGraph.scan(ClassGraph.java:1201) ~[classgraph-4.8.41.jar:4.8.41]
    at io.github.classgraph.ClassGraph.scan(ClassGraph.java:1214) ~[classgraph-4.8.41.jar:4.8.41]
    at net.corda.core.internal.ClassGraphUtilsKt.pooledScan(ClassGraphUtils.kt:18) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.internal.ClassLoadingUtilsKt.createInstancesOfClassesImplementing(ClassLoadingUtils.kt:22) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder$withAttachmentsClassloaderContext$serializationContext$1.apply(AttachmentsClassLoader.kt:325) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder$withAttachmentsClassloaderContext$serializationContext$1.apply(AttachmentsClassLoader.kt:297) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at java.util.HashMap.computeIfAbsent(HashMap.java:1127) ~[?:1.8.0_211]
    at java.util.Collections$SynchronizedMap.computeIfAbsent(Collections.java:2672) ~[?:1.8.0_211]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext(AttachmentsClassLoader.kt:322) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder.withAttachmentsClassloaderContext$default(AttachmentsClassLoader.kt:318) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.transactions.LedgerTransaction.internalPrepareVerify$core(LedgerTransaction.kt:217) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.transactions.LedgerTransaction.verify(LedgerTransaction.kt:207) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at net.corda.core.transactions.TransactionBuilder.verify(TransactionBuilder.kt:559) ~[corda-core-4.4-SNAPSHOT.jar:?]
    at com.flows.SetupGameBoardFlow.call(SetupGameBoardFlow.kt:131) ~[workflows-0.1.jar:?]
    at com.flows.SetupGameBoardFlow.call(SetupGameBoardFlow.kt:33) ~[workflows-0.1.jar:?]
    at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:270) ~[corda-node-4.4-SNAPSHOT.jar:?]
    at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:46) ~[corda-node-4.4-SNAPSHOT.jar:?]
    at co.paralleluniverse.fibers.Fiber.run1(Fiber.java:1092) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at co.paralleluniverse.fibers.Fiber.exec(Fiber.java:788) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at co.paralleluniverse.fibers.RunnableFiberTask.doExec(RunnableFiberTask.java:100) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at co.paralleluniverse.fibers.RunnableFiberTask.run(RunnableFiberTask.java:91) ~[quasar-core-0.7.12_r3-jdk8.jar:0.7.12_r3]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:1.8.0_211]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:1.8.0_211]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[?:1.8.0_211]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[?:1.8.0_211]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[?:1.8.0_211]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[?:1.8.0_211]
    at net.corda.node.utilities.AffinityExecutor$ServiceAffinityExecutor$1$thread$1.run(AffinityExecutor.kt:63) ~[corda-node-4.4-SNAPSHOT.jar:?]
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

测试套件 1:

class RobberFlowTests {
private val network = MockNetwork(MockNetworkParameters(
        notarySpecs = listOf(MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))),
        networkParameters = testNetworkParameters(minimumPlatformVersion = 4),
        cordappsForAllNodes = listOf(
                TestCordapp.findCordapp("com.flows"),
                TestCordapp.findCordapp("com.oracleClientFlows"),
                TestCordapp.findCordapp("com.contractsAndStates"),
                TestCordapp.findCordapp("com.r3.corda.lib.tokens.workflows"),
                TestCordapp.findCordapp("com.r3.corda.lib.tokens.contracts"),
                TestCordapp.findCordapp("com.r3.corda.lib.tokens.money")
        )
)
)
private val a = network.createNode(MockNodeParameters())
private val b = network.createNode(MockNodeParameters())
private val c = network.createNode(MockNodeParameters())
private val d = network.createNode(MockNodeParameters())
private val oracleName = CordaX500Name("Oracle", "New York", "US")
private val oracle = network.createNode(
        MockNodeParameters(legalName = oracleName).withAdditionalCordapps(
                listOf(
                        TestCordapp.findCordapp("com.oracleService")
                )
        )
)

@Before
fun setup() {
    network.runNetwork()
}

@After
fun tearDown() = network.stopNodes()

@Test
fun player1IsUnableToMoveTheRobberWhenA7IsNotRolled() {

    // Get an identity for each of the players of the game.
    val p1 = a.info.chooseIdentity()
    val p2 = b.info.chooseIdentity()
    val p3 = c.info.chooseIdentity()
    val p4 = d.info.chooseIdentity()

    // Issue a game state onto the ledger
    val gameStateIssueFlow = (SetupGameBoardFlow(p1, p2, p3, p4))
    val futureWithGameState = a.startFlow(gameStateIssueFlow)
    network.runNetwork()

    val stxGameState = futureWithGameState.getOrThrow()

    // Get a reference to the issued game state
    val gameState = stxGameState.coreTransaction.outputsOfType<GameBoardState>().single()

    val arrayOfAllTransactions = arrayListOf<SignedTransaction>()
    val arrayOfAllPlayerNodes = arrayListOf(a, b, c, d)
    val arrayOfAllPlayerNodesInOrder = gameState.players.map { player -> arrayOfAllPlayerNodes.filter { it.info.chooseIdentity() == player }.first() }

    setupGameBoardForTesting(gameState, network, arrayOfAllPlayerNodesInOrder, arrayOfAllTransactions)
    val gameBoardState = arrayOfAllTransactions.last().coreTransaction.outRefsOfType<GameBoardState>().first().state.data

    val deterministicDiceRoll = getDiceRollWithSpecifiedRollValue(3, 2, gameBoardState, oracle)
    val rollDiceFlow = RollDiceFlow(gameBoardState.linearId, deterministicDiceRoll)
    val futureWithDiceRoll = arrayOfAllPlayerNodesInOrder[0].startFlow(rollDiceFlow)
    network.runNetwork()
    futureWithDiceRoll.getOrThrow()

    val futureWithMovedRobber = arrayOfAllPlayerNodesInOrder[0].startFlow(MoveRobberFlow(gameBoardState.linearId, 5))
    network.runNetwork()

    assertFailsWith<TransactionVerificationException.ContractRejection>("The associated dice roll must have a value of 7.") { futureWithMovedRobber.getOrThrow() }
}

@Test
fun player1IsAbleToMoveTheRobberWhenA7IsRolled() {

    // Get an identity for each of the players of the game.
    val p1 = a.info.chooseIdentity()
    val p2 = b.info.chooseIdentity()
    val p3 = c.info.chooseIdentity()
    val p4 = d.info.chooseIdentity()

    // Issue a game state onto the ledger
    val gameStateIssueFlow = (SetupGameBoardFlow(p1, p2, p3, p4))
    val futureWithGameState = a.startFlow(gameStateIssueFlow)
    network.runNetwork()

    val stxGameState = futureWithGameState.getOrThrow()

    // Get a reference to the issued game state
    val gameState = stxGameState.coreTransaction.outputsOfType<GameBoardState>().single()

    val arrayOfAllTransactions = arrayListOf<SignedTransaction>()
    val arrayOfAllPlayerNodes = arrayListOf(a, b, c, d)
    val arrayOfAllPlayerNodesInOrder = gameState.players.map { player -> arrayOfAllPlayerNodes.filter { it.info.chooseIdentity() == player }.first() }

    setupGameBoardForTesting(gameState, network, arrayOfAllPlayerNodesInOrder, arrayOfAllTransactions)

    val gameBoardState = arrayOfAllTransactions.last().coreTransaction.outRefsOfType<GameBoardState>().first().state.data

    val deterministicDiceRoll = getDiceRollWithSpecifiedRollValue(3, 4, gameBoardState, oracle)
    val rollDiceFlow = RollDiceFlow(gameBoardState.linearId, deterministicDiceRoll)
    val futureWithDiceRoll = arrayOfAllPlayerNodesInOrder[0].startFlow(rollDiceFlow)
    network.runNetwork()
    futureWithDiceRoll.getOrThrow()

    val futureWithClaimedResources = arrayOfAllPlayerNodesInOrder[0].startFlow(MoveRobberFlow(gameBoardState.linearId, 5))
    network.runNetwork()
    val futureWithMovedRobber = futureWithClaimedResources.getOrThrow()

    requireThat {
        val outputRobber = futureWithMovedRobber.coreTransaction.outputsOfType<RobberState>().first()
        "Assert that the robber has been moved to the appropriate position" using (outputRobber.hexTileIndex == HexTileIndex(5))
    }
}

@Test
fun player1IsAbleToApplyTheRobberAfterMovingIt() {

    // Get an identity for each of the players of the game.
    val p1 = a.info.chooseIdentity()
    val p2 = b.info.chooseIdentity()
    val p3 = c.info.chooseIdentity()
    val p4 = d.info.chooseIdentity()

    // Issue a game state onto the ledger
    val gameStateIssueFlow = (SetupGameBoardFlow(p1, p2, p3, p4))
    val futureWithGameState = a.startFlow(gameStateIssueFlow)
    network.runNetwork()

    val stxGameState = futureWithGameState.getOrThrow()

    // Get a reference to the issued game state
    val gameState = stxGameState.coreTransaction.outputsOfType<GameBoardState>().single()

    val arrayOfAllTransactions = arrayListOf<SignedTransaction>()
    val arrayOfAllPlayerNodes = arrayListOf(a, b, c, d)
    val arrayOfAllPlayerNodesInOrder = gameState.players.map { player -> arrayOfAllPlayerNodes.filter { it.info.chooseIdentity() == player }.first() }

    setupGameBoardForTesting(gameState, network, arrayOfAllPlayerNodesInOrder, arrayOfAllTransactions)

    val gameBoardState = arrayOfAllTransactions.last().coreTransaction.outRefsOfType<GameBoardState>().first().state.data

    val deterministicDiceRoll = getDiceRollWithSpecifiedRollValue(3, 4, gameBoardState, oracle)
    val rollDiceFlow = RollDiceFlow(gameBoardState.linearId, deterministicDiceRoll)
    val futureWithDiceRoll = arrayOfAllPlayerNodesInOrder[0].startFlow(rollDiceFlow)
    network.runNetwork()
    futureWithDiceRoll.getOrThrow()

    val futureWithMovedRobber = arrayOfAllPlayerNodesInOrder[0].startFlow(MoveRobberFlow(gameBoardState.linearId, 5))
    network.runNetwork()
    futureWithMovedRobber.getOrThrow()

    val futureWithRobberApplied = arrayOfAllPlayerNodesInOrder[0].startFlow(ApplyRobberFlow(gameBoardState.linearId))
    network.runNetwork()
    val txWithAppliedRobber = futureWithRobberApplied.getOrThrow().coreTransaction

    val inputRobber = txWithAppliedRobber.outputsOfType<RobberState>().single()
    val outputRobber = txWithAppliedRobber.outputsOfType<RobberState>().single()

    requireThat {
        "The robber that was deactivated is the robber that was moved" using (outputRobber.linearId == inputRobber.linearId)
        "The robber has no changed position" using (outputRobber.hexTileIndex == inputRobber.hexTileIndex)
        "The output Robber has been deactivated" using (!outputRobber.active)
    }

}

@Test
fun aPlayerIsAbleToRemoveAPlayBlockerState() {

    // Get an identity for each of the players of the game.
    val p1 = a.info.chooseIdentity()
    val p2 = b.info.chooseIdentity()
    val p3 = c.info.chooseIdentity()
    val p4 = d.info.chooseIdentity()

    // Issue a game state onto the ledger
    val gameStateIssueFlow = (SetupGameBoardFlow(p1, p2, p3, p4))
    val futureWithGameState = a.startFlow(gameStateIssueFlow)
    network.runNetwork()

    val stxGameState = futureWithGameState.getOrThrow()

    // Get a reference to the issued game state
    val gameState = stxGameState.coreTransaction.outputsOfType<GameBoardState>().single()

    val arrayOfAllTransactions = arrayListOf<SignedTransaction>()
    val arrayOfAllPlayerNodes = arrayListOf(a, b, c, d)
    val arrayOfAllPlayerNodesInOrder = gameState.players.map { player -> arrayOfAllPlayerNodes.filter { it.info.chooseIdentity() == player }.first() }

    setupGameBoardForTesting(gameState, network, arrayOfAllPlayerNodesInOrder, arrayOfAllTransactions)

    val gameBoardState = arrayOfAllTransactions.last().coreTransaction.outRefsOfType<GameBoardState>().first().state.data
    val nodeWithMoreThan7 = gatherResourcesUntilAPlayerHasMoreThan7(gameBoardState, arrayOfAllPlayerNodesInOrder, oracle, network)

    val diceRollTriggeringRobber = getDiceRollWithSpecifiedRollValue(3,4, gameBoardState, oracle)
    val futureWithRobberTriggered = arrayOfAllPlayerNodes[0].startFlow(RollDiceFlow(gameBoardState.linearId, diceRollTriggeringRobber))
    network.runNetwork()
    futureWithRobberTriggered.getOrThrow()

    val futureWithMovedRobber = arrayOfAllPlayerNodesInOrder[0].startFlow(MoveRobberFlow(gameBoardState.linearId, 5))
    network.runNetwork()
    futureWithMovedRobber.getOrThrow()

    val futureWithRobberApplied = arrayOfAllPlayerNodesInOrder[0].startFlow(ApplyRobberFlow(gameBoardState.linearId))
    network.runNetwork()
    val txWithAppliedRobber = futureWithRobberApplied.getOrThrow().coreTransaction

   val playBlockerState = txWithAppliedRobber.outputsOfType<PlayBlockerState>()
            .filter { it.playerBlocked == nodeWithMoreThan7.info.legalIdentities.first() }
            .first()

    var resourceTotal = 0
    val resourcesToSpend = mutableMapOf<TokenType, Long>()
    val playerResources = countAllResourcesForASpecificNode(nodeWithMoreThan7).mutableMap
    playerResources.forEach {
        if (resourceTotal < playBlockerState.price) {
            if (resourceTotal + it.value > playBlockerState.price) {
                val amount = it.value + resourceTotal.toLong() - playBlockerState.price
                resourcesToSpend[it.key] = amount
                resourceTotal += amount.toInt()
            }
            else {
                resourcesToSpend[it.key] = it.value
                resourceTotal += it.value.toInt()
            }
        }
    }

    val futureWithRemovedPlayBlockerState = nodeWithMoreThan7.startFlow(RemovePlayBlockerFlow(playBlockerState.linearId, resourcesToSpend))
    network.runNetwork()
    futureWithRemovedPlayBlockerState.getOrThrow()

    requireThat {
        "All nodes now recognize that the nodeWithMoreThan7 has removed its playBlocker" using (
                arrayOfAllPlayerNodes.all { it.services.vaultService.queryBy<PlayBlockerState>().states.filter { it.state.data.playerBlocked == nodeWithMoreThan7.info.legalIdentities.first() }.isEmpty() })
    }

}

}

【问题讨论】:

  • " 这导致了一个非常奇怪的内存泄漏错误。测试在单独运行时通过,但在一次运行时失败。"请向我们展示您正在运行的测试。
  • @AdriaanKoster - 我会将它们添加为单独的 sn-p

标签: kotlin garbage-collection jvm out-of-memory corda


【解决方案1】:

generateInGameSpend 函数移动到 Corda 服务中,这样代码就不会被检查点。

在检查点流时,随着流的堆栈不断增长,运行大/长的循环可能会导致问题。我认为在持久化检查点/稍后重新加载它时可能会导致问题。

Corda 服务内部的代码没有检查点,因此可以规避这个问题。

【讨论】:

  • 刚刚重新实现为 CordaService。手指交叉!
  • 该死,没有运气。奇怪的是,当我单独运行测试时,它们通过了。当我将它们与其他任何东西一起运行时,它们会失败。 ./gradlew clean :allTests:test --tests 'com.robberFlowTests.*' 通过和 `./gradlew clean :allTests:test --tests 'com.tradeTests.*' --tests 'com.robberFlowTests.*' 失败
【解决方案2】:

你期待吗

@Before
fun setup() {
    network.runNetwork()
}

在每个测试用例之前运行?因为这就是这样做的。快速连续运行/停止网络是否没有副作用?

旁注:如果您想为所有测试用例重复使用网络,那么您可以使用@BeforeClass(必须在静态方法上指定)。加载测试类时,该方法将只运行一次。

【讨论】:

    【解决方案3】:

    解决此问题的方法是告诉 gradle 每 N 次测试丢弃测试过程并使用新的。这将容忍内存泄漏。

    forkEvery(N) 
    

    默认值为 0。即它使用相同的测试过程进行无限数量的测试。

    此属性指定 Gradle 的最大测试类数 应该在处理之前运行一个测试进程和一个新进程 创建的。这主要用作管理泄漏测试或 具有无法清除或重置的静态状态的框架 测试之间。

    警告:低值(0 以外)可能会严重影响性能 测试次数

    -Gradle docs

    【讨论】:

      猜你喜欢
      • 2013-03-10
      • 1970-01-01
      • 2016-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-16
      相关资源
      最近更新 更多