是什么导致我的Corda流使用的此函数内部出现内存泄漏?

在Cordapp的内部,我编写了一个自定义的generateSpend函数(附加在代码段中)。它结合使用tokenSelection.attemptSpendgenerateExitaddTokensToRedeem来花费来自各个发行者的代币来支付一笔款项。

示例:[成本为3木材],由[2奥斯丁发行的尼克持有的木材],[1尼克发行的尼克发行的木材]支付

我的实施过程是否存在明显错误?这会导致非常奇怪的内存泄漏错误: Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded。孤立运行时测试通过,但一次运行失败。请注意,这里还有100万种可能出错的地方-我使用的是最新的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,arrayOfAllTransactions)

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

    val deterministicDiceRoll = getDiceRollWithSpecifiedRollValue(3,4,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,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,arrayOfAllTransactions)

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

    val diceRollTriggeringRobber = getDiceRollWithSpecifiedRollValue(3,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() })
    }

}

}

vfc123_vgfgfcv 回答:是什么导致我的Corda流使用的此函数内部出现内存泄漏?

generateInGameSpend函数移至Corda服务中,因此永远不会检查代码。

在检查点流中,由于流的堆栈不断增长,因此具有大/长时间运行的循环会导致问题。我认为在保留检查点/稍后再加载它时可能会引起问题。

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

,

您期望

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

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

侧面说明:如果要对所有测试用例重新使用网络,则可以使用@BeforeClass(必须在静态方法上指定)。加载测试类后,该方法将只运行一次。

,

解决此问题的方法是,告诉gradle每N个测试放弃测试过程并使用一个新的测试。这样可以容忍内存泄漏。

forkEvery(N) 

默认值为0,即它对无数次测试使用相同的测试过程。

此属性指定Gradle的最大测试类数 应该在测试过程之前进行测试,然后再重新进行测试 创建。这主要用作管理泄漏测试或 静态状态无法清除或重置的框架 在两次测试之间。

警告:较低的值(非0)会严重损害性能 测试

-Gradle docs

本文链接:https://www.f2er.com/3101529.html

大家都在问