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