Skip to content

Commit d7d62a7

Browse files
authored
Fix unbounded memory usage with era imported node during old heders (#7941)
1 parent 695420e commit d7d62a7

File tree

6 files changed

+56
-35
lines changed

6 files changed

+56
-35
lines changed

src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs

+3
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,7 @@ public interface ISyncConfig : IConfig
167167

168168
[ConfigItem(Description = "_Technical._ Max distance between best suggested header and available state to assume state is synced.", DefaultValue = "0", HiddenFromDocs = true)]
169169
int HeaderStateDistance { get; set; }
170+
171+
[ConfigItem(Description = "_Technical._ Memory budget for in memory dependencies of fast headers.", DefaultValue = "0", HiddenFromDocs = true)]
172+
ulong FastHeadersMemoryBudget { get; set; }
170173
}

src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
22
// SPDX-License-Identifier: LGPL-3.0-only
33
using Nethermind.Config;
4+
using Nethermind.Core.Extensions;
45
using Nethermind.Db;
56

67
namespace Nethermind.Blockchain.Synchronization
@@ -80,6 +81,8 @@ public string? PivotHash
8081
/// </summary>
8182
public int HeaderStateDistance { get; set; } = 0;
8283

84+
public ulong FastHeadersMemoryBudget { get; set; } = (ulong)128.MB();
85+
8386
public override string ToString()
8487
{
8588
return

src/Nethermind/Nethermind.Init/MemoryHintMan.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ private void AssignFastBlocksMemory(ISyncConfig syncConfig)
154154
FastBlocksMemory = Math.Min(1.GB(), (long)(0.1 * _remainingMemory));
155155
}
156156

157-
Synchronization.MemoryAllowance.FastBlocksMemory = (ulong)FastBlocksMemory;
157+
syncConfig.FastHeadersMemoryBudget = (ulong)FastBlocksMemory;
158158
}
159159
}
160160

src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/FastHeadersSyncTests.cs

+33
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,39 @@ void FillBatch(HeadersSyncBatch batch)
557557
localBlockTree.LowestInsertedHeader?.Number.Should().Be(0);
558558
}
559559

560+
[Test]
561+
public async Task Limits_persisted_headers_dependency()
562+
{
563+
var peerChain = CachedBlockTreeBuilder.OfLength(1000);
564+
var pivotHeader = peerChain.FindHeader(700)!;
565+
var syncConfig = new TestSyncConfig
566+
{
567+
FastSync = true,
568+
PivotNumber = pivotHeader.Number.ToString(),
569+
PivotHash = pivotHeader.Hash!.ToString(),
570+
PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()!,
571+
FastHeadersMemoryBudget = (ulong)100.KB(),
572+
};
573+
574+
IBlockTree localBlockTree = Build.A.BlockTree(peerChain.FindBlock(0, BlockTreeLookupOptions.None)!, null).WithSyncConfig(syncConfig).TestObject;
575+
576+
// Insert some chain
577+
for (int i = 300; i < 600; i++)
578+
{
579+
localBlockTree.Insert(peerChain.FindHeader(i)!).Should().Be(AddBlockResult.Added);
580+
}
581+
582+
ISyncPeerPool syncPeerPool = Substitute.For<ISyncPeerPool>();
583+
ISyncReport report = Substitute.For<ISyncReport>();
584+
report.HeadersInQueue.Returns(new MeasuredProgress());
585+
report.FastBlocksHeaders.Returns(new MeasuredProgress());
586+
using HeadersSyncFeed feed = new(localBlockTree, syncPeerPool, syncConfig, report, new TestLogManager(LogLevel.Trace));
587+
feed.InitializeFeed();
588+
589+
(await feed.PrepareRequest()).Should().NotBe(null);
590+
(await feed.PrepareRequest()).Should().Be(null);
591+
}
592+
560593
[Test]
561594
public async Task Will_never_lose_batch_on_invalid_batch()
562595
{

src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs

+16-22
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class HeadersSyncFeed : ActivatedSyncFeed<HeadersSyncBatch?>
3838
private readonly Lock _handlerLock = new();
3939

4040
private readonly int _headersRequestSize = GethSyncLimits.MaxHeaderFetch;
41+
private readonly ulong _fastHeadersMemoryBudget;
4142
protected long _lowestRequestedHeaderNumber;
4243

4344
protected Hash256 _nextHeaderHash;
@@ -67,7 +68,6 @@ public class HeadersSyncFeed : ActivatedSyncFeed<HeadersSyncBatch?>
6768
/// </summary>
6869
private readonly ReaderWriterLockSlim _resetLock = new();
6970

70-
private IEnumerator<KeyValuePair<long, HeadersSyncBatch>>? _enumerator;
7171
private ulong _memoryEstimate;
7272
private long _headersEstimate;
7373

@@ -104,19 +104,14 @@ private long HeadersInQueue
104104
private long CalculateHeadersInQueue()
105105
{
106106
// Reuse the enumerator
107-
var enumerator = Interlocked.Exchange(ref _enumerator, null) ?? _dependencies.GetEnumerator();
107+
using var enumerator = _dependencies.GetEnumerator();
108108

109109
long count = 0;
110110
while (enumerator.MoveNext())
111111
{
112112
count += enumerator.Current.Value.Response?.Count ?? 0;
113113
}
114114

115-
// Stop gap method to reduce allocations from non-struct enumerator
116-
// https://github.com/dotnet/runtime/pull/38296
117-
enumerator.Reset();
118-
_enumerator = enumerator;
119-
120115
return count;
121116
}
122117

@@ -138,19 +133,14 @@ private ulong MemoryInQueue
138133
private ulong CalculateMemoryInQueue()
139134
{
140135
// Reuse the enumerator
141-
var enumerator = Interlocked.Exchange(ref _enumerator, null) ?? _dependencies.GetEnumerator();
136+
using var enumerator = _dependencies.GetEnumerator();
142137

143138
ulong amount = 0;
144139
while (enumerator.MoveNext())
145140
{
146141
amount += (ulong)enumerator.Current.Value?.ResponseSizeEstimate;
147142
}
148143

149-
// Stop gap method to reduce allocations from non-struct enumerator
150-
// https://github.com/dotnet/runtime/pull/38296
151-
enumerator.Reset();
152-
_enumerator = enumerator;
153-
154144
return amount;
155145
}
156146

@@ -169,6 +159,7 @@ public HeadersSyncFeed(
169159
_syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig));
170160
_logger = logManager?.GetClassLogger<HeadersSyncFeed>() ?? throw new ArgumentNullException(nameof(HeadersSyncFeed));
171161
_totalDifficultyStrategy = totalDifficultyStrategy ?? new CumulativeTotalDifficultyStrategy();
162+
_fastHeadersMemoryBudget = syncConfig.FastHeadersMemoryBudget;
172163

