Skip to content

Commit

Permalink
Add ReadOnly Support for IDevice (#870)
Browse files Browse the repository at this point in the history
* add readOnly flag in streamProvider for LocalStorageDevice

* add linux readOnly flag for managed device

* add explicit readOnly assignment for parameter
  • Loading branch information
vazois authored Dec 12, 2024
1 parent cf1d4b6 commit 0d4c744
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 22 deletions.
14 changes: 11 additions & 3 deletions libs/common/StreamProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ public class StreamProviderFactory
/// <param name="locationType">Type of location of files the stream provider reads from / writes to</param>
/// <param name="connectionString">Connection string to Azure Storage, if applicable</param>
/// <param name="resourceAssembly">Assembly from which to load the embedded resource, if applicable.</param>
/// <param name="readOnly">Open file in read only mode</param>
/// <returns>StreamProvider instance</returns>
public static IStreamProvider GetStreamProvider(FileLocationType locationType, string connectionString = null, Assembly resourceAssembly = null)
public static IStreamProvider GetStreamProvider(FileLocationType locationType, string connectionString = null, Assembly resourceAssembly = null, bool readOnly = false)
{
switch (locationType)
{
Expand All @@ -130,7 +131,7 @@ public static IStreamProvider GetStreamProvider(FileLocationType locationType, s
throw new ArgumentException("Azure Storage connection string is required to read/write to Azure Storage", nameof(connectionString));
return new AzureStreamProvider(connectionString);
case FileLocationType.Local:
return new LocalFileStreamProvider();
return new LocalFileStreamProvider(readOnly);
case FileLocationType.EmbeddedResource:
if (resourceAssembly == null)
throw new ArgumentException(
Expand Down Expand Up @@ -181,11 +182,18 @@ protected override long GetBytesToWrite(byte[] bytes, IDevice device)
/// </summary>
internal class LocalFileStreamProvider : StreamProviderBase
{
private readonly bool readOnly;

public LocalFileStreamProvider(bool readOnly = false)
{
this.readOnly = readOnly;
}

protected override IDevice GetDevice(string path)
{
var fileInfo = new FileInfo(path);

INamedDeviceFactory settingsDeviceFactoryCreator = new LocalStorageNamedDeviceFactory(disableFileBuffering: false);
INamedDeviceFactory settingsDeviceFactoryCreator = new LocalStorageNamedDeviceFactory(disableFileBuffering: false, readOnly: readOnly);
settingsDeviceFactoryCreator.Initialize("");
var settingsDevice = settingsDeviceFactoryCreator.Get(new FileDescriptor(fileInfo.DirectoryName, fileInfo.Name));
settingsDevice.Initialize(-1, epoch: null, omitSegmentIdFromFilename: true);
Expand Down
2 changes: 1 addition & 1 deletion libs/host/ServerSettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ private static bool TryImportServerOptions(string path, ConfigFileType configFil
{
var assembly = fileLocationType == FileLocationType.EmbeddedResource ? Assembly.GetExecutingAssembly() : null;

var streamProvider = StreamProviderFactory.GetStreamProvider(fileLocationType, connString, assembly);
var streamProvider = StreamProviderFactory.GetStreamProvider(fileLocationType, connString, assembly, readOnly: true);
var configProvider = ConfigProviderFactory.GetConfigProvider(configFileType);

using var stream = streamProvider.Read(path);
Expand Down
11 changes: 6 additions & 5 deletions libs/storage/Tsavorite/cs/src/core/Device/Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,27 @@ public static class Devices
/// Create a storage device for the log
/// </summary>
/// <param name="logPath">Path to file that will store the log (empty for null device)</param>
/// <param name="preallocateFile">Whether we try to preallocate the file on creation</param>
/// <param name="preallocateFile">Whether we try to pre-allocate the file on creation</param>
/// <param name="deleteOnClose">Delete files on close</param>
/// <param name="capacity">The maximal number of bytes this storage device can accommondate, or CAPACITY_UNSPECIFIED if there is no such limit</param>
/// <param name="capacity">The maximal number of bytes this storage device can accommodate, or CAPACITY_UNSPECIFIED if there is no such limit</param>
/// <param name="recoverDevice">Whether to recover device metadata from existing files</param>
/// <param name="useIoCompletionPort">Whether we use IO completion port with polling</param>
/// <param name="disableFileBuffering">Whether file buffering (during write) is disabled (default of true requires aligned writes)</param>
/// <param name="useNativeDeviceLinux">Use native device on Linux, instead of managed device based on FileStream</param>
/// <param name="readOnly">Open file in readOnly mode</param>
/// <param name="logger"></param>
/// <returns>Device instance</returns>
public static IDevice CreateLogDevice(string logPath, bool preallocateFile = false, bool deleteOnClose = false, long capacity = CAPACITY_UNSPECIFIED, bool recoverDevice = false, bool useIoCompletionPort = false, bool disableFileBuffering = true, bool useNativeDeviceLinux = false, ILogger logger = null)
public static IDevice CreateLogDevice(string logPath, bool preallocateFile = false, bool deleteOnClose = false, long capacity = CAPACITY_UNSPECIFIED, bool recoverDevice = false, bool useIoCompletionPort = false, bool disableFileBuffering = true, bool useNativeDeviceLinux = false, bool readOnly = false, ILogger logger = null)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && useNativeDeviceLinux)
return new NativeStorageDevice(logPath, deleteOnClose, disableFileBuffering, capacity, logger: logger);
else
return new ManagedLocalStorageDevice(logPath, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice);
return new ManagedLocalStorageDevice(logPath, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice, readOnly: readOnly);
}
else
return new LocalStorageDevice(logPath, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice, useIoCompletionPort);
return new LocalStorageDevice(logPath, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice, useIoCompletionPort, readOnly: readOnly);
}
}
}
17 changes: 11 additions & 6 deletions libs/storage/Tsavorite/cs/src/core/Device/LocalStorageDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public unsafe class LocalStorageDevice : StorageDeviceBase
private readonly ConcurrentQueue<SimpleAsyncResult> results;
private static uint sectorSize = 0;
private bool _disposed;
readonly bool readOnly;

/// <summary>
/// Number of pending reads on device
Expand All @@ -62,8 +63,10 @@ public LocalStorageDevice(string filename,
bool deleteOnClose = false,
bool disableFileBuffering = true,
long capacity = Devices.CAPACITY_UNSPECIFIED,
bool recoverDevice = false, bool useIoCompletionPort = false)
: this(filename, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice, null, useIoCompletionPort)
bool recoverDevice = false,
bool useIoCompletionPort = false,
bool readOnly = false)
: this(filename, preallocateFile, deleteOnClose, disableFileBuffering, capacity, recoverDevice, null, useIoCompletionPort, readOnly: readOnly)
{
}

Expand Down Expand Up @@ -96,7 +99,8 @@ protected internal LocalStorageDevice(string filename,
long capacity = Devices.CAPACITY_UNSPECIFIED,
bool recoverDevice = false,
IEnumerable<KeyValuePair<int, SafeFileHandle>> initialLogFileHandles = null,
bool useIoCompletionPort = true)
bool useIoCompletionPort = true,
bool readOnly = false)
: base(filename, GetSectorSize(filename), capacity)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Expand Down Expand Up @@ -134,6 +138,7 @@ protected internal LocalStorageDevice(string filename,
this.preallocateFile = preallocateFile;
this.deleteOnClose = deleteOnClose;
this.disableFileBuffering = disableFileBuffering;
this.readOnly = readOnly;
results = new ConcurrentQueue<SimpleAsyncResult>();

logHandles = initialLogFileHandles != null
Expand Down Expand Up @@ -388,14 +393,14 @@ public override long GetFileSize(int segment)
}

private SafeFileHandle CreateHandle(int segmentId, bool disableFileBuffering, bool deleteOnClose, bool preallocateFile, long segmentSize, string fileName, IntPtr ioCompletionPort)
=> CreateHandle(segmentId, disableFileBuffering, deleteOnClose, preallocateFile, segmentSize, fileName, ioCompletionPort, OmitSegmentIdFromFileName);
=> CreateHandle(segmentId, disableFileBuffering, deleteOnClose, preallocateFile, segmentSize, fileName, ioCompletionPort, OmitSegmentIdFromFileName, readOnly);

/// <summary>
/// Creates a SafeFileHandle for the specified segment. This can be used by derived classes to prepopulate logHandles in the constructor.
/// </summary>
protected internal static SafeFileHandle CreateHandle(int segmentId, bool disableFileBuffering, bool deleteOnClose, bool preallocateFile, long segmentSize, string fileName, IntPtr ioCompletionPort, bool omitSegmentId = false)
protected internal static SafeFileHandle CreateHandle(int segmentId, bool disableFileBuffering, bool deleteOnClose, bool preallocateFile, long segmentSize, string fileName, IntPtr ioCompletionPort, bool omitSegmentId = false, bool readOnly = false)
{
uint fileAccess = Native32.GENERIC_READ | Native32.GENERIC_WRITE;
uint fileAccess = readOnly ? Native32.GENERIC_READ : 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public sealed class ManagedLocalStorageDevice : StorageDeviceBase
private readonly bool deleteOnClose;
private readonly bool disableFileBuffering;
private readonly bool osReadBuffering;
private readonly bool readOnly;
private readonly SafeConcurrentDictionary<int, (AsyncPool<Stream>, AsyncPool<Stream>)> logHandles;
private readonly SectorAlignedBufferPool pool;

Expand All @@ -40,7 +41,8 @@ public sealed class ManagedLocalStorageDevice : StorageDeviceBase
/// <param name="capacity">The maximal number of bytes this storage device can accommondate, or CAPACITY_UNSPECIFIED if there is no such limit</param>
/// <param name="recoverDevice">Whether to recover device metadata from existing files</param>
/// <param name="osReadBuffering">Enable OS read buffering</param>
public ManagedLocalStorageDevice(string filename, bool preallocateFile = false, bool deleteOnClose = false, bool disableFileBuffering = true, long capacity = Devices.CAPACITY_UNSPECIFIED, bool recoverDevice = false, bool osReadBuffering = false)
/// <param name="readOnly">Open file in readOnly mode</param>
public ManagedLocalStorageDevice(string filename, bool preallocateFile = false, bool deleteOnClose = false, bool disableFileBuffering = true, long capacity = Devices.CAPACITY_UNSPECIFIED, bool recoverDevice = false, bool osReadBuffering = false, bool readOnly = false)
: base(filename, GetSectorSize(filename), capacity)
{
pool = new(1, 1);
Expand All @@ -55,6 +57,7 @@ public ManagedLocalStorageDevice(string filename, bool preallocateFile = false,
this.deleteOnClose = deleteOnClose;
this.disableFileBuffering = disableFileBuffering;
this.osReadBuffering = osReadBuffering;
this.readOnly = readOnly;
logHandles = new();
if (recoverDevice)
RecoverFiles();
Expand Down Expand Up @@ -420,7 +423,7 @@ private Stream CreateReadHandle(int segmentId)

var logReadHandle = new FileStream(
GetSegmentName(segmentId), FileMode.OpenOrCreate,
FileAccess.Read, FileShare.ReadWrite, 512, fo);
FileAccess.Read, readOnly ? FileShare.Read : FileShare.ReadWrite, 512, fo);

return logReadHandle;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ public abstract class StorageDeviceBase : IDevice
/// </summary>
/// <param name="filename">Name of the file to use</param>
/// <param name="sectorSize">The smallest unit of write of the underlying storage device (e.g. 512 bytes for a disk) </param>
/// <param name="capacity">The maximal number of bytes this storage device can accommondate, or CAPAPCITY_UNSPECIFIED if there is no such limit </param>
public StorageDeviceBase(string filename, uint sectorSize, long capacity)
/// <param name="capacity">The maximal number of bytes this storage device can accommodate, or CAPAPCITY_UNSPECIFIED if there is no such limit </param>
/// <param name="readOnly">Open file in readOnly mode </param>
public StorageDeviceBase(string filename, uint sectorSize, long capacity, bool readOnly = false)
{
FileName = filename;
SectorSize = sectorSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class LocalStorageNamedDeviceFactory : INamedDeviceFactory
readonly bool preallocateFile;
readonly bool disableFileBuffering;
readonly bool useNativeDeviceLinux;
readonly bool readOnly;
readonly ILogger logger;

/// <summary>
Expand All @@ -32,13 +33,14 @@ public class LocalStorageNamedDeviceFactory : INamedDeviceFactory
/// <param name="throttleLimit">Throttle limit (max number of pending I/Os) for this device instance</param>
/// <param name="useNativeDeviceLinux">Use native device on Linux</param>
/// <param name="logger"></param>
public LocalStorageNamedDeviceFactory(bool preallocateFile = false, bool deleteOnClose = false, bool disableFileBuffering = true, int? throttleLimit = null, bool useNativeDeviceLinux = false, ILogger logger = null)
public LocalStorageNamedDeviceFactory(bool preallocateFile = false, bool deleteOnClose = false, bool disableFileBuffering = true, int? throttleLimit = null, bool useNativeDeviceLinux = false, bool readOnly = false, ILogger logger = null)
{
this.preallocateFile = preallocateFile;
this.deleteOnClose = deleteOnClose;
this.disableFileBuffering = disableFileBuffering;
this.throttleLimit = throttleLimit;
this.useNativeDeviceLinux = useNativeDeviceLinux;
this.readOnly = readOnly;
this.logger = logger;
}

Expand All @@ -51,15 +53,21 @@ public void Initialize(string baseName)
/// <inheritdoc />
public IDevice Get(FileDescriptor fileInfo)
{
var device = Devices.CreateLogDevice(Path.Combine(baseName, fileInfo.directoryName, fileInfo.fileName), preallocateFile: preallocateFile, deleteOnClose: deleteOnClose, disableFileBuffering: disableFileBuffering, useNativeDeviceLinux: useNativeDeviceLinux, logger: logger);
var device = Devices.CreateLogDevice(
Path.Combine(baseName, fileInfo.directoryName, fileInfo.fileName),
preallocateFile: preallocateFile,
deleteOnClose: deleteOnClose,
disableFileBuffering: disableFileBuffering,
useNativeDeviceLinux: useNativeDeviceLinux,
readOnly: readOnly,
logger: logger);
if (throttleLimit.HasValue)
{
device.ThrottleLimit = throttleLimit.Value;
}
return device;
}


/// <inheritdoc />
public IEnumerable<FileDescriptor> ListContents(string path)
{
Expand Down

0 comments on commit 0d4c744

Please sign in to comment.