Skip to content

Commit 1baa0de

Browse files
committed
Use object pool
1 parent 4ea74dd commit 1baa0de

File tree

3 files changed

+66
-62
lines changed

3 files changed

+66
-62
lines changed

src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnection.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,13 @@ public override async ValueTask DisposeAsync()
294294
return;
295295
}
296296

297-
if (!_streamDisconnected || !_connectionListener.TryCacheStream(_stream))
297+
if (!_streamDisconnected)
298298
{
299299
_stream.Dispose();
300300
}
301+
else
302+
{
303+
_connectionListener.ReturnStream(_stream);
304+
}
301305
}
302306
}

src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Buffers;
5-
using System.Collections.Concurrent;
65
using System.Diagnostics;
76
using System.IO.Pipelines;
87
using System.IO.Pipes;
98
using System.Net;
109
using System.Threading.Channels;
1110
using Microsoft.AspNetCore.Connections;
1211
using Microsoft.Extensions.Logging;
12+
using Microsoft.Extensions.ObjectPool;
1313
using NamedPipeOptions = System.IO.Pipes.PipeOptions;
1414
using PipeOptions = System.IO.Pipelines.PipeOptions;
1515

@@ -20,14 +20,14 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener
2020
private readonly ILogger _log;
2121
private readonly NamedPipeEndPoint _endpoint;
2222
private readonly NamedPipeTransportOptions _options;
23+
private readonly ObjectPool<NamedPipeServerStream> _namedPipeServerStreamPool;
2324
private readonly CancellationTokenSource _listeningTokenSource = new CancellationTokenSource();
2425
private readonly CancellationToken _listeningToken;
2526
private readonly Channel<ConnectionContext> _acceptedQueue;
2627
private readonly MemoryPool<byte> _memoryPool;
2728
private readonly PipeOptions _inputOptions;
2829
private readonly PipeOptions _outputOptions;
2930
private readonly Mutex _mutex;
30-
private readonly ConcurrentQueue<NamedPipeServerStream> _streamsCache = new ConcurrentQueue<NamedPipeServerStream>();
3131
private Task[]? _listeningTasks;
3232
private int _disposed;
3333

@@ -40,6 +40,7 @@ public NamedPipeConnectionListener(
4040
_log = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes");
4141
_endpoint = endpoint;
4242
_options = options;
43+
_namedPipeServerStreamPool = new DefaultObjectPoolProvider().Create(new NamedPipeServerStreamPoolPolicy(this));
4344
_mutex = mutex;
4445
_memoryPool = options.MemoryPoolFactory();
4546
_listeningToken = _listeningTokenSource.Token;
@@ -56,17 +57,10 @@ public NamedPipeConnectionListener(
5657
_outputOptions = new PipeOptions(_memoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false);
5758
}
5859

59-
internal bool TryCacheStream(NamedPipeServerStream namedPipeServerStream)
60+
internal void ReturnStream(NamedPipeServerStream namedPipeServerStream)
6061
{
61-
// Limit the number of cached named pipe server streams.
62-
// This isn't thread safe so it's possible for Count and Enqueue to race and slightly exceed this limit.
63-
if (_streamsCache.Count <= 50)
64-
{
65-
_streamsCache.Enqueue(namedPipeServerStream);
66-
return true;
67-
}
68-
69-
return false;
62+
// The stream is automatically disposed if there isn't space in the pool.
63+
_namedPipeServerStreamPool.Return(namedPipeServerStream);
7064
}
7165

7266
public void Start()
@@ -78,7 +72,7 @@ public void Start()
7872
for (var i = 0; i < _listeningTasks.Length; i++)
7973
{
8074
// Start first stream inline to catch creation errors.
81-
var initialStream = CreateServerStream();
75+
var initialStream = _namedPipeServerStreamPool.Get();
8276

8377
_listeningTasks[i] = Task.Run(() => StartAsync(initialStream));
8478
}
@@ -104,7 +98,7 @@ private async Task StartAsync(NamedPipeServerStream nextStream)
10498
// Create the next stream before writing connected stream to the channel.
10599
// This ensures there is always a created stream and another process can't
106100
// create a stream with the same name with different a access policy.
107-
nextStream = GetOrCreateServerStream();
101+
nextStream = _namedPipeServerStreamPool.Get();
108102

109103
while (!_acceptedQueue.Writer.TryWrite(connection))
110104
{
@@ -121,7 +115,7 @@ private async Task StartAsync(NamedPipeServerStream nextStream)
121115

122116
// Dispose existing pipe, create a new one and continue accepting.
123117
nextStream.Dispose();
124-
nextStream = GetOrCreateServerStream();
118+
nextStream = _namedPipeServerStreamPool.Get();
125119
}
126120
catch (OperationCanceledException ex) when (_listeningToken.IsCancellationRequested)
127121
{
@@ -140,52 +134,6 @@ private async Task StartAsync(NamedPipeServerStream nextStream)
140134
}
141135
}
142136

