diff --git a/cs/src/core/Device/LocalStorageDevice.cs b/cs/src/core/Device/LocalStorageDevice.cs
index 3974f74fc..7d3ef2d92 100644
--- a/cs/src/core/Device/LocalStorageDevice.cs
+++ b/cs/src/core/Device/LocalStorageDevice.cs
@@ -36,14 +36,36 @@ public LocalStorageDevice(string filename,
bool disableFileBuffering = true,
long capacity = Devices.CAPACITY_UNSPECIFIED,
bool recoverDevice = false)
+ : this(filename, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice, initialLogFileHandles: null)
+ {
+ }
+
+ ///
+ /// Constructor with more options for derived classes
+ ///
+ /// File name (or prefix) with path
+ ///
+ ///
+ ///
+ /// The maximum 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
+ /// Optional set of preloaded safe file handles, which can speed up hydration of preexisting log file handles
+ protected internal LocalStorageDevice(string filename,
+ bool preallocateFile = false,
+ bool deleteOnClose = false,
+ bool disableFileBuffering = true,
+ long capacity = Devices.CAPACITY_UNSPECIFIED,
+ bool recoverDevice = false,
+ IEnumerable> initialLogFileHandles = null)
: base(filename, GetSectorSize(filename), capacity)
-
{
Native32.EnableProcessPrivileges();
this.preallocateFile = preallocateFile;
this.deleteOnClose = deleteOnClose;
this.disableFileBuffering = disableFileBuffering;
- logHandles = new SafeConcurrentDictionary();
+ logHandles = initialLogFileHandles != null
+ ? new SafeConcurrentDictionary(initialLogFileHandles)
+ : new SafeConcurrentDictionary();
if (recoverDevice)
RecoverFiles();
}
@@ -217,48 +239,16 @@ public override void Close()
}
///
- ///
+ /// Creates a SafeFileHandle for the specified segment. This can be used by derived classes to prepopulate logHandles in the constructor.
///
- ///
- ///
- protected string GetSegmentName(int segmentId)
- {
- return FileName + "." + segmentId;
- }
-
- ///
- ///
- ///
- ///
- ///
- // Can be used to pre-load handles, e.g., after a checkpoint
- protected SafeFileHandle GetOrAddHandle(int _segmentId)
- {
- return logHandles.GetOrAdd(_segmentId, segmentId => CreateHandle(segmentId));
- }
-
- private static uint GetSectorSize(string filename)
- {
- 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 SafeFileHandle CreateHandle(int segmentId)
+ protected internal static SafeFileHandle CreateHandle(int segmentId, bool disableFileBuffering, bool deleteOnClose, bool preallocateFile, long segmentSize, string fileName)
{
uint fileAccess = Native32.GENERIC_READ | Native32.GENERIC_WRITE;
uint fileShare = unchecked(((uint)FileShare.ReadWrite & ~(uint)FileShare.Inheritable));
uint fileCreation = unchecked((uint)FileMode.OpenOrCreate);
uint fileFlags = Native32.FILE_FLAG_OVERLAPPED;
- if (this.disableFileBuffering)
+ if (disableFileBuffering)
{
fileFlags = fileFlags | Native32.FILE_FLAG_NO_BUFFERING;
}
@@ -273,7 +263,7 @@ private SafeFileHandle CreateHandle(int segmentId)
}
var logHandle = Native32.CreateFileW(
- GetSegmentName(segmentId),
+ GetSegmentName(fileName, segmentId),
fileAccess, fileShare,
IntPtr.Zero, fileCreation,
fileFlags, IntPtr.Zero);
@@ -281,11 +271,11 @@ private SafeFileHandle CreateHandle(int segmentId)
if (logHandle.IsInvalid)
{
var error = Marshal.GetLastWin32Error();
- throw new IOException($"Error creating log file for {GetSegmentName(segmentId)}, error: {error}", Native32.MakeHRFromErrorCode(error));
+ throw new IOException($"Error creating log file for {GetSegmentName(fileName, segmentId)}, error: {error}", Native32.MakeHRFromErrorCode(error));
}
if (preallocateFile && segmentSize != -1)
- SetFileSize(FileName, logHandle, segmentSize);
+ SetFileSize(fileName, logHandle, segmentSize);
try
{
@@ -293,16 +283,59 @@ private SafeFileHandle CreateHandle(int segmentId)
}
catch (Exception e)
{
- throw new FasterException("Error binding log handle for " + GetSegmentName(segmentId) + ": " + e.ToString());
+ throw new FasterException("Error binding log handle for " + GetSegmentName(fileName, segmentId) + ": " + e.ToString());
}
return logHandle;
}
+ ///
+ /// Static method to construct segment name
+ ///
+ protected static string GetSegmentName(string fileName, int segmentId)
+ {
+ return fileName + "." + segmentId;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected string GetSegmentName(int segmentId) => GetSegmentName(FileName, segmentId);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ // Can be used to pre-load handles, e.g., after a checkpoint
+ protected SafeFileHandle GetOrAddHandle(int _segmentId)
+ {
+ return logHandles.GetOrAdd(_segmentId, segmentId => CreateHandle(segmentId));
+ }
+
+ private SafeFileHandle CreateHandle(int segmentId)
+ => CreateHandle(segmentId, this.disableFileBuffering, this.deleteOnClose, this.preallocateFile, this.segmentSize, this.FileName);
+
+ private static uint GetSectorSize(string filename)
+ {
+ 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;
+ }
+
/// Sets file size to the specified value.
/// Does not reset file seek pointer to original location.
- private bool SetFileSize(string filename, SafeFileHandle logHandle, long size)
+ private static bool SetFileSize(string filename, SafeFileHandle logHandle, long size)
{
- if (segmentSize <= 0)
+ if (size <= 0)
return false;
if (Native32.EnableVolumePrivileges(filename, logHandle))
diff --git a/cs/src/core/Utilities/SafeConcurrentDictionary.cs b/cs/src/core/Utilities/SafeConcurrentDictionary.cs
index e9513af5d..38aefddfb 100644
--- a/cs/src/core/Utilities/SafeConcurrentDictionary.cs
+++ b/cs/src/core/Utilities/SafeConcurrentDictionary.cs
@@ -18,10 +18,20 @@ namespace FASTER.core
/// Type of values in the dictionary
internal sealed class SafeConcurrentDictionary : IEnumerable>
{
- private readonly ConcurrentDictionary dictionary = new ConcurrentDictionary();
+ private readonly ConcurrentDictionary dictionary;
private readonly ConcurrentDictionary keyLocks = new ConcurrentDictionary();
+ public SafeConcurrentDictionary()
+ {
+ this.dictionary = new ConcurrentDictionary();
+ }
+
+ public SafeConcurrentDictionary(IEnumerable> initialCollection)
+ {
+ this.dictionary = new ConcurrentDictionary(initialCollection);
+ }
+
///
/// Returns the count of the dictionary.
///
diff --git a/cs/test/SharedDirectoryTests.cs b/cs/test/SharedDirectoryTests.cs
index 3fef94120..ade30b815 100644
--- a/cs/test/SharedDirectoryTests.cs
+++ b/cs/test/SharedDirectoryTests.cs
@@ -2,9 +2,11 @@
// Licensed under the MIT license.
using FASTER.core;
+using Microsoft.Win32.SafeHandles;
using NUnit.Framework;
using NUnit.Framework.Internal;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -68,7 +70,7 @@ public void SharedLogDirectory()
CopyDirectory(new DirectoryInfo(this.original.CheckpointDirectory), new DirectoryInfo(cloneCheckpointDirectory));
// Recover from original checkpoint
- this.clone.Initialize(cloneCheckpointDirectory, this.sharedLogDirectory);
+ this.clone.Initialize(cloneCheckpointDirectory, this.sharedLogDirectory, populateLogHandles: true);
this.clone.Faster.Recover(checkpointGuid);
// Both sessions should work concurrently
@@ -95,12 +97,32 @@ private struct FasterTestInstance
public FasterKV Faster { get; private set; }
public IDevice LogDevice { get; private set; }
- public void Initialize(string checkpointDirectory, string logDirectory)
+ public void Initialize(string checkpointDirectory, string logDirectory, bool populateLogHandles = false)
{
this.CheckpointDirectory = checkpointDirectory;
this.LogDirectory = logDirectory;
- this.LogDevice = Devices.CreateLogDevice($"{this.LogDirectory}\\log", deleteOnClose: true);
+ string logFileName = "log";
+ string deviceFileName = $"{this.LogDirectory}\\{logFileName}";
+ KeyValuePair[] initialHandles = null;
+ if (populateLogHandles)
+ {
+ var segmentIds = new List();
+ foreach (FileInfo item in new DirectoryInfo(logDirectory).GetFiles(logFileName + "*"))
+ {
+ segmentIds.Add(int.Parse(item.Name.Replace(logFileName, "").Replace(".", "")));
+ }
+ segmentIds.Sort();
+ initialHandles = new KeyValuePair[segmentIds.Count];
+ for (int i = 0; i < segmentIds.Count; i++)
+ {
+ var segmentId = segmentIds[i];
+ var handle = LocalStorageDevice.CreateHandle(segmentId, disableFileBuffering: false, deleteOnClose: true, preallocateFile: false, segmentSize: -1, fileName: deviceFileName);
+ initialHandles[i] = new KeyValuePair(segmentId, handle);
+ }
+ }
+
+ this.LogDevice = new LocalStorageDevice(deviceFileName, deleteOnClose: true, disableFileBuffering: false, initialLogFileHandles: initialHandles);
this.Faster = new FasterKV(
keySpace,
new Functions(),