Skip to content

Commit 329abde

Browse files
pairbitITikhonov
authored andcommitted
add ResponseArrayPool (#3104)
* add ResponseArrayPool * Lease * IMemoryOwner<byte>? --------- Co-authored-by: ITikhonov <ITikhonov@lanit.ru>
1 parent ab38143 commit 329abde

6 files changed

Lines changed: 80 additions & 26 deletions

File tree

src/StackExchange.Redis/ConfigurationOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers;
23
using System.Collections.Generic;
34
using System.ComponentModel;
45
using System.Diagnostics.CodeAnalysis;
@@ -1438,5 +1439,16 @@ public CycleBufferPool? RequestCycleBufferPool
14381439
[Experimental(Experiments.Respite, UrlFormat = Experiments.UrlFormat)]
14391440
set;
14401441
}
1442+
1443+
/// <summary>
1444+
/// The memory pool to use when buffering responses.
1445+
/// </summary>
1446+
public MemoryPool<byte>? ResponseMemoryPool
1447+
{
1448+
[Experimental(Experiments.Respite, UrlFormat = Experiments.UrlFormat)]
1449+
get;
1450+
[Experimental(Experiments.Respite, UrlFormat = Experiments.UrlFormat)]
1451+
set;
1452+
}
14411453
}
14421454
}

src/StackExchange.Redis/Lease.cs

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Buffers;
33
using System.Runtime.CompilerServices;
4+
using System.Runtime.InteropServices;
45
using System.Threading;
56

67
namespace StackExchange.Redis
@@ -16,7 +17,7 @@ public sealed class Lease<T> : IMemoryOwner<T>
1617
/// </summary>
1718
public static Lease<T> Empty { get; } = new Lease<T>(System.Array.Empty<T>(), 0);
1819

19-
private T[]? _arr;
20+
private object? _buffer;
2021

2122
/// <summary>
2223
/// Gets whether this lease is empty.
@@ -41,9 +42,29 @@ public static Lease<T> Create(int length, bool clear = true)
4142
return new Lease<T>(arr, length);
4243
}
4344

45+
/// <summary>
46+
/// Create a new lease.
47+
/// </summary>
48+
/// <param name="length">The size required.</param>
49+
/// <param name="memoryOwner">Buffer.</param>
50+
public static Lease<T> Create(int length, IMemoryOwner<T> memoryOwner)
51+
{
52+
if (length == 0) return Empty;
53+
if ((uint)length > memoryOwner.Memory.Length)
54+
throw new ArgumentOutOfRangeException(nameof(length));
55+
56+
return new Lease<T>(memoryOwner, length);
57+
}
58+
4459
private Lease(T[] arr, int length)
4560
{
46-
_arr = arr;
61+
_buffer = arr;
62+
Length = length;
63+
}
64+
65+
private Lease(IMemoryOwner<T> memoryOwner, int length)
66+
{
67+
_buffer = memoryOwner;
4768
Length = length;
4869
}
4970

@@ -54,33 +75,50 @@ public void Dispose()
5475
{
5576
if (Length != 0)
5677
{
57-
var arr = Interlocked.Exchange(ref _arr, null);
58-
if (arr != null) ArrayPool<T>.Shared.Return(arr);
78+
var buffer = Interlocked.Exchange(ref _buffer, null);
79+
if (buffer != null)
80+
{
81+
if (buffer is T[] arr)
82+
ArrayPool<T>.Shared.Return(arr);
83+
else
84+
((IMemoryOwner<T>)buffer).Dispose();
85+
}
5986
}
6087
}
6188

6289
[MethodImpl(MethodImplOptions.NoInlining)]
6390
private static T[] ThrowDisposed() => throw new ObjectDisposedException(nameof(Lease<T>));
6491

65-
private T[] Array
66-
{
67-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
68-
get => _arr ?? ThrowDisposed();
69-
}
70-
7192
/// <summary>
7293
/// The data as a <see cref="Memory{T}"/>.
7394
/// </summary>
74-
public Memory<T> Memory => new Memory<T>(Array, 0, Length);
95+
public Memory<T> Memory => _buffer is IMemoryOwner<T> memoryOwner
96+
? memoryOwner.Memory.Slice(0, Length)
97+
: new Memory<T>((T[]?)_buffer ?? ThrowDisposed(), 0, Length);
7598

7699
/// <summary>
77100
/// The data as a <see cref="Span{T}"/>.
78101
/// </summary>
79-
public Span<T> Span => new Span<T>(Array, 0, Length);
102+
public Span<T> Span => _buffer is IMemoryOwner<T> memoryOwner
103+
? memoryOwner.Memory.Span.Slice(0, Length)
104+
: new Span<T>((T[]?)_buffer ?? ThrowDisposed(), 0, Length);
80105

81106
/// <summary>
82107
/// The data as an <see cref="ArraySegment{T}"/>.
83108
/// </summary>
84-
public ArraySegment<T> ArraySegment => new ArraySegment<T>(Array, 0, Length);
109+
public ArraySegment<T> ArraySegment
110+
{
111+
get
112+
{
113+
if (_buffer is IMemoryOwner<T> memoryOwner)
114+
{
115+
if (!MemoryMarshal.TryGetArray((ReadOnlyMemory<T>)memoryOwner.Memory, out var segment))
116+
throw new NotSupportedException("Only array-backed buffers are supported");
117+
118+
return new ArraySegment<T>(segment.Array!, segment.Offset, Length);
119+
}
120+
return new ArraySegment<T>((T[]?)_buffer ?? ThrowDisposed(), 0, Length);
121+
}
122+
}
85123
}
86124
}

