Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dispose block.AccountChanges ArrayPoolList #8051

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Marchhill
Copy link
Contributor

Changes

  • Dispose ArrayPoolList

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

Remarks

Fixes the following error observed on chiado node:

Unhandled exception. System.InvalidOperationException: ArrayPoolList hasn't been disposed. Created    at Nethermind.Core.Collections.ArrayPoolList`1..ctor(ArrayPool`1 arrayPool, Int32 capacity, Int32 startingCount)
   at Nethermind.Core.Collections.ArrayPoolList`1..ctor(Int32 capacity)
   at Nethermind.State.StateProvider.ChangedAddresses()
   at Nethermind.State.WorldState.Nethermind.State.IWorldState.GetAccountChanges()
   at Nethermind.State.WorldStateMetricsDecorator.GetAccountChanges()
   at Nethermind.Consensus.Processing.BlockProcessor.ProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options)
   at Nethermind.Consensus.AuRa.AuRaBlockProcessor.PostMergeProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options)
   at Nethermind.Merge.AuRa.AuRaMergeBlockProcessor.ProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options)
   at Nethermind.Consensus.Processing.BlockProcessor.ProcessOne(Block suggestedBlock, ProcessingOptions options, IBlockTracer blockTracer)
   at Nethermind.Consensus.Processing.BlockProcessor.Process(Hash256 newBranchStateRoot, List`1 suggestedBlocks, ProcessingOptions options, IBlockTracer blockTracer)
   at Nethermind.Consensus.Processing.BlockchainProcessor.ProcessBranch(ProcessingBranch& processingBranch, ProcessingOptions options, IBlockTracer tracer, String& error)
   at Nethermind.Consensus.Processing.BlockchainProcessor.Process(Block suggestedBlock, ProcessingOptions options, IBlockTracer tracer, String& error)
   at Nethermind.Consensus.Processing.BlockchainProcessor.RunProcessingLoop()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore`1.SetResult(TResult result)
   at System.Threading.Channels.ChannelReader`1.ReadAllAsync(CancellationToken cancellationToken)+MoveNext()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.MoveNext(Thread threadPoolThread)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

   at Nethermind.Core.Collections.ArrayPoolList`1.Finalize() in /root/nethermind/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs:line 386
   at System.GC.RunFinalizers()

@Marchhill Marchhill requested a review from benaadams January 14, 2025 14:55
@benaadams
Copy link
Member

Is Disposed here in TxPool; so this would mean it was disposed before the txPool could use it

ArrayPoolList<AddressAsKey>? accountChanges = args.Block.AccountChanges;
if (!CanUseCache(args.Block, accountChanges))
{
// Not sequential block, reset cache
_accountCache.Reset();
}
else
{
// Sequential block, just remove changed accounts from cache
_accountCache.RemoveAccounts(accountChanges);
}
args.Block.AccountChanges = null;
accountChanges?.Dispose();

However; OnHeadChange isn't always fired to the txpool for every block 🤔

Copy link
Contributor

@MarekM25 MarekM25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you try to write unit tests that reproduce it? Of course, if possible

@Marchhill
Copy link
Contributor Author

Is Disposed here in TxPool; so this would mean it was disposed before the txPool could use it

ArrayPoolList<AddressAsKey>? accountChanges = args.Block.AccountChanges;
if (!CanUseCache(args.Block, accountChanges))
{
// Not sequential block, reset cache
_accountCache.Reset();
}
else
{
// Sequential block, just remove changed accounts from cache
_accountCache.RemoveAccounts(accountChanges);
}
args.Block.AccountChanges = null;
accountChanges?.Dispose();

However; OnHeadChange isn't always fired to the txpool for every block 🤔

I think this the TxPool would use it before my code disposes it. I added just after the Process function.

The call stack would look something like this Process -> _blockTree.UpdateMainChain -> MoveToMain -> invoke BlockReplacementEventArgs -> TxPool ProcessNewHeads

So the change should just dispose if this is never called

@Marchhill
Copy link
Contributor Author

Could you try to write unit tests that reproduce it? Of course, if possible

I'll have a look into it, happens for me just from starting the node on chiado

Copy link
Member

@LukaszRozmej LukaszRozmej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +288 to +289
block.AccountChanges?.Dispose();
processedBlock?.AccountChanges?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is used in TxPool so cannot be disposed here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my above comment, I think that's before

@LukaszRozmej
Copy link
Member

Is Disposed here in TxPool; so this would mean it was disposed before the txPool could use it

ArrayPoolList<AddressAsKey>? accountChanges = args.Block.AccountChanges;
if (!CanUseCache(args.Block, accountChanges))
{
// Not sequential block, reset cache
_accountCache.Reset();
}
else
{
// Sequential block, just remove changed accounts from cache
_accountCache.RemoveAccounts(accountChanges);
}
args.Block.AccountChanges = null;
accountChanges?.Dispose();

However; OnHeadChange isn't always fired to the txpool for every block 🤔

BlockAddedToMain is fired on every block

@LukaszRozmej
Copy link
Member

Is Disposed here in TxPool; so this would mean it was disposed before the txPool could use it

ArrayPoolList<AddressAsKey>? accountChanges = args.Block.AccountChanges;
if (!CanUseCache(args.Block, accountChanges))
{
// Not sequential block, reset cache
_accountCache.Reset();
}
else
{
// Sequential block, just remove changed accounts from cache
_accountCache.RemoveAccounts(accountChanges);
}
args.Block.AccountChanges = null;
accountChanges?.Dispose();

However; OnHeadChange isn't always fired to the txpool for every block 🤔

I think this the TxPool would use it before my code disposes it. I added just after the Process function.

The call stack would look something like this Process -> _blockTree.UpdateMainChain -> MoveToMain -> invoke BlockReplacementEventArgs -> TxPool ProcessNewHeads

So the change should just dispose if this is never called

Can you add a test for it? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants