Skip to content

Commit 80753ea

Browse files
committed
Added comments and decrease GC pressure
Use arrays for O(1) access
1 parent 1f97195 commit 80753ea

1 file changed

Lines changed: 63 additions & 18 deletions

File tree

src/FubarDev.FtpServer/MultiBindingTcpListener.cs

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using System;
66
using System.Collections.Generic;
7-
using System.Diagnostics;
87
using System.Linq;
98
using System.Net;
109
using System.Net.Sockets;
@@ -21,12 +20,10 @@ namespace FubarDev.FtpServer
2120
public class MultiBindingTcpListener
2221
{
2322
private readonly string? _address;
24-
2523
private readonly int _port;
26-
2724
private readonly ILogger? _logger;
28-
private readonly IList<TcpListener> _listeners = new List<TcpListener>();
29-
private readonly IList<Task<TcpClient?>> _acceptors = new List<Task<TcpClient?>>();
25+
private Task<AcceptInfo>[] _acceptors = Array.Empty<Task<AcceptInfo>>();
26+
private TcpListener[] _listeners = Array.Empty<TcpListener>();
3027

3128
/// <summary>
3229
/// Initializes a new instance of the <see cref="MultiBindingTcpListener"/> class.
@@ -107,8 +104,8 @@ public void Stop()
107104
listener.Stop();
108105
}
109106

110-
_listeners.Clear();
111-
_acceptors.Clear();
107+
_listeners = Array.Empty<TcpListener>();
108+
_acceptors = Array.Empty<Task<AcceptInfo>>();
112109
Port = 0;
113110
_logger?.LogInformation("Listener stopped");
114111
}
@@ -120,46 +117,80 @@ public void Stop()
120117
/// <returns>The new TCP client.</returns>
121118
public async Task<TcpClient> WaitAnyTcpClientAsync(CancellationToken token)
122119
{
120+
// The task that just waits indefinitely for a triggered cancellation token
121+
var cancellationTask = Task.Delay(-1, token);
122+
123+
// Build the list of awaitable tasks
124+
var tasks = new Task[_acceptors.Length + 1];
125+
Array.Copy(_acceptors, tasks, _acceptors.Length);
126+
127+
// Add the cancellation task as last task
128+
tasks[_acceptors.Length] = cancellationTask;
129+
123130
TcpClient? result;
124131
do
125132
{
126-
var tasks = _acceptors.Cast<Task>().ToList();
127-
tasks.Add(Task.Delay(-1, token));
133+
// Wait for any task to be finished
128134
var retVal = await Task.WhenAny(tasks).ConfigureAwait(false);
135+
136+
// Test if the cancellation token was triggered
129137
token.ThrowIfCancellationRequested();
130-
var index = tasks.IndexOf(retVal);
131-
_acceptors[index] = _listeners[index].AcceptTcpClientAsync();
132-
result = ((Task<TcpClient?>)retVal).Result;
138+
139+
// It was a listener task when the cancellation token was not triggered
140+
#if NETSTANDARD1_3
141+
var acceptInfo = await ((Task<AcceptInfo>)retVal).ConfigureAwait(false);
142+
#else
143+
var acceptInfo = ((Task<AcceptInfo>)retVal).Result;
144+
retVal.Dispose();
145+
#endif
146+
147+
// Avoid indexed access into the list of acceptors
148+
var index = acceptInfo.Index;
149+
150+
// Gets the result of the finished task.
151+
result = acceptInfo.Client;
152+
153+
// Start accepting the next TCP client for the
154+
// listener whose task was finished.
155+
var listener = _listeners[index];
156+
var newAcceptor = AcceptForListenerAsync(listener, index);
157+
158+
// Start accepting the next TCP client for the
159+
// listener whose task was finished.
160+
tasks[index] = _acceptors[index] = newAcceptor;
133161
}
134162
while (result == null);
135163

136164
return result;
137165
}
138166

139167
/// <summary>
140-
/// Start the asynchronous acception for all listeners.
168+
/// Start the asynchronous accept operation for all listeners.
141169
/// </summary>
142170
public void StartAccepting()
143171
{
144-
_listeners.ToList().ForEach(x => _acceptors.Add(AcceptForListenerAsync(x)));
172+
_acceptors = _listeners.Select(AcceptForListenerAsync).ToArray();
145173
}
146174

147-
private async Task<TcpClient?> AcceptForListenerAsync(TcpListener listener)
175+
private async Task<AcceptInfo> AcceptForListenerAsync(TcpListener listener, int index)
148176
{
149177
try
150178
{
151-
return await listener.AcceptTcpClientAsync().ConfigureAwait(false);
179+
var client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
180+
return new AcceptInfo(client, index);
152181
}
153182
catch (ObjectDisposedException)
154183
{
155184
// Ignore the exception. This happens when the listener gets stopped.
156-
return null;
185+
return new AcceptInfo(null, index);
157186
}
158187
}
159188

160189
private int StartListening(IEnumerable<IPAddress> addresses, int port)
161190
{
162191
var selectedPort = port;
192+
193+
var listeners = new List<TcpListener>();
163194
foreach (var address in addresses)
164195
{
165196
var listener = new TcpListener(address, selectedPort);
@@ -172,10 +203,24 @@ private int StartListening(IEnumerable<IPAddress> addresses, int port)
172203

173204
_logger?.LogInformation("Started listening on {address}:{port}", address, selectedPort);
174205

175-
_listeners.Add(listener);
206+
listeners.Add(listener);
176207
}
177208

209+
_listeners = listeners.ToArray();
210+
178211
return selectedPort;
179212
}
213+
214+
private struct AcceptInfo
215+
{
216+
public AcceptInfo(TcpClient? client, int index)
217+
{
218+
Client = client;
219+
Index = index;
220+
}
221+
222+
public TcpClient? Client { get; }
223+
public int Index { get; }
224+
}
180225
}
181226
}

0 commit comments

Comments
 (0)