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(); } } } }
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) { const int CancellationSentinel = -42; int rv = (int)SysCall(_handle, (fd, ptr, len, cancellationFd) => { // 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.PollFD *fds = stackalloc Interop.Sys.PollFD[2]; fds[0] = new Interop.Sys.PollFD { FD = fd, Events = Interop.Sys.PollFlags.POLLIN, REvents = 0 }; fds[1] = new Interop.Sys.PollFD { FD = (int)cancellationFd, Events = Interop.Sys.PollFlags.POLLIN, REvents = 0 }; // 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. int signaledFdCount; while (Interop.CheckIo(signaledFdCount = Interop.Sys.Poll(fds, 2, timeout))) { ; } if (cancellationToken.IsCancellationRequested) { // Cancellation occurred. Bail by returning the cancellation sentinel. return(CancellationSentinel); } else if (signaledFdCount == 0) { // Timeout occurred. Loop around to poll again. continue; } else { // Our pipe is ready. Break out of the loop to read from it. Debug.Assert((fds[0].REvents & Interop.Sys.PollFlags.POLLIN) != 0, "Expected revents on read fd to have POLLIN set"); break; } } // Read it. Debug.Assert((fds[0].REvents & Interop.Sys.PollFlags.POLLIN) != 0); int result = Interop.Sys.Read(fd, (byte *)ptr, len); Debug.Assert(result <= len); return(result); }, (IntPtr)(bufPtr + offset), count, cancellation.Poll.DangerousGetHandle()); Debug.Assert(rv >= 0 || rv == CancellationSentinel); // If cancellation was requested, waking up the read, throw. if (rv == CancellationSentinel) { Debug.Assert(cancellationToken.IsCancellationRequested); throw new OperationCanceledException(cancellationToken); } // Otherwise return what we read. return(rv); } } finally { if (gotRef) { cancellation.Poll.DangerousRelease(); } } } }
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) { const int CancellationSentinel = -42; int rv = (int)SysCall(_handle, (fd, ptr, len, cancellationFd) => { // 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.libc.pollfd *fds = stackalloc Interop.libc.pollfd[2]; fds[0] = new Interop.libc.pollfd { fd = fd, events = Interop.libc.PollFlags.POLLIN, revents = 0 }; fds[1] = new Interop.libc.pollfd { fd = (int)cancellationFd, events = Interop.libc.PollFlags.POLLIN, revents = 0 }; while (Interop.CheckIo(Interop.libc.poll(fds, 2, -1))) { ; } // If we woke up because of a cancellation request, bail. if ((fds[1].revents & Interop.libc.PollFlags.POLLIN) != 0) { return(CancellationSentinel); } // Otherwise, we woke up because data is available on the pipe. Read it. Debug.Assert((fds[0].revents & Interop.libc.PollFlags.POLLIN) != 0); long result = (long)Interop.libc.read(fd, (byte *)ptr, (IntPtr)len); Debug.Assert(result <= len); return(result); }, (IntPtr)(bufPtr + offset), count, cancellation.Poll.DangerousGetHandle()); Debug.Assert(rv >= 0 || rv == CancellationSentinel); // If cancellation was requested, waking up the read, throw. if (rv == CancellationSentinel) { Debug.Assert(cancellationToken.IsCancellationRequested); throw new OperationCanceledException(cancellationToken); } // Otherwise return what we read. return(rv); } } finally { if (gotRef) { cancellation.Poll.DangerousRelease(); } } } }