// 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();
}
}