Skip to content

Commit 990c396

Browse files
committed
fb
1 parent aa7f1d0 commit 990c396

File tree

4 files changed

+42
-9
lines changed

4 files changed

+42
-9
lines changed

src/Servers/Kestrel/Core/src/Internal/PinnedBlockMemoryPoolFactory.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,28 @@
66
using System.Diagnostics.Metrics;
77
using Microsoft.AspNetCore.Connections;
88
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
9+
using Microsoft.Extensions.Logging;
910

1011
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
1112

1213
internal sealed class PinnedBlockMemoryPoolFactory : IMemoryPoolFactory<byte>, IHeartbeatHandler
1314
{
1415
private readonly IMeterFactory _meterFactory;
16+
private readonly ILogger? _logger;
1517
private readonly TimeProvider _timeProvider;
1618
// micro-optimization: Using nuint as the value type to avoid GC write barriers; could replace with ConcurrentHashSet if that becomes available
1719
private readonly ConcurrentDictionary<PinnedBlockMemoryPool, nuint> _pools = new();
1820

19-
public PinnedBlockMemoryPoolFactory(IMeterFactory meterFactory, TimeProvider? timeProvider = null)
21+
public PinnedBlockMemoryPoolFactory(IMeterFactory meterFactory, TimeProvider? timeProvider = null, ILogger<PinnedBlockMemoryPoolFactory>? logger = null)
2022
{
2123
_timeProvider = timeProvider ?? TimeProvider.System;
2224
_meterFactory = meterFactory;
25+
_logger = logger;
2326
}
2427

2528
public MemoryPool<byte> Create()
2629
{
27-
var pool = new PinnedBlockMemoryPool(_meterFactory);
30+
var pool = new PinnedBlockMemoryPool(_meterFactory, _logger);
2831

2932
_pools.TryAdd(pool, nuint.Zero);
3033

src/Servers/Kestrel/Core/test/PinnedBlockMemoryPoolFactoryTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,19 @@ public async Task FactoryHeartbeatWorks()
9494

9595
static async Task VerifyPoolEviction(PinnedBlockMemoryPool pool, int previousCount)
9696
{
97+
// Because the eviction happens on a thread pool thread, we need to wait for it to complete
98+
// and the only way to do that (without adding a test hook in the pool code) is to delay.
99+
// But we don't want to add an arbitrary delay, so we do a short delay with block count checks
100+
// to reduce the wait time.
97101
var maxWait = TimeSpan.FromSeconds(5);
98102
while (pool.BlockCount() > previousCount - (previousCount / 30) && maxWait > TimeSpan.Zero)
99103
{
100104
await Task.Delay(50);
101105
maxWait -= TimeSpan.FromMilliseconds(50);
102106
}
103107

108+
// Assert that the block count has decreased by 3.3-10%.
109+
// This relies on the current implementation of eviction logic which may change in the future.
104110
Assert.InRange(pool.BlockCount(), previousCount - (previousCount / 10), previousCount - (previousCount / 30));
105111
}
106112
}

src/Shared/Buffers.MemoryPool/DefaultMemoryPoolFactory.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ internal sealed class DefaultMemoryPoolFactory : IMemoryPoolFactory<byte>, IAsyn
1818
private readonly ConcurrentDictionary<PinnedBlockMemoryPool, bool> _pools = new();
1919
private readonly PeriodicTimer _timer;
2020
private readonly Task _timerTask;
21+
private readonly ILogger? _logger;
2122

2223
public DefaultMemoryPoolFactory(IMeterFactory? meterFactory = null, ILogger<DefaultMemoryPoolFactory>? logger = null)
2324
{
2425
_meterFactory = meterFactory;
25-
_timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
26+
_logger = logger;
27+
_timer = new PeriodicTimer(PinnedBlockMemoryPool.DefaultEvictionDelay);
2628
_timerTask = Task.Run(async () =>
2729
{
2830
try
@@ -37,14 +39,14 @@ public DefaultMemoryPoolFactory(IMeterFactory? meterFactory = null, ILogger<Defa
3739
}
3840
catch (Exception ex)
3941
{
40-
logger?.LogCritical(ex, "Error while evicting memory from pools.");
42+
_logger?.LogCritical(ex, "Error while evicting memory from pools.");
4143
}
4244
});
4345
}
4446

4547
public MemoryPool<byte> Create()
4648
{
47-
var pool = new PinnedBlockMemoryPool(_meterFactory);
49+
var pool = new PinnedBlockMemoryPool(_meterFactory, _logger);
4850

4951
_pools.TryAdd(pool, true);
5052

src/Shared/Buffers.MemoryPool/PinnedBlockMemoryPool.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Buffers;
55
using System.Collections.Concurrent;
66
using System.Diagnostics.Metrics;
7+
using Microsoft.Extensions.Logging;
78

89
#nullable enable
910

@@ -19,6 +20,12 @@ internal sealed class PinnedBlockMemoryPool : MemoryPool<byte>, IThreadPoolWorkI
1920
/// </summary>
2021
private const int _blockSize = 4096;
2122

23+
// 10 seconds chosen arbitrarily. Trying to avoid running eviction too frequently
24+
// to avoid trashing if the server gets batches of requests, but want to run often
25+
// enough to avoid memory bloat if the server is idle for a while.
26+
// This can be tuned later if needed.
27+
public static readonly TimeSpan DefaultEvictionDelay = TimeSpan.FromSeconds(10);
28+
2229
/// <summary>
2330
/// Max allocation block size for pooled blocks,
2431
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
@@ -42,10 +49,11 @@ internal sealed class PinnedBlockMemoryPool : MemoryPool<byte>, IThreadPoolWorkI
4249
private bool _isDisposed; // To detect redundant calls
4350

4451
private readonly PinnedBlockMemoryPoolMetrics? _metrics;
52+
private readonly ILogger? _logger;
4553

4654
private long _currentMemory;
4755
private long _evictedMemory;
48-
private DateTimeOffset _nextEviction = DateTime.UtcNow.AddSeconds(10);
56+
private DateTimeOffset _nextEviction = DateTime.UtcNow.Add(DefaultEvictionDelay);
4957

5058
private uint _rentCount;
5159
private uint _returnCount;
@@ -60,9 +68,10 @@ internal sealed class PinnedBlockMemoryPool : MemoryPool<byte>, IThreadPoolWorkI
6068
/// </summary>
6169
private const int AnySize = -1;
6270

63-
public PinnedBlockMemoryPool(IMeterFactory? meterFactory = null)
71+
public PinnedBlockMemoryPool(IMeterFactory? meterFactory = null, ILogger? logger = null)
6472
{
6573
_metrics = meterFactory is null ? null : new PinnedBlockMemoryPoolMetrics(meterFactory);
74+
_logger = logger;
6675
}
6776

6877
/// <summary>
@@ -140,7 +149,7 @@ public bool TryScheduleEviction(DateTimeOffset now)
140149
{
141150
if (now >= _nextEviction)
142151
{
143-
_nextEviction = now.AddSeconds(10);
152+
_nextEviction = now.Add(DefaultEvictionDelay);
144153
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
145154
return true;
146155
}
@@ -150,7 +159,15 @@ public bool TryScheduleEviction(DateTimeOffset now)
150159

151160
void IThreadPoolWorkItem.Execute()
152161
{
153-
PerformEviction();
162+
try
163+
{
164+
PerformEviction();
165+
}
166+
catch (Exception ex)
167+
{
168+
// Log the exception, but don't let it crash the thread pool
169+
_logger?.LogError(ex, "Error while performing eviction in PinnedBlockMemoryPool.");
170+
}
154171
}
155172

156173
/// <summary>
@@ -214,6 +231,11 @@ protected override void Dispose(bool disposing)
214231

215232
lock (_disposeSync)
216233
{
234+
if (_isDisposed)
235+
{
236+
return;
237+
}
238+
217239
_isDisposed = true;
218240

219241
_onPoolDisposed?.Invoke(_onPoolDisposedState, this);

0 commit comments

Comments
 (0)