src/StackExchange.Redis/PhysicalConnection.Read.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Buffers;
33
using System.Buffers.Binary;
44
using System.Diagnostics;
@@ -205,7 +205,7 @@ private bool ShouldTransitionToAsync()
205205

206206
private bool ForceReconnect => BridgeCouldBeNull?.NeedsReconnect == true;
207207

208-
private static byte[]? SharedNoLease;
208+
private static IMemoryOwner<byte>? SharedNoLease;
209209

210210
private CycleBuffer _readBuffer;
211211
private RespScanState _readState = default;
@@ -401,15 +401,15 @@ private void OnResponseFrame(RespPrefix prefix, ReadOnlySequence<byte> payload)
401401
else
402402
{
403403
var len = checked((int)payload.Length);
404-
byte[]? oversized = ArrayPool<byte>.Shared.Rent(len);
404+
var memoryPool = BridgeCouldBeNull?.Multiplexer.RawConfig.ResponseMemoryPool ?? MemoryPool<byte>.Shared;
405+
var memoryOwner = memoryPool.Rent(len);
406+
Span<byte> oversized = memoryOwner.Memory.Span.Slice(0, len);
407+
405408
payload.CopyTo(oversized);
406-
OnResponseFrame(prefix, new(oversized, 0, len), ref oversized);
407409

408-
// the lease could have been claimed by the activation code (to prevent another memcpy); otherwise, free
409-
if (oversized is not null)
410-
{
411-
ArrayPool<byte>.Shared.Return(oversized);
412-
}
410+
OnResponseFrame(prefix, oversized, ref memoryOwner);
411+
412+
memoryOwner?.Dispose();
413413
}
414414
}
415415

@@ -420,7 +420,7 @@ private void UpdateBufferStats(int lastResult, long inBuffer)
420420
bytesLastResult = lastResult;
421421
}
422422

423-
private void OnResponseFrame(RespPrefix prefix, ReadOnlySpan<byte> frame, ref byte[]? lease)
423+
private void OnResponseFrame(RespPrefix prefix, ReadOnlySpan<byte> frame, ref IMemoryOwner<byte>? memoryOwner)
424424
{
425425
DebugValidateSingleFrame(frame);
426426
_readStatus = ReadStatus.MatchResult;
@@ -431,7 +431,7 @@ private void OnResponseFrame(RespPrefix prefix, ReadOnlySpan<byte> frame, ref by
431431
case RespPrefix.Array when (_protocol is RedisProtocol.Resp2 & connectionType is ConnectionType.Subscription)
432432
&& !IsArrayPong(frame): // could be a RESP2 pub/sub payload
433433
// out-of-band; pub/sub etc
434-
if (OnOutOfBand(frame, ref lease))
434+
if (OnOutOfBand(frame, ref memoryOwner))
435435
{
436436
OnDetailLog($"out-of-band message, not dequeuing: {prefix}");
437437
return;
@@ -502,7 +502,7 @@ internal static ReadOnlySpan<byte> StackCopyLengthChecked(scoped in RespReader r
502502
return buffer.Slice(0, len);
503503
}
504504

505-
private bool OnOutOfBand(ReadOnlySpan<byte> payload, ref byte[]? lease)
505+
private bool OnOutOfBand(ReadOnlySpan<byte> payload, ref IMemoryOwner<byte>? memoryOwner)
506506
{
507507
var muxer = BridgeCouldBeNull?.Multiplexer;
508508
if (muxer is null) return true; // consume it blindly

src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,7 @@ static StackExchange.Redis.HashEntry.operator !=(StackExchange.Redis.HashEntry x
17341734
static StackExchange.Redis.HashEntry.operator ==(StackExchange.Redis.HashEntry x, StackExchange.Redis.HashEntry y) -> bool
17351735
static StackExchange.Redis.KeyspaceIsolation.DatabaseExtensions.WithKeyPrefix(this StackExchange.Redis.IDatabase! database, StackExchange.Redis.RedisKey keyPrefix) -> StackExchange.Redis.IDatabase!
17361736
static StackExchange.Redis.Lease<T>.Create(int length, bool clear = true) -> StackExchange.Redis.Lease<T>!
1737+
static StackExchange.Redis.Lease<T>.Create(int length, System.Buffers.IMemoryOwner<T>! memoryOwner) -> StackExchange.Redis.Lease<T>!
17371738
static StackExchange.Redis.Lease<T>.Empty.get -> StackExchange.Redis.Lease<T>!
17381739
static StackExchange.Redis.ListPopResult.Null.get -> StackExchange.Redis.ListPopResult
17391740
static StackExchange.Redis.LuaScript.GetCachedScriptCount() -> int

src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#nullable enable
2+
[SER004]StackExchange.Redis.ConfigurationOptions.ResponseMemoryPool.get -> System.Buffers.MemoryPool<byte>?
3+
[SER004]StackExchange.Redis.ConfigurationOptions.ResponseMemoryPool.set -> void
24
[SER004]StackExchange.Redis.ConfigurationOptions.RequestCycleBufferPool.get -> RESPite.Buffers.CycleBufferPool?
35
[SER004]StackExchange.Redis.ConfigurationOptions.RequestCycleBufferPool.set -> void
46
[SER004]StackExchange.Redis.ConfigurationOptions.ResponseCycleBufferPool.get -> RESPite.Buffers.CycleBufferPool?

src/StackExchange.Redis/RespReaderExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
using System;
2+
using System.Buffers;
23
using System.Diagnostics;
34
using System.Threading.Tasks;
45
using RESPite.Messages;

0 commit comments

Comments
 (0)