143-
private NamedPipeServerStream GetOrCreateServerStream()
144-
{
145-
if (!_streamsCache.TryDequeue(out var stream))
146-
{
147-
// Cache is empty. Create a new server stream.
148-
stream = CreateServerStream();
149-
}
150-
151-
return stream;
152-
}
153-
154-
private NamedPipeServerStream CreateServerStream()
155-
{
156-
NamedPipeServerStream stream;
157-
var pipeOptions = NamedPipeOptions.Asynchronous | NamedPipeOptions.WriteThrough;
158-
if (_options.CurrentUserOnly)
159-
{
160-
pipeOptions |= NamedPipeOptions.CurrentUserOnly;
161-
}
162-
163-
if (_options.PipeSecurity != null)
164-
{
165-
stream = NamedPipeServerStreamAcl.Create(
166-
_endpoint.PipeName,
167-
PipeDirection.InOut,
168-
NamedPipeServerStream.MaxAllowedServerInstances,
169-
PipeTransmissionMode.Byte,
170-
pipeOptions,
171-
inBufferSize: 0, // Buffer in System.IO.Pipelines
172-
outBufferSize: 0, // Buffer in System.IO.Pipelines
173-
_options.PipeSecurity);
174-
}
175-
else
176-
{
177-
stream = new NamedPipeServerStream(
178-
_endpoint.PipeName,
179-
PipeDirection.InOut,
180-
NamedPipeServerStream.MaxAllowedServerInstances,
181-
PipeTransmissionMode.Byte,
182-
pipeOptions,
183-
inBufferSize: 0,
184-
outBufferSize: 0);
185-
}
186-
return stream;
187-
}
188-
189137
public async ValueTask<ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default)
190138
{
191139
while (await _acceptedQueue.Reader.WaitToReadAsync(cancellationToken))
@@ -217,5 +165,56 @@ public async ValueTask DisposeAsync()
217165
{
218166
await Task.WhenAll(_listeningTasks);
219167
}
168+
169+
// Dispose pool after listening tasks are complete so there is no chance a stream
170+
// is fetched from the pool after the pool is disposed.
171+
((IDisposable)_namedPipeServerStreamPool).Dispose();
172+
}
173+
174+
private sealed class NamedPipeServerStreamPoolPolicy : IPooledObjectPolicy<NamedPipeServerStream>
175+
{
176+
public NamedPipeConnectionListener _listener;
177+
178+
public NamedPipeServerStreamPoolPolicy(NamedPipeConnectionListener listener)
179+
{
180+
_listener = listener;
181+
}
182+
183+
public NamedPipeServerStream Create()
184+
{
185+
NamedPipeServerStream stream;
186+
var pipeOptions = NamedPipeOptions.Asynchronous | NamedPipeOptions.WriteThrough;
187+
if (_listener._options.CurrentUserOnly)
188+
{
189+
pipeOptions |= NamedPipeOptions.CurrentUserOnly;
190+
}
191+
192+
if (_listener._options.PipeSecurity != null)
193+
{
194+
stream = NamedPipeServerStreamAcl.Create(
195+
_listener._endpoint.PipeName,
196+
PipeDirection.InOut,
197+
NamedPipeServerStream.MaxAllowedServerInstances,
198+
PipeTransmissionMode.Byte,
199+
pipeOptions,
200+
inBufferSize: 0, // Buffer in System.IO.Pipelines
201+
outBufferSize: 0, // Buffer in System.IO.Pipelines
202+
_listener._options.PipeSecurity);
203+
}
204+
else
205+
{
206+
stream = new NamedPipeServerStream(
207+
_listener._endpoint.PipeName,
208+
PipeDirection.InOut,
209+
NamedPipeServerStream.MaxAllowedServerInstances,
210+
PipeTransmissionMode.Byte,
211+
pipeOptions,
212+
inBufferSize: 0,
213+
outBufferSize: 0);
214+
}
215+
return stream;
216+
}
217+
218+
public bool Return(NamedPipeServerStream obj) => true;
220219
}
221220
}

src/Servers/Kestrel/Transport.NamedPipes/src/Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<ItemGroup>
2727
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
2828
<Reference Include="Microsoft.AspNetCore.Connections.Abstractions" />
29+
<Reference Include="Microsoft.Extensions.ObjectPool" />
2930
<Reference Include="Microsoft.Extensions.Options" />
3031
</ItemGroup>
3132

0 commit comments

Comments
 (0)