1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Threading;
8 using System.Threading.Tasks;
9 
10 using Xunit;
11 using Xunit.Abstractions;
12 
13 namespace System.Net.Sockets.Tests
14 {
15     public class SelectTest
16     {
17         private readonly ITestOutputHelper _log;
18 
SelectTest(ITestOutputHelper output)19         public SelectTest(ITestOutputHelper output)
20         {
21             _log = output;
22         }
23 
24         private const int SmallTimeoutMicroseconds = 10 * 1000;
25         private const int FailTimeoutMicroseconds  = 30 * 1000 * 1000;
26 
27         [PlatformSpecific(~TestPlatforms.OSX)] // typical OSX install has very low max open file descriptors value
28         [Theory]
29         [InlineData(90, 0)]
30         [InlineData(0, 90)]
31         [InlineData(45, 45)]
Select_ReadWrite_AllReady_ManySockets(int reads, int writes)32         public void Select_ReadWrite_AllReady_ManySockets(int reads, int writes)
33         {
34             Select_ReadWrite_AllReady(reads, writes);
35         }
36 
37         [Theory]
38         [InlineData(1, 0)]
39         [InlineData(0, 1)]
40         [InlineData(2, 2)]
Select_ReadWrite_AllReady(int reads, int writes)41         public void Select_ReadWrite_AllReady(int reads, int writes)
42         {
43             var readPairs = Enumerable.Range(0, reads).Select(_ => CreateConnectedSockets()).ToArray();
44             var writePairs = Enumerable.Range(0, writes).Select(_ => CreateConnectedSockets()).ToArray();
45             try
46             {
47                 foreach (var pair in readPairs)
48                 {
49                     pair.Value.Send(new byte[1] { 42 });
50                 }
51 
52                 var readList = new List<Socket>(readPairs.Select(p => p.Key).ToArray());
53                 var writeList = new List<Socket>(writePairs.Select(p => p.Key).ToArray());
54 
55                 Socket.Select(readList, writeList, null, -1); // using -1 to test wait code path, but should complete instantly
56 
57                 // Since no buffers are full, all writes should be available.
58                 Assert.Equal(writePairs.Length, writeList.Count);
59 
60                 // We could wake up from Select for writes even if reads are about to become available,
61                 // so there's very little we can assert if writes is non-zero.
62                 if (writes == 0 && reads > 0)
63                 {
64                     Assert.InRange(readList.Count, 1, readPairs.Length);
65                 }
66 
67                 // When we do the select again, the lists shouldn't change at all, as they've already
68                 // been filtered to ones that were ready.
69                 int readListCountBefore = readList.Count;
70                 int writeListCountBefore = writeList.Count;
71                 Socket.Select(readList, writeList, null, FailTimeoutMicroseconds);
72                 Assert.Equal(readListCountBefore, readList.Count);
73                 Assert.Equal(writeListCountBefore, writeList.Count);
74             }
75             finally
76             {
77                 DisposeSockets(readPairs);
78                 DisposeSockets(writePairs);
79             }
80         }
81 
82         [PlatformSpecific(~TestPlatforms.OSX)] // typical OSX install has very low max open file descriptors value
83         [Fact]
Select_ReadError_NoneReady_ManySockets()84         public void Select_ReadError_NoneReady_ManySockets()
85         {
86             Select_ReadError_NoneReady(45, 45);
87         }
88 
89         [Theory]
90         [InlineData(1, 0)]
91         [InlineData(0, 1)]
92         [InlineData(2, 2)]
Select_ReadError_NoneReady(int reads, int errors)93         public void Select_ReadError_NoneReady(int reads, int errors)
94         {
95             var readPairs = Enumerable.Range(0, reads).Select(_ => CreateConnectedSockets()).ToArray();
96             var errorPairs = Enumerable.Range(0, errors).Select(_ => CreateConnectedSockets()).ToArray();
97             try
98             {
99                 var readList = new List<Socket>(readPairs.Select(p => p.Key).ToArray());
100                 var errorList = new List<Socket>(errorPairs.Select(p => p.Key).ToArray());
101 
102                 Socket.Select(readList, null, errorList, SmallTimeoutMicroseconds);
103 
104                 Assert.Empty(readList);
105                 Assert.Empty(errorList);
106             }
107             finally
108             {
109                 DisposeSockets(readPairs);
110                 DisposeSockets(errorPairs);
111             }
112         }
113 
114         [PlatformSpecific(~TestPlatforms.OSX)] // typical OSX install has very low max open file descriptors value
Select_Read_OneReadyAtATime_ManySockets(int reads)115         public void Select_Read_OneReadyAtATime_ManySockets(int reads)
116         {
117             Select_Read_OneReadyAtATime(90); // value larger than the internal value in SocketPal.Unix that swaps between stack and heap allocation
118         }
119 
120         [Theory]
121         [InlineData(2)]
Select_Read_OneReadyAtATime(int reads)122         public void Select_Read_OneReadyAtATime(int reads)
123         {
124             var rand = new Random(42);
125             var readPairs = Enumerable.Range(0, reads).Select(_ => CreateConnectedSockets()).ToList();
126             try
127             {
128                 while (readPairs.Count > 0)
129                 {
130                     int next = rand.Next(0, readPairs.Count);
131                     readPairs[next].Value.Send(new byte[1] { 42 });
132 
133                     var readList = new List<Socket>(readPairs.Select(p => p.Key).ToArray());
134                     Socket.Select(readList, null, null, FailTimeoutMicroseconds);
135 
136                     Assert.Equal(1, readList.Count);
137                     Assert.Same(readPairs[next].Key, readList[0]);
138 
139                     readPairs.RemoveAt(next);
140                 }
141             }
142             finally
143             {
144                 DisposeSockets(readPairs);
145             }
146         }
147 
148         [PlatformSpecific(~TestPlatforms.OSX)] // typical OSX install has very low max open file descriptors value
149         [Fact]
Select_Error_OneReadyAtATime()150         public void Select_Error_OneReadyAtATime()
151         {
152             const int Errors = 90; // value larger than the internal value in SocketPal.Unix that swaps between stack and heap allocation
153             var rand = new Random(42);
154             var errorPairs = Enumerable.Range(0, Errors).Select(_ => CreateConnectedSockets()).ToList();
155             try
156             {
157                 while (errorPairs.Count > 0)
158                 {
159                     int next = rand.Next(0, errorPairs.Count);
160                     errorPairs[next].Value.Send(new byte[1] { 42 }, SocketFlags.OutOfBand);
161 
162                     var errorList = new List<Socket>(errorPairs.Select(p => p.Key).ToArray());
163                     Socket.Select(null, null, errorList, FailTimeoutMicroseconds);
164 
165                     Assert.Equal(1, errorList.Count);
166                     Assert.Same(errorPairs[next].Key, errorList[0]);
167 
168                     errorPairs.RemoveAt(next);
169                 }
170             }
171             finally
172             {
173                 DisposeSockets(errorPairs);
174             }
175         }
176 
177         [Theory]
178         [InlineData(SelectMode.SelectRead)]
179         [InlineData(SelectMode.SelectError)]
Poll_NotReady(SelectMode mode)180         public void Poll_NotReady(SelectMode mode)
181         {
182             KeyValuePair<Socket, Socket> pair = CreateConnectedSockets();
183             try
184             {
185                 Assert.False(pair.Key.Poll(SmallTimeoutMicroseconds, mode));
186             }
187             finally
188             {
189                 pair.Key.Dispose();
190                 pair.Value.Dispose();
191             }
192         }
193 
194         [Theory]
195         [InlineData(-1)]
196         [InlineData(FailTimeoutMicroseconds)]
Poll_ReadReady_LongTimeouts(int microsecondsTimeout)197         public void Poll_ReadReady_LongTimeouts(int microsecondsTimeout)
198         {
199             KeyValuePair<Socket, Socket> pair = CreateConnectedSockets();
200             try
201             {
202                 Task.Delay(1).ContinueWith(_ => pair.Value.Send(new byte[1] { 42 }),
203                     CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
204 
205                 Assert.True(pair.Key.Poll(microsecondsTimeout, SelectMode.SelectRead));
206             }
207             finally
208             {
209                 pair.Key.Dispose();
210                 pair.Value.Dispose();
211             }
212         }
213 
CreateConnectedSockets()214         private static KeyValuePair<Socket, Socket> CreateConnectedSockets()
215         {
216             using (Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
217             {
218                 listener.LingerState = new LingerOption(true, 0);
219                 listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
220                 listener.Listen(1);
221 
222                 Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
223                 client.LingerState = new LingerOption(true, 0);
224 
225                 Task<Socket> acceptTask = listener.AcceptAsync();
226                 client.Connect(listener.LocalEndPoint);
227                 Socket server = acceptTask.GetAwaiter().GetResult();
228 
229                 return new KeyValuePair<Socket, Socket>(client, server);
230             }
231         }
232 
DisposeSockets(IEnumerable<KeyValuePair<Socket, Socket>> sockets)233         private static void DisposeSockets(IEnumerable<KeyValuePair<Socket, Socket>> sockets)
234         {
235             foreach (var pair in sockets)
236             {
237                 pair.Key.Dispose();
238                 pair.Value.Dispose();
239             }
240         }
241 
242         [OuterLoop]
243         [Fact]
Select_AcceptNonBlocking_Success()244         public static async Task Select_AcceptNonBlocking_Success()
245         {
246             using (Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
247             {
248                 int port = listenSocket.BindToAnonymousPort(IPAddress.Loopback);
249 
250                 listenSocket.Blocking = false;
251 
252                 listenSocket.Listen(5);
253 
254                 Task t = Task.Run(() => { DoAccept(listenSocket, 5); });
255 
256                 // Loop, doing connections and pausing between
257                 for (int i = 0; i < 5; i++)
258                 {
259                     Thread.Sleep(50);
260                     using (Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
261                     {
262                         connectSocket.Connect(listenSocket.LocalEndPoint);
263                     }
264                 }
265 
266                 // Give the task 5 seconds to complete; if not, assume it's hung.
267                 await t.TimeoutAfter(5000);
268             }
269         }
270 
DoAccept(Socket listenSocket, int connectionsToAccept)271         public static void DoAccept(Socket listenSocket, int connectionsToAccept)
272         {
273             int connectionCount = 0;
274             while (true)
275             {
276                 var ls = new List<Socket> { listenSocket };
277                 Socket.Select(ls, null, null, 1000000);
278                 if (ls.Count > 0)
279                 {
280                     while (true)
281                     {
282                         try
283                         {
284                             Socket s = listenSocket.Accept();
285                             s.Close();
286                             connectionCount++;
287                         }
288                         catch (SocketException e)
289                         {
290                             Assert.Equal(e.SocketErrorCode, SocketError.WouldBlock);
291 
292                             //No more requests in queue
293                             break;
294                         }
295 
296                         if (connectionCount == connectionsToAccept)
297                         {
298                             return;
299                         }
300                     }
301                 }
302             }
303         }
304     }
305 }
306