private static unsafe void AddToPollArray(Interop.Sys.PollEvent* arr, int arrLength, IList socketList, ref int arrOffset, Interop.Sys.PollEvents events) { if (socketList == null) return; int listCount = socketList.Count; for (int i = 0; i < listCount; i++) { if (arrOffset >= arrLength) { Debug.Fail("IList.Count must have been faulty, returning a negative value and/or returning a different value across calls."); throw new ArgumentOutOfRangeException(nameof(socketList)); } Socket socket = socketList[i] as Socket; if (socket == null) { throw new ArgumentException(SR.Format(SR.net_sockets_select, socket?.GetType().FullName ?? "null", typeof(Socket).FullName)); } int fd = (int)socket.SafeHandle.DangerousGetHandle(); arr[arrOffset++] = new Interop.Sys.PollEvent { Events = events, FileDescriptor = fd }; } }
private unsafe int ReadCoreWithCancellation(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { DebugAssertReadWriteArgs(buffer, offset, count, _handle); Debug.Assert(cancellationToken.CanBeCanceled, "ReadCoreNoCancellation should be used if cancellation can't happen"); // Register for a cancellation request. This will throw if cancellation has already been requested, // and otherwise will write to the cancellation pipe if/when cancellation has been requested. using (DescriptorCancellationRegistration cancellation = RegisterForCancellation(cancellationToken)) { bool gotRef = false; try { cancellation.Poll.DangerousAddRef(ref gotRef); fixed(byte *bufPtr = buffer) { // We want to wait for data to be available on either the pipe we want to read from // or on the cancellation pipe, which would signal a cancellation request. Interop.Sys.PollEvent *events = stackalloc Interop.Sys.PollEvent[2]; events[0] = new Interop.Sys.PollEvent { FileDescriptor = (int)_handle.DangerousGetHandle(), Events = Interop.Sys.PollEvents.POLLIN }; events[1] = new Interop.Sys.PollEvent { FileDescriptor = (int)cancellation.Poll.DangerousGetHandle(), Events = Interop.Sys.PollEvents.POLLIN }; // Some systems (at least OS X) appear to have a race condition in poll with FIFOs where the poll can // end up not noticing writes of greater than the internal buffer size. Restarting the poll causes it // to notice. To deal with that, we loop around poll, first starting with a small timeout and backing off // to a larger one. This ensures we'll at least eventually notice such changes in these corner // cases, while not adding too much overhead on systems that don't suffer from the problem. const int InitialMsTimeout = 30, MaxMsTimeout = 2000; for (int timeout = InitialMsTimeout; ; timeout = Math.Min(timeout * 2, MaxMsTimeout)) { // Do the poll. uint signaledFdCount; Interop.CheckIo(Interop.Sys.Poll(events, 2, timeout, &signaledFdCount)); cancellationToken.ThrowIfCancellationRequested(); if (signaledFdCount != 0) { // Our pipe is ready. Break out of the loop to read from it. The fd may have been signaled due to // POLLIN (data available), POLLHUP (hang-up), POLLERR (some error on the stream), etc... any such // data will be propagated to us when we do the actual read. break; } } // Read it. int result = CheckPipeCall(Interop.Sys.Read(_handle, bufPtr + offset, count)); Debug.Assert(result >= 0 && result <= count, "Expected 0 <= result <= count bytes, got " + result); // return what we read. return(result); } } finally { if (gotRef) { cancellation.Poll.DangerousRelease(); } } } }
public static unsafe SocketError Select(IList checkRead, IList checkWrite, IList checkError, int microseconds) { int checkReadInitialCount = checkRead != null ? checkRead.Count : 0; int checkWriteInitialCount = checkWrite != null ? checkWrite.Count : 0; int checkErrorInitialCount = checkError != null ? checkError.Count : 0; int count = checked(checkReadInitialCount + checkWriteInitialCount + checkErrorInitialCount); Debug.Assert(count > 0, $"Expected at least one entry."); // Rather than using the select syscall, we use poll. While this has a mismatch in API from Select and // requires some translation, it avoids the significant limitation of select only working with file descriptors // less than FD_SETSIZE, and thus failing arbitrarily depending on the file descriptor value assigned // by the system. Since poll then expects an array of entries, we try to allocate the array on the stack, // only falling back to allocating it on the heap if it's deemed too big. const int StackThreshold = 80; // arbitrary limit to avoid too much space on stack if (count < StackThreshold) { Interop.Sys.PollEvent* eventsOnStack = stackalloc Interop.Sys.PollEvent[count]; return SelectViaPoll( checkRead, checkReadInitialCount, checkWrite, checkWriteInitialCount, checkError, checkErrorInitialCount, eventsOnStack, count, microseconds); } else { var eventsOnHeap = new Interop.Sys.PollEvent[count]; fixed (Interop.Sys.PollEvent* eventsOnHeapPtr = eventsOnHeap) { return SelectViaPoll( checkRead, checkReadInitialCount, checkWrite, checkWriteInitialCount, checkError, checkErrorInitialCount, eventsOnHeapPtr, count, microseconds); } } }
private unsafe int ReadCoreWithCancellation(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { DebugAssertReadWriteArgs(buffer, offset, count, _handle); Debug.Assert(cancellationToken.CanBeCanceled, "ReadCoreNoCancellation should be used if cancellation can't happen"); // Register for a cancellation request. This will throw if cancellation has already been requested, // and otherwise will write to the cancellation pipe if/when cancellation has been requested. using (DescriptorCancellationRegistration cancellation = RegisterForCancellation(cancellationToken)) { bool gotRef = false; try { cancellation.Poll.DangerousAddRef(ref gotRef); fixed (byte* bufPtr = buffer) { // We want to wait for data to be available on either the pipe we want to read from // or on the cancellation pipe, which would signal a cancellation request. Interop.Sys.PollEvent* events = stackalloc Interop.Sys.PollEvent[2]; events[0] = new Interop.Sys.PollEvent { FileDescriptor = (int)_handle.DangerousGetHandle(), Events = Interop.Sys.PollEvents.POLLIN }; events[1] = new Interop.Sys.PollEvent { FileDescriptor = (int)cancellation.Poll.DangerousGetHandle(), Events = Interop.Sys.PollEvents.POLLIN }; // Some systems (at least OS X) appear to have a race condition in poll with FIFOs where the poll can // end up not noticing writes of greater than the internal buffer size. Restarting the poll causes it // to notice. To deal with that, we loop around poll, first starting with a small timeout and backing off // to a larger one. This ensures we'll at least eventually notice such changes in these corner // cases, while not adding too much overhead on systems that don't suffer from the problem. const int InitialMsTimeout = 30, MaxMsTimeout = 2000; for (int timeout = InitialMsTimeout; ; timeout = Math.Min(timeout * 2, MaxMsTimeout)) { // Do the poll. uint signaledFdCount; Interop.CheckIo(Interop.Sys.Poll(events, 2, timeout, &signaledFdCount)); cancellationToken.ThrowIfCancellationRequested(); if (signaledFdCount != 0) { // Our pipe is ready. Break out of the loop to read from it. Debug.Assert((events[0].TriggeredEvents & Interop.Sys.PollEvents.POLLIN) != 0, "Expected revents on read fd to have POLLIN set"); break; } } // Read it. Debug.Assert((events[0].TriggeredEvents & Interop.Sys.PollEvents.POLLIN) != 0); int result = CheckPipeCall(Interop.Sys.Read(_handle, bufPtr + offset, count)); Debug.Assert(result <= count); Debug.Assert(result >= 0); // return what we read. return result; } } finally { if (gotRef) cancellation.Poll.DangerousRelease(); } } }