173164
if (!_syncConfig.UseGethLimitsInFastBlocks)
174165
{
@@ -229,7 +220,7 @@ private bool ShouldBuildANewBatch()
229220

230221
bool noBatchesLeft = AllHeadersDownloaded
231222
|| destinationHeaderRequested
232-
|| MemoryInQueue >= MemoryAllowance.FastBlocksMemory
223+
|| MemoryInQueue >= _fastHeadersMemoryBudget
233224
|| isImmediateSync && AnyHeaderDownloaded;
234225

235226
if (noBatchesLeft)
@@ -512,15 +503,18 @@ private void EnqueueBatch(HeadersSyncBatch batch, bool skipPersisted = false)
512503
}
513504

514505
headers.AsSpan().Reverse();
515-
516-
using HeadersSyncBatch newBatchToProcess = new HeadersSyncBatch();
517-
newBatchToProcess.StartNumber = lastHeader.Number;
518-
newBatchToProcess.RequestSize = headers.Count;
519-
newBatchToProcess.Response = headers;
520-
if (_logger.IsDebug) _logger.Debug($"Handling header portion {newBatchToProcess.StartNumber} to {newBatchToProcess.EndNumber} with persisted headers.");
521-
InsertHeaders(newBatchToProcess);
522-
523506
int newRequestSize = batch.RequestSize - headers.Count;
507+
if (headers.Count > 0)
508+
{
509+
using HeadersSyncBatch newBatchToProcess = new HeadersSyncBatch();
510+
newBatchToProcess.StartNumber = lastHeader.Number;
511+
newBatchToProcess.RequestSize = headers.Count;
512+
newBatchToProcess.Response = headers;
513+
if (_logger.IsDebug) _logger.Debug($"Handling header portion {newBatchToProcess.StartNumber} to {newBatchToProcess.EndNumber} with persisted headers.");
514+
InsertHeaders(newBatchToProcess);
515+
MarkDirty();
516+
HeadersSyncQueueReport.Update(HeadersInQueue);
517+
}
524518

525519
if (newRequestSize == 0) return null;
526520

src/Nethermind/Nethermind.Synchronization/MemoryAllowance.cs

-12
This file was deleted.

0 commit comments

Comments
 (0)