// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using Microsoft.Win32.SafeHandles; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace FASTER.core { /// /// /// public abstract class StorageDeviceBase : IDevice { /// /// /// public uint SectorSize { get; } /// /// /// public string FileName { get; } /// /// /// public long Capacity { get; } /// /// /// public int StartSegment { get { return startSegment; } } /// /// /// public int EndSegment { get { return endSegment; } } /// /// /// public long SegmentSize { get { return segmentSize; } } /// /// Segment size /// protected long segmentSize; private int segmentSizeBits; private ulong segmentSizeMask; /// /// Instance of the epoch protection framework in the current system. /// A device may have internal in-memory data structure that requires epoch protection under concurrent access. /// protected LightEpoch epoch; /// /// start and end segment corresponding to and . Subclasses are /// allowed to modify these as needed. /// protected int startSegment = 0, endSegment = -1; /// /// Initializes a new StorageDeviceBase /// /// Name of the file to use /// The smallest unit of write of the underlying storage device (e.g. 512 bytes for a disk) /// The maximal number of bytes this storage device can accommondate, or CAPAPCITY_UNSPECIFIED if there is no such limit public StorageDeviceBase(string filename, uint sectorSize, long capacity) { FileName = filename; SectorSize = sectorSize; segmentSize = -1; segmentSizeBits = 64; segmentSizeMask = ~0UL; Capacity = capacity; } /// /// Initialize device /// /// /// public virtual void Initialize(long segmentSize, LightEpoch epoch = null) { Debug.Assert(Capacity == -1 || Capacity % segmentSize == 0, "capacity must be a multiple of segment sizes"); this.segmentSize = segmentSize; this.epoch = epoch; if (!Utility.IsPowerOfTwo(segmentSize)) { if (segmentSize != -1) throw new Exception("Invalid segment size: " + segmentSize); segmentSizeBits = 64; segmentSizeMask = ~0UL; } else { segmentSizeBits = Utility.GetLogBase2((ulong)segmentSize); segmentSizeMask = (ulong)segmentSize - 1; } } /// /// /// /// /// /// /// /// public void WriteAsync(IntPtr alignedSourceAddress, ulong alignedDestinationAddress, uint numBytesToWrite, IOCompletionCallback callback, IAsyncResult asyncResult) { int segment = (int)(segmentSizeBits < 64 ? alignedDestinationAddress >> segmentSizeBits : 0); // If the device has bounded space, and we are writing a new segment, need to check whether an existing segment needs to be evicted. if (Capacity != Devices.CAPACITY_UNSPECIFIED && Utility.MonotonicUpdate(ref endSegment, segment, out int oldEnd)) { // Attempt to update the stored range until there are enough space on the tier to accomodate the current logTail int newStartSegment = endSegment - (int)(Capacity >> segmentSizeBits); // Assuming that we still have enough physical capacity to write another segment, even if delete does not immediately free up space. TruncateUntilSegmentAsync(newStartSegment, r => { }, null); } WriteAsync( alignedSourceAddress, segment, alignedDestinationAddress & segmentSizeMask, numBytesToWrite, callback, asyncResult); } /// /// /// /// /// /// /// /// public void ReadAsync(ulong alignedSourceAddress, IntPtr alignedDestinationAddress, uint aligned_read_length, IOCompletionCallback callback, IAsyncResult asyncResult) { var segment = segmentSizeBits < 64 ? alignedSourceAddress >> segmentSizeBits : 0; ReadAsync( (int)segment, alignedSourceAddress & segmentSizeMask, alignedDestinationAddress, aligned_read_length, callback, asyncResult); } /// /// /// /// /// /// public abstract void RemoveSegmentAsync(int segment, AsyncCallback callback, IAsyncResult result); /// /// /// By default the implementation calls into /// /// public virtual void RemoveSegment(int segment) { ManualResetEventSlim completionEvent = new ManualResetEventSlim(false); RemoveSegmentAsync(segment, r => completionEvent.Set(), null); completionEvent.Wait(); } /// /// /// /// /// /// public void TruncateUntilSegmentAsync(int toSegment, AsyncCallback callback, IAsyncResult result) { // Reset begin range to at least toAddress if (!Utility.MonotonicUpdate(ref startSegment, toSegment, out int oldStart)) { // If no-op, invoke callback and return immediately callback(result); return; } CountdownEvent countdown = new CountdownEvent(toSegment - oldStart); // This action needs to be epoch-protected because readers may be issuing reads to the deleted segment, unaware of the delete. // Because of earlier compare-and-swap, the caller has exclusive access to the range [oldStartSegment, newStartSegment), and there will // be no double deletes. epoch.BumpCurrentEpoch(() => { for (int i = oldStart; i < toSegment; i++) { RemoveSegmentAsync(i, r => { if (countdown.Signal()) { callback(r); countdown.Dispose(); } }, result); } }); } /// /// /// /// public void TruncateUntilSegment(int toSegment) { using (ManualResetEventSlim completionEvent = new ManualResetEventSlim(false)) { TruncateUntilSegmentAsync(toSegment, r => completionEvent.Set(), null); completionEvent.Wait(); } } /// /// /// /// /// /// public virtual void TruncateUntilAddressAsync(long toAddress, AsyncCallback callback, IAsyncResult result) { // Truncate only up to segment boundary if address is not aligned TruncateUntilSegmentAsync((int)(toAddress >> segmentSizeBits), callback, result); } /// /// /// /// public virtual void TruncateUntilAddress(long toAddress) { using (ManualResetEventSlim completionEvent = new ManualResetEventSlim(false)) { TruncateUntilAddressAsync(toAddress, r => completionEvent.Set(), null); completionEvent.Wait(); } } /// /// /// /// /// /// /// /// /// public abstract void WriteAsync(IntPtr sourceAddress, int segmentId, ulong destinationAddress, uint numBytesToWrite, IOCompletionCallback callback, IAsyncResult asyncResult); /// /// /// /// /// /// /// /// /// public abstract void ReadAsync(int segmentId, ulong sourceAddress, IntPtr destinationAddress, uint readLength, IOCompletionCallback callback, IAsyncResult asyncResult); /// /// /// public abstract void Close(); } }