public override Task <int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { if (buffer == null) { throw new ArgumentNullException("buffer"); } if (offset < 0) { throw new ArgumentOutOfRangeException("offset"); } if (count < 0) { throw new ArgumentOutOfRangeException("count"); } if (offset > buffer.Length - count) { throw new ArgumentException("buffer"); } CheckDisposed(); VerboseTrace("buffer: " + buffer.Length + ", offset: " + offset + ", count: " + count); // Check for cancellation if (cancellationToken.IsCancellationRequested) { VerboseTrace("Canceled"); return(Task.FromCanceled <int>(cancellationToken)); } lock (_lockObject) { VerifyInvariants(); // If there's currently a pending read, fail this read, as we don't support concurrent reads. if (_pendingReadRequest != null) { VerboseTrace("Existing pending read"); return(Task.FromException <int>(new InvalidOperationException(SR.net_http_content_no_concurrent_reads))); } // If the stream was already completed with failure, complete the read as a failure. if (_completed != null && _completed != s_completionSentinel) { VerboseTrace("Failing read with " + _completed); OperationCanceledException oce = _completed as OperationCanceledException; return((oce != null && oce.CancellationToken.IsCancellationRequested) ? Task.FromCanceled <int>(oce.CancellationToken) : Task.FromException <int>(_completed)); } // Quick check for if no data was actually requested. We do this after the check // for errors so that we can still fail the read and transfer the exception if we should. if (count == 0) { VerboseTrace("Zero count"); return(s_zeroTask); } // If there's any data left over from a previous call, grab as much as we can. if (_remainingDataCount > 0) { int bytesToCopy = Math.Min(count, _remainingDataCount); Array.Copy(_remainingData, _remainingDataOffset, buffer, offset, bytesToCopy); _remainingDataOffset += bytesToCopy; _remainingDataCount -= bytesToCopy; Debug.Assert(_remainingDataCount >= 0, "The remaining count should never go negative"); Debug.Assert(_remainingDataOffset <= _remainingData.Length, "The remaining offset should never exceed the buffer size"); VerboseTrace("Copied to task: " + bytesToCopy); return(Task.FromResult(bytesToCopy)); } // If the stream has already been completed, complete the read immediately. if (_completed == s_completionSentinel) { VerboseTrace("Completed successfully after stream completion"); return(s_zeroTask); } // Finally, the stream is still alive, and we want to read some data, but there's no data // in the buffer so we need to register ourself to get the next write. if (cancellationToken.CanBeCanceled) { // If the cancellation token is cancelable, then we need to register for cancellation. // We creat a special CancelableReadState that carries with it additional info: // the cancellation token and the registration with that token. When cancellation // is requested, we schedule a work item that tries to remove the read state // from being pending, canceling it in the process. This needs to happen under the // lock, which is why we schedule the operation to run asynchronously: if it ran // synchronously, it could deadlock due to code on another thread holding the lock // and calling Dispose on the registration concurrently with the call to Cancel // the cancellation token. Dispose on the registration won't return until the action // associated with the registration has completed, but if that action is currently // executing and is blocked on the lock that's held while calling Dispose... deadlock. var crs = new CancelableReadState(buffer, offset, count, this, cancellationToken); crs._registration = cancellationToken.Register(s1 => { ((CancelableReadState)s1)._stream.VerboseTrace("Cancellation invoked. Queueing work item to cancel read state."); Task.Factory.StartNew(s2 => { var crsRef = (CancelableReadState)s2; Debug.Assert(crsRef._token.IsCancellationRequested, "We should only be here if cancellation was requested."); lock (crsRef._stream._lockObject) { if (crsRef._stream._pendingReadRequest == crsRef) { crsRef.TrySetCanceled(crsRef._token); crsRef._stream.ClearPendingReadRequest(); } } }, s1, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); }, crs); _pendingReadRequest = crs; VerboseTrace("Created pending cancelable read"); } else { // The token isn't cancelable. Just create a normal read state. _pendingReadRequest = new ReadState(buffer, offset, count); VerboseTrace("Created pending read"); } _easy._associatedMultiAgent.RequestUnpause(_easy); return(_pendingReadRequest.Task); } }
public override ValueTask <int> ReadAsync(Memory <byte> buffer, CancellationToken cancellationToken = default) { CheckDisposed(); EventSourceTrace("Buffer: {0}", buffer.Length); // Check for cancellation if (cancellationToken.IsCancellationRequested) { EventSourceTrace("Canceled"); return(new ValueTask <int>(Task.FromCanceled <int>(cancellationToken))); } lock (_lockObject) { VerifyInvariants(); // If there's currently a pending read, fail this read, as we don't support concurrent reads. if (_pendingReadRequest != null) { EventSourceTrace("Failing due to existing pending read; concurrent reads not supported."); return(new ValueTask <int>(Task.FromException <int>(new InvalidOperationException(SR.net_http_content_no_concurrent_reads)))); } // If the stream was already completed with failure, complete the read as a failure. if (_completed != null && _completed != s_completionSentinel) { EventSourceTrace("Failing read with error: {0}", _completed); OperationCanceledException oce = _completed as OperationCanceledException; return(new ValueTask <int>((oce != null && oce.CancellationToken.IsCancellationRequested) ? Task.FromCanceled <int>(oce.CancellationToken) : Task.FromException <int>(MapToReadWriteIOException(_completed, isRead: true)))); } // Quick check for if no data was actually requested. We do this after the check // for errors so that we can still fail the read and transfer the exception if we should. if (buffer.Length == 0) { return(new ValueTask <int>(0)); } // If there's any data left over from a previous call, grab as much as we can. if (_remainingDataCount > 0) { int bytesToCopy = Math.Min(buffer.Length, _remainingDataCount); new Span <byte>(_remainingData, _remainingDataOffset, bytesToCopy).CopyTo(buffer.Span); _remainingDataOffset += bytesToCopy; _remainingDataCount -= bytesToCopy; Debug.Assert(_remainingDataCount >= 0, "The remaining count should never go negative"); Debug.Assert(_remainingDataOffset <= _remainingData.Length, "The remaining offset should never exceed the buffer size"); EventSourceTrace("Read {0} bytes", bytesToCopy); return(new ValueTask <int>(bytesToCopy)); } // If the stream has already been completed, complete the read immediately. if (_completed == s_completionSentinel) { EventSourceTrace("Stream already completed"); return(new ValueTask <int>(0)); } // Finally, the stream is still alive, and we want to read some data, but there's no data // in the buffer so we need to register ourself to get the next write. if (cancellationToken.CanBeCanceled) { // If the cancellation token is cancelable, then we need to register for cancellation. // We create a special CancelableReadState that carries with it additional info: // the cancellation token and the registration with that token. When cancellation // is requested, we schedule a work item that tries to remove the read state // from being pending, canceling it in the process. This needs to happen under the // lock, which is why we schedule the operation to run asynchronously: if it ran // synchronously, it could deadlock due to code on another thread holding the lock // and calling Dispose on the registration concurrently with the call to Cancel // the cancellation token. Dispose on the registration won't return until the action // associated with the registration has completed, but if that action is currently // executing and is blocked on the lock that's held while calling Dispose... deadlock. var crs = new CancelableReadState(buffer, this, cancellationToken); crs._registration = cancellationToken.Register(s1 => { ((CancelableReadState)s1)._stream.EventSourceTrace("Cancellation invoked. Queueing work item to cancel read state"); Task.Factory.StartNew(s2 => { var crsRef = (CancelableReadState)s2; lock (crsRef._stream._lockObject) { Debug.Assert(crsRef._token.IsCancellationRequested, "We should only be here if cancellation was requested."); if (crsRef._stream._pendingReadRequest == crsRef) { crsRef._stream.EventSourceTrace("Canceling"); crsRef.TrySetCanceled(crsRef._token); crsRef._stream.ClearPendingReadRequest(); } } }, s1, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); }, crs); _pendingReadRequest = crs; } else { // The token isn't cancelable. Just create a normal read state. _pendingReadRequest = new ReadState(buffer); } _easy._associatedMultiAgent.RequestUnpause(_easy); _easy._selfStrongToWeakReference.MakeStrong(); // convert from a weak to a strong ref to keep the easy alive during the read return(new ValueTask <int>(_pendingReadRequest.Task)); } }