fix: dispose evicted envs from BlockCachePreWarmer pool#11013
Conversation
51bb596 to
34c0cf1
Compare
34c0cf1 to
19a1b49
Compare
|
@claude review |
|
Claude finished @newmanifold's task in 2m 56s —— View job Code ReviewThe fix is correct and the approach is sound. Bug / Correctness1. Nullability mismatch in
// Interface declares:
IReadOnlyTxProcessingScope Build(BlockHeader? baseBlock);
// TrackingEnv implements:
public IReadOnlyTxProcessingScope Build(BlockHeader baseBlock) =>The parameter is Test Reliability2. Test can pass vacuously if no eviction occurs
int evictedCount = created.Count - 1; // at most 1 retained in pool
disposed.Count.Should().BeGreaterThanOrEqualTo(evictedCount, ...);If the thread scheduler runs both sender groups sequentially on the same thread, only 1 env is ever created. Then Consider adding an assertion that forces eviction to have been observable: created.Count.Should().BeGreaterThanOrEqualTo(2, "two distinct senders must exercise two concurrent workers");Minor Style3. Redundant
public class AutoReadOnlyTxProcessingEnv(...) : IReadOnlyTxProcessorSource, IDisposableSince 4. Odd field name
private readonly ObjectPool<IReadOnlyTxProcessorSource> _envPool2;The Observation (not a bug, but worth noting)5. Both classes hold an SummaryThe core fix is correct — the mechanism works and the AuRa test teardowns are a good catch. Items 1 and 2 are worth fixing before merge; items 3 and 4 are minor cleanup. |
…rldState leak DefaultObjectPool<T> silently drops objects when at capacity without calling Dispose(). The child ILifetimeScope inside each evicted IReadOnlyTxProcessorSource was never closed, leaking WorldState and everything it holds until process shutdown. Fix: add IDisposable to IReadOnlyTxProcessorSource (the only implementor, AutoReadOnlyTxProcessingEnv, already had Dispose()). Replace new DefaultObjectPool<>() with DefaultObjectPoolProvider.Create(), which detects T : IDisposable at runtime and returns the internal DisposableObjectPool<T> that calls Dispose() on evicted items. Apply the same fix to ShareableTxProcessingSource which had the identical pattern. Add regression test: PoolEviction_WhenPoolIsFull_EvictedEnvIsDisposed verifies that Dispose() is called synchronously on Return() when the pool is at capacity.
19a1b49 to
1f96bcf
Compare
|
@claude review again |
|
Claude finished @newmanifold's task in 1m 42s —— View job Code Review
All four items from the previous review have been addressed. Here's the updated assessment: Previously Flagged — Now Fixed
Remaining Minor IssueConstructor parameter still named
public ScopeWrapper(IReadOnlyTxProcessorSource source, ObjectPool<IReadOnlyTxProcessorSource> envPool2, IReadOnlyTxProcessingScope scope)The field was correctly renamed to Correctness CheckThe core mechanism is sound: NSubstitute mocks in The only two concrete implementations ( Standing Observation (non-blocking)
SummaryThe fix is correct and all substantive issues have been resolved. The |
…able project context
…iction and scope close
|
@claude review please |
|
Claude finished @newmanifold's task in 2m 1s —— View job Code Review
This revision adds two meaningful improvements on top of the previous pass: Still Open (from previous review)1. Constructor parameter
public ScopeWrapper(IReadOnlyTxProcessorSource source, ObjectPool<IReadOnlyTxProcessorSource> envPool2, IReadOnlyTxProcessingScope scope)The field was correctly renamed to New Issues in This Revision2. Private fields missing
private readonly PreBlockCaches preBlockCaches;
private readonly NodeStorageCache nodeStorageCache;The other private fields in the same class ( 3. Fragile
public void Dispose() => ((IDisposable)_envPool).Dispose();This relies on // In the constructor:
ObjectPool<IReadOnlyTxProcessorSource> pool = new DefaultObjectPoolProvider { MaximumRetained = maxPoolSize }.Create(poolPolicy);
_envPool = pool;
_poolDisposable = pool as IDisposable; // or store IDisposable separately
// Dispose:
public void Dispose() => _poolDisposable?.Dispose();Or if you want a hard guarantee, assert it in the constructor: Debug.Assert(_envPool is IDisposable, "Pool must be disposable for env cleanup to work");Previously Flagged — Now Fixed
Minor Observation (non-blocking)
SummaryItems 2 and 3 above are worth fixing before merge — item 2 is a style violation, item 3 is a latent correctness risk. Item 1 (the |
…emove unused using
|
@claude review again |
|
Claude finished @newmanifold's task in 1m 19s —— View job Code Review
All three issues flagged in the previous review have been addressed. Previously Flagged — Now Fixed
Correctness CheckThe core mechanism is correct throughout:
Standing Observation (non-blocking)
SummaryThe fix is correct, all substantive and cosmetic issues have been resolved, and the tests provide good regression coverage for both eviction and disposal on shutdown. Ready to merge. |
|
@claude review please |
|
Claude finished @newmanifold's task in 1m 35s —— View job Code Review
|
| # | Issue | Status |
|---|---|---|
| 1 | Nullability mismatch TrackingEnv.Build(BlockHeader baseBlock) |
✅ Fixed |
| 2 | Test could pass vacuously if no eviction occurs | ✅ Fixed |
| 3 | Redundant explicit IDisposable on AutoReadOnlyTxProcessingEnv |
✅ Fixed |
| 4 | _envPool2 field and constructor parameter |
✅ Fixed |
| 5 | Private fields missing _ prefix |
✅ Fixed |
| 6 | Fragile hard cast ((IDisposable)_envPool).Dispose() |
✅ Fixed — as pattern used |
| 7 | ShareableTxProcessingSource didn't dispose its pool (standing observation) |
✅ Fixed — IShareableTxProcessorSource extends IDisposable, Dispose() implemented |
One Remaining Nit
_envPool field in ShareableTxProcessingSource missing private readonly
src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs:21
ObjectPool<IReadOnlyTxProcessorSource> _envPool = new DefaultObjectPoolProvider().Create(new EnvPoolPolicy(envFactory));The equivalent field in BlockCachePreWarmer is declared private readonly. This field is implicitly private (C# default for class members) and is never reassigned, so private readonly would match both the convention in the sibling class and make the intent explicit.
Correctness Check
The core mechanism is correct throughout:
- Both
DefaultObjectPoolProvider.Create<T>()calls seeT : IDisposable(sinceIReadOnlyTxProcessorSource : IDisposable) and selectDisposableObjectPool<T>, which callsDispose()immediately on any item the pool cannot retain. - Both
Dispose()implementations use(_envPool as IDisposable)?.Dispose(), which is safe and won't throw if the pool implementation changes. AutoReadOnlyTxProcessingEnvsatisfiesIReadOnlyTxProcessorSourcewith a properDispose()that tears down its childILifetimeScope.- Both tests cover the two disposal paths: eviction under pool pressure, and graceful shutdown via
preWarmer.Dispose().
Summary
All substantive and cosmetic issues have been resolved. The only remaining item is a private readonly annotation nit on _envPool in ShareableTxProcessingSource. Ready to merge.

Changes
IDisposabletoIReadOnlyTxProcessorSourceandIShareableTxProcessorSourceso pool implementations can clean up retained itemsnew DefaultObjectPool<>()withDefaultObjectPoolProvider.Create()inBlockCachePreWarmerandShareableTxProcessingSource— this selects the internalDisposableObjectPool<T>which callsDispose()on evicted items when the pool is at capacityIDisposabletoIBlockCachePreWarmerand implementDispose()onBlockCachePreWarmerto release retained envs when the processing scope closes, rather than waiting for the parent Autofac scope to shut downShareableTxProcessingSourcepreBlockCaches/nodeStorageCacheto_preBlockCaches/_nodeStorageCachefor consistency with the rest of the classRoot cause
DefaultObjectPool<T>silently drops objects when the pool is at capacity without callingDispose(). Each evictedAutoReadOnlyTxProcessingEnvheld a childILifetimeScoperegistered on the parent Autofac scope's disposal list — so those scopes (and theWorldStateinstances they own) stayed alive until the parent scope shut down, never released eagerly.A secondary issue: even retained envs in the pool were never disposed when the processing scope closed, because neither
BlockCachePreWarmernorShareableTxProcessingSourceimplementedIDisposable. Autofac skipped them on scope teardown, deferring cleanup to the parent scope.Types of changes
Testing
Requires testing
If yes, did you write tests?
Notes on testing
Two regression tests in
BlockCachePreWarmerTests:PreWarmCaches_WhenPoolEvicts_EvictedEnvsAreDisposed— verifies evicted envs haveDispose()called immediately (pool capacity 1, two parallel sender groups)Dispose_WhenCalled_DisposesRetainedEnvsInPool— verifies all retained envs in the pool are disposed when the prewarmer itself is disposedDocumentation
Requires documentation update
Requires explanation in Release Notes