/// <summary> /// Starts a background operation. This causes the <see cref="internalBuffer"/> to fill or flush. /// It must be ensured that no other background operation will be in progress, and that the stream is still usable after it. /// - First start an operation with <see cref="StartNewOperation"/>; this reserves the right to start the next background operation. /// - Then, wait for the current background operation, if any. We've established another one will not follow it due to the prior point. /// - Then, respond to the current stream usability (EOF, broken, etc.) as updated by the prior operation. /// </summary> private unsafe void StartBackgroundOperation(BackgroundOperationSlot slot, StreamBackgroundOperation nextOperation) { Contract.Requires(nextOperation != StreamBackgroundOperation.None); Analysis.IgnoreArgument(slot); lock (m_backgroundOperationLock) { if (m_usability != StreamUsability.Usable) { Contract.Assume(false, "Attempting to start a background operation on an unusable stream: " + m_usability.ToString("G")); } Contract.Assume( m_currentBackgroundOperation == StreamBackgroundOperation.None, "Background operation already in progress; wait on it first?"); Contract.Assume(m_currentBackgroundOperationCompletionSource == null); m_currentBackgroundOperation = nextOperation; } // Now actually start the async operation. // Note that the callback to 'this' (IIOCompletionTarget) can happen on this same stack byte *pinnedBuffer; int operationLength; switch (nextOperation) { case StreamBackgroundOperation.Fill: internalBuffer.LockForFill(out pinnedBuffer, out operationLength); m_file.ReadOverlapped(this, pinnedBuffer, operationLength, m_bufferPosition); break; case StreamBackgroundOperation.Flush: internalBuffer.LockForFlush(out pinnedBuffer, out operationLength); m_file.WriteOverlapped(this, pinnedBuffer, operationLength, m_bufferPosition); break; default: throw Contract.AssertFailure("Unhandled StreamBackgroundOperation"); } }
/// <summary> /// Starts a background operation. No background operations may be in progress; /// ensure that is not the case by waiting for background operation completion if one may be in progress. /// </summary> public void StartBackgroundOperation(StreamBackgroundOperation nextOperation) => m_stream.StartBackgroundOperation(this, nextOperation);
void IIOCompletionTarget.OnCompletion(FileAsyncIOResult result) { // Note that this may be called on the same stack as StartBackgroundOperation (sync completion), Contract.Assume(result.Status != FileAsyncIOStatus.Pending); bool failed = result.Status == FileAsyncIOStatus.Failed; if (!failed) { // Note, that FileAsyncIOResult constructor can't enforce this check because in some other cases // 0 transfered bytes is ok for a successful IO operation. Contract.Assert(result.BytesTransferred > 0, "Zero bytes transferred is a failure indication (otherwise can be confused with EOF)"); } TaskCompletionSource <BackgroundOperationSlot> completionSource; lock (m_backgroundOperationLock) { // Capture the completion source to use outside of the lock. completionSource = m_currentBackgroundOperationCompletionSource; switch (m_currentBackgroundOperation) { case StreamBackgroundOperation.Fill: m_bufferPosition += result.BytesTransferred; internalBuffer.FinishFillAndUnlock(numberOfBytesFilled: result.BytesTransferred); break; case StreamBackgroundOperation.Flush: m_bufferPosition += result.BytesTransferred; internalBuffer.FinishFlushAndUnlock(numberOfBytesFlushed: result.BytesTransferred); break; case StreamBackgroundOperation.None: Contract.Assume(false, "Unexpected I/O completion (no background operation in progress)"); throw new InvalidOperationException("Unreachable"); default: throw Contract.AssertFailure("Unhandled StreamBackgroundOperation"); } if (failed) { StreamUsability newUsability; if (result.ErrorIndicatesEndOfFile) { newUsability = StreamUsability.EndOfFileReached; } else { newUsability = StreamUsability.Broken; m_brokenStreamException = new IOException( "An error occurred while reading or writing to a file stream. The stream will no longer be usable.", new NativeWin32Exception(result.Error)); } m_usability = newUsability; } else { Contract.Assume(m_usability == StreamUsability.Usable); } m_currentBackgroundOperation = StreamBackgroundOperation.None; m_currentBackgroundOperationCompletionSource = null; } // Since the lock is no longer held, it is safe to resume any waiters (note that they may run on this stack). if (completionSource != null) { completionSource.SetResult(new BackgroundOperationSlot(this)); } }