// 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;
namespace FASTER.core
{
///
/// Managed device using .NET streams
///
public class ManagedLocalStorageDevice : StorageDeviceBase
{
private readonly bool preallocateFile;
private readonly bool deleteOnClose;
private readonly ConcurrentDictionary logHandles;
private SectorAlignedBufferPool pool;
///
///
///
/// File name (or prefix) with path
///
///
/// The maximal number of bytes this storage device can accommondate, or CAPACITY_UNSPECIFIED if there is no such limit
/// Whether to recover device metadata from existing files
public ManagedLocalStorageDevice(string filename, bool preallocateFile = false, bool deleteOnClose = false, long capacity = Devices.CAPACITY_UNSPECIFIED, bool recoverDevice = false)
: base(filename, GetSectorSize(filename), capacity)
{
pool = new SectorAlignedBufferPool(1, 1);
this.preallocateFile = preallocateFile;
this.deleteOnClose = deleteOnClose;
logHandles = new ConcurrentDictionary();
if (recoverDevice)
RecoverFiles();
}
private void RecoverFiles()
{
FileInfo fi = new FileInfo(FileName); // may not exist
DirectoryInfo di = fi.Directory;
if (!di.Exists) return;
string bareName = fi.Name;
List segids = new List();
foreach (FileInfo item in di.GetFiles(bareName + "*"))
{
segids.Add(Int32.Parse(item.Name.Replace(bareName, "").Replace(".", "")));
}
segids.Sort();
int prevSegmentId = -1;
foreach (int segmentId in segids)
{
if (segmentId != prevSegmentId + 1)
{
startSegment = segmentId;
}
else
{
endSegment = segmentId;
}
prevSegmentId = segmentId;
}
// No need to populate map because logHandles use Open or create on files.
}
class ReadCallbackWrapper
{
readonly Stream logHandle;
readonly IOCompletionCallback callback;
readonly IAsyncResult asyncResult;
SectorAlignedMemory memory;
readonly IntPtr destinationAddress;
readonly uint readLength;
public ReadCallbackWrapper(Stream logHandle, IOCompletionCallback callback, IAsyncResult asyncResult, SectorAlignedMemory memory, IntPtr destinationAddress, uint readLength)
{
this.logHandle = logHandle;
this.callback = callback;
this.asyncResult = asyncResult;
this.memory = memory;
this.destinationAddress = destinationAddress;
this.readLength = readLength;
}
public unsafe void Callback(IAsyncResult result)
{
uint errorCode = 0;
try
{
logHandle.EndRead(result);
fixed (void* source = memory.buffer)
{
Buffer.MemoryCopy(source, (void*)destinationAddress, readLength, readLength);
}
}
catch
{
errorCode = 1;
}
memory.Return();
Overlapped ov = new Overlapped(0, 0, IntPtr.Zero, asyncResult);
callback(errorCode, 0, ov.UnsafePack(callback, IntPtr.Zero));
}
}
class WriteCallbackWrapper
{
readonly Stream logHandle;
readonly IOCompletionCallback callback;
readonly IAsyncResult asyncResult;
SectorAlignedMemory memory;
public WriteCallbackWrapper(Stream logHandle, IOCompletionCallback callback, IAsyncResult asyncResult, SectorAlignedMemory memory)
{
this.callback = callback;
this.asyncResult = asyncResult;
this.memory = memory;
this.logHandle = logHandle;
}
public unsafe void Callback(IAsyncResult result)
{
uint errorCode = 0;
try
{
logHandle.EndWrite(result);
}
catch
{
errorCode = 1;
}
memory.Return();
Overlapped ov = new Overlapped(0, 0, IntPtr.Zero, asyncResult);
callback(errorCode, 0, ov.UnsafePack(callback, IntPtr.Zero));
}
}
///
///
///
///
///
///
///
///
///
public override unsafe void ReadAsync(int segmentId, ulong sourceAddress,
IntPtr destinationAddress,
uint readLength,
IOCompletionCallback callback,
IAsyncResult asyncResult)
{
var logHandle = GetOrAddHandle(segmentId);
var memory = pool.Get((int)readLength);
logHandle.Seek((long)sourceAddress, SeekOrigin.Begin);
logHandle.BeginRead(memory.buffer, 0, (int)readLength,
new ReadCallbackWrapper(logHandle, callback, asyncResult, memory, destinationAddress, readLength).Callback, null);
}
///
///
///
///
///
///
///
///
///
public override unsafe void WriteAsync(IntPtr sourceAddress,
int segmentId,
ulong destinationAddress,
uint numBytesToWrite,
IOCompletionCallback callback,
IAsyncResult asyncResult)
{
var logHandle = GetOrAddHandle(segmentId);
var memory = pool.Get((int)numBytesToWrite);
fixed (void* destination = memory.buffer)
{
Buffer.MemoryCopy((void*)sourceAddress, destination, numBytesToWrite, numBytesToWrite);
}
logHandle.Seek((long)destinationAddress, SeekOrigin.Begin);
logHandle.BeginWrite(memory.buffer, 0, (int)numBytesToWrite,
new WriteCallbackWrapper(logHandle, callback, asyncResult, memory).Callback, null);
}
///
///
///
///
public override void RemoveSegment(int segment)
{
if (logHandles.TryRemove(segment, out Stream logHandle))
{
logHandle.Dispose();
File.Delete(GetSegmentName(segment));
}
}
///
///
///
///
///
///
public override void RemoveSegmentAsync(int segment, AsyncCallback callback, IAsyncResult result)
{
RemoveSegment(segment);
callback(result);
}
///
///
///
public override void Close()
{
foreach (var logHandle in logHandles.Values)
logHandle.Dispose();
pool.Free();
}
private string GetSegmentName(int segmentId)
{
return FileName + "." + segmentId;
}
private static uint GetSectorSize(string filename)
{
#if DOTNETCORE
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Debug.WriteLine("Assuming 512 byte sector alignment for disk with file " + filename);
return 512;
}
#endif
if (!Native32.GetDiskFreeSpace(filename.Substring(0, 3),
out uint lpSectorsPerCluster,
out uint _sectorSize,
out uint lpNumberOfFreeClusters,
out uint lpTotalNumberOfClusters))
{
Debug.WriteLine("Unable to retrieve information for disk " + filename.Substring(0, 3) + " - check if the disk is available and you have specified the full path with drive name. Assuming sector size of 512 bytes.");
_sectorSize = 512;
}
return _sectorSize;
}
private Stream CreateHandle(int segmentId)
{
FileOptions fo = FileOptions.WriteThrough;
fo |= FileOptions.Asynchronous;
if (deleteOnClose)
fo |= FileOptions.DeleteOnClose;
var logHandle = new FileStream(
GetSegmentName(segmentId), FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.ReadWrite, 4096, fo);
if (preallocateFile && segmentSize != -1)
SetFileSize(FileName, logHandle, segmentSize);
return logHandle;
}
private Stream GetOrAddHandle(int _segmentId)
{
return logHandles.GetOrAdd(_segmentId, segmentId => CreateHandle(segmentId));
}
///
/// Sets file size to the specified value.
/// Does not reset file seek pointer to original location.
///
///
///
///
///
private bool SetFileSize(string filename, Stream logHandle, long size)
{
logHandle.SetLength(size);
return true;
}
}
}