/// <inheritdoc /> protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { using (StreamOperationToken op = StartNewOperation(StreamOperation.Dispose)) { BackgroundOperationSlot slot = op.WaitForBackgroundOperationSlot(); if (slot.Usability != StreamUsability.Broken) { FlushOrDiscardBufferAsync(slot).GetAwaiter().GetResult(); } if (m_ownsFile) { m_file.Close(); } if (slot.Usability == StreamUsability.Broken) { throw slot.ThrowExceptionForBrokenStream(); } } } // TODO: Consider FailFast for the !disposing (finalizer) case. internalBuffer.Dispose(); }
/// <summary> /// Adjusts the current stream position based on having read or written data in the buffer. /// </summary> protected void AdvancePosition(StreamOperationToken token, int advance) { Contract.Requires(advance >= 0); Analysis.IgnoreArgument(token); m_position += advance; Contract.Assume(m_position >= 0); }
/// <inheritdoc /> public override async Task <int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { using (StreamOperationToken op = StartNewOperation(StreamOperation.Read)) { BackgroundOperationSlot backgroundOperationSlot = await op.WaitForBackgroundOperationSlotAsync(); Contract.Assume( internalBuffer.State != FileBuffer.BufferState.Locked, "Buffer should not be locked, since no background operation is running."); int bytesReadFromBuffer; bool fillStarted = ReadBufferAndStartFillIfEmptiedButUsable(backgroundOperationSlot, buffer, offset, count, out bytesReadFromBuffer); if (bytesReadFromBuffer == 0) { if (fillStarted) { backgroundOperationSlot = await op.WaitForBackgroundOperationSlotAsync(); } // We just ran a fill and waited for it (usability may be updated on its completion) or we were unable to start a fill. // In either case, we should now respond to usability. We don't have any bytes read, and so we are now in some sense at the position // where the unusability occurs, rather than behind it (e.g. we should quietly exhaust the buffer before complaining about EOF). switch (backgroundOperationSlot.Usability) { case StreamUsability.Usable: Contract.Assume( fillStarted, "ReadBufferAndStartFillIfEmptiedButUsable should have started a fill, since the stream is usable"); Analysis.IgnoreResult( ReadBufferAndStartFillIfEmptiedButUsable(backgroundOperationSlot, buffer, offset, count, out bytesReadFromBuffer)); Contract.Assume( bytesReadFromBuffer > 0, "Usable stream implies that the completed fill obtained bytes. Zero bytes returned from ReadFile implies failure."); break; case StreamUsability.EndOfFileReached: Contract.Assume( internalBuffer.State == FileBuffer.BufferState.Empty, "EndOfFileReached usability coincides with a totally-failed fill (nothing read)"); break; case StreamUsability.Broken: throw backgroundOperationSlot.ThrowExceptionForBrokenStream(); default: throw Contract.AssertFailure("Unhandled StreamUsability"); } } // We've satisfied a read request for 'bytesReadFromBuffer' bytes, which advances our virtual file position. op.AdvancePosition(bytesReadFromBuffer); return(bytesReadFromBuffer); } }
/// <summary> /// <see cref="Stream.Flush"/> /// </summary> public override void Flush() { using (StreamOperationToken op = StartNewOperation(StreamOperation.Flush)) { BackgroundOperationSlot slot = op.WaitForBackgroundOperationSlot(); if (slot.Usability == StreamUsability.Broken) { throw slot.ThrowExceptionForBrokenStream(); } // TODO: Shouldn't drop the read buffer unless seeking to a new position. FlushOrDiscardBufferAndResetPositionAsync(op, slot, m_position).GetAwaiter().GetResult(); } }
/// <summary> /// Attempts to complete the current operation. This is atomic to <see cref="StreamOperation.None"/>. /// </summary> private void CompleteOperation(StreamOperationToken token) { Analysis.IgnoreArgument(token); lock (m_operationLock) { if (m_currentOperation == StreamOperation.None) { Contract.Assume(false, "Attempted to complete an operation, but no operation was in progress"); } // Dispose is terminal. if (m_currentOperation != StreamOperation.Dispose) { m_currentOperation = StreamOperation.None; } } }
/// <summary> /// Waits for the current background operation to complete, if any. /// </summary> protected Task <BackgroundOperationSlot> WaitForBackgroundOperationSlotAsync(StreamOperationToken token) { Analysis.IgnoreArgument(token); lock (m_backgroundOperationLock) { if (m_currentBackgroundOperation == StreamBackgroundOperation.None) { return(m_completedBackgroundOperationTask); } if (m_currentBackgroundOperationCompletionSource == null) { m_currentBackgroundOperationCompletionSource = new TaskCompletionSource <BackgroundOperationSlot>(); } return(m_currentBackgroundOperationCompletionSource.Task); } }
/// <summary> /// <see cref="Stream.Seek(long, SeekOrigin)"/> /// </summary> public override long Seek(long offset, SeekOrigin origin) { using (StreamOperationToken token = StartNewOperation(StreamOperation.Seek)) { BackgroundOperationSlot slot = token.WaitForBackgroundOperationSlot(); if (slot.Usability == StreamUsability.Broken) { throw slot.ThrowExceptionForBrokenStream(); } long offsetFromStart; switch (origin) { case SeekOrigin.Begin: Contract.Assume(offset >= 0, "Attempted to seek to a negative offset"); offsetFromStart = offset; break; case SeekOrigin.Current: Contract.Assume(m_position >= offset, "Attempted to seek (relative to current) to a negative offset"); offsetFromStart = m_position + offset; break; case SeekOrigin.End: throw new NotSupportedException("Seeking relative to stream end is not supported"); default: throw Contract.AssertFailure("Unknwon SeekOrigin"); } if (m_position != offsetFromStart) { FlushOrDiscardBufferAndResetPositionAsync(token, slot, offsetFromStart).GetAwaiter().GetResult(); } return(offsetFromStart); } }
private async Task FlushOrDiscardBufferAndResetPositionAsync(StreamOperationToken token, BackgroundOperationSlot slot, long newPosition) { Contract.Requires(slot.Usability != StreamUsability.Broken); Analysis.IgnoreArgument(token); m_position = newPosition; await FlushOrDiscardBufferAsync(slot); lock (m_backgroundOperationLock) { // Buffer is now empty, and so its position should be in sync with the virtual position (recall how both begin at zero on stream open). m_bufferPosition = m_position; // Buffer position changed, so an end-of-file indication is no longer valid. if (m_usability == StreamUsability.EndOfFileReached) { m_usability = StreamUsability.Usable; } else { Contract.Assume(m_usability == StreamUsability.Usable, "m_usability == slot.Usability is not Broken"); } } }
/// <summary> /// Waits for the current background operation to complete, if any. /// </summary> protected BackgroundOperationSlot WaitForBackgroundOperationSlot(StreamOperationToken token) { return(WaitForBackgroundOperationSlotAsync(token).GetAwaiter().GetResult()); }