public unsafe void MapFile_WhenCalled_ShouldSuccess() { //TODO To remove when mmap functions are implemented in windows if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) == false) { return; } var path = Path.Combine(NewDataPath(forceCreateDir: true), $"test_journal.{Guid.NewGuid()}"); var initFileSize = 4096L; var mmapOptions = PalFlags.MmapOptions.CopyOnWrite; var ret = Pal.rvn_create_and_mmap64_file(path, initFileSize, mmapOptions, out var handle, out var baseAddress, out var actualFileSize, out var errorCode); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errorCode, ""); } ret = Pal.rvn_unmap(mmapOptions, baseAddress, actualFileSize, out errorCode); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errorCode, ""); } handle.Dispose(); }
public TimeSpan Write(long posBy4Kb, byte *p, int numberOf4Kb) { Debug.Assert(_options.IoMetrics != null); Stopwatch sp; using (var metrics = _options.IoMetrics.MeterIoRate(FileName.FullPath, IoMetrics.MeterType.JournalWrite, numberOf4Kb * 4L * Constants.Size.Kilobyte)) { sp = Stopwatch.StartNew(); var result = Pal.rvn_write_journal(_writeHandle, p, numberOf4Kb * 4L * Constants.Size.Kilobyte, posBy4Kb * 4L * Constants.Size.Kilobyte, out var error); if (result != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(result, error, $"Attempted to write to journal file - Path: {FileName.FullPath} Size: {numberOf4Kb * 4L * Constants.Size.Kilobyte}, numberOf4Kb={numberOf4Kb}"); } sp.Stop(); if (error == ERROR_WORKING_SET_QUOTA && _log.IsOperationsEnabled && _workingSetQuotaLogged == false) { _log.Operations( $"We managed to accomplish journal write although we got {nameof(ERROR_WORKING_SET_QUOTA)} under the covers and wrote data in 4KB chunks"); _workingSetQuotaLogged = true; } metrics.SetFileSize(NumberOfAllocated4Kb * (4L * Constants.Size.Kilobyte)); } return(sp.Elapsed); }
public override void Sync(long totalUnsynced) { var currentState = GetPagerStateAndAddRefAtomically(); try { using (var metric = Options.IoMetrics.MeterIoRate(FileName.FullPath, IoMetrics.MeterType.DataSync, 0)) { foreach (var alloc in currentState.AllocationInfos) { metric.IncrementFileSize(alloc.Size); var rc = rvn_memory_sync(alloc.BaseAddress, alloc.Size, out var errorCode); if (rc != FailCodes.Success) { PalHelper.ThrowLastError(rc, errorCode, $"Failed to memory sync at ${new IntPtr(alloc.BaseAddress).ToInt64():X} for for '{FileName.FullPath}'. TotalUnsynced = ${totalUnsynced}"); } } metric.IncrementSize(totalUnsynced); } } finally { currentState.Release(); } }
public void Read(byte *buffer, long numOfBytes, long offsetInFile) { int errorCode; long actualSize = 0; PalFlags.FailCodes result; if (_readHandle.IsInvalid) { result = Pal.rvn_open_journal_for_reads(FileName.FullPath, out _readHandle, out errorCode); EnsureValidResult(); } result = Pal.rvn_read_journal( _readHandle, buffer, numOfBytes, offsetInFile, out actualSize, out errorCode ); EnsureValidResult(); void EnsureValidResult() { if (result != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(result, errorCode, $"Attempted to read from journal file - Path: {FileName.FullPath} Size: {numOfBytes} Offset: {offsetInFile} ActualSize: {actualSize}"); } } }
public unsafe void MMap_WhenLinkEndOfPath_ShouldSucceed() { var basicPath = NewDataPath(forceCreateDir: true); var linkTarget = Path.Combine(basicPath, "linkTarget"); Directory.CreateDirectory(linkTarget); var link = Path.Combine(basicPath, "l"); symlink(linkTarget, link); var filePath = Path.Combine(link, $"test_journal.{Guid.NewGuid()}"); var initFileSize = 4096L; var mmapOptions = PalFlags.MmapOptions.CopyOnWrite; var ret = Pal.rvn_create_and_mmap64_file(filePath, initFileSize, mmapOptions, out var handle, out var baseAddress, out var actualFileSize, out var errorCode); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errorCode, ""); } ret = Pal.rvn_unmap(mmapOptions, baseAddress, actualFileSize, out errorCode); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errorCode, ""); } handle.Dispose(); }
public void OpenJournal_WhenCalled_ShouldCreateFile() { var file = Path.Combine(NewDataPath(forceCreateDir: true), $"test_journal.{Guid.NewGuid()}"); var fileSize = 4096L; PalFlags.FailCodes ret; ret = Pal.rvn_open_journal_for_writes(file, PalFlags.JournalMode.Safe, fileSize, PalFlags.DurabilityMode.DurabililtySupported, out var handle, out var actualSize, out var errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } Assert.Equal(PalFlags.FailCodes.Success, ret); Assert.True(File.Exists(file)); Assert.False(handle.IsInvalid); handle.Dispose(); var length = new FileInfo(file).Length; Assert.True(length >= 4096L); Assert.Equal(length, actualSize); }
public unsafe void MMap_WhenLinkBroken_ShouldFailWithInfoError() { var basicPath = NewDataPath(forceCreateDir: true); var linkTarget = Path.Combine(basicPath, "brokentarget"); var link = Path.Combine(basicPath, "brokenlink"); symlink(linkTarget, link); var filePath = Path.Combine(link, $"test_journal.{Guid.NewGuid()}"); var initFileSize = 4096L; var mmapOptions = PalFlags.MmapOptions.CopyOnWrite; var ret = PalFlags.FailCodes.None; Exception ex = Assert.Throws <InvalidOperationException>(() => { ret = Pal.rvn_create_and_mmap64_file(filePath, initFileSize, mmapOptions, out var handle, out var baseAddress, out var actualFileSize, out var errorCode); using (handle) { if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errorCode, ""); } ret = Pal.rvn_unmap(mmapOptions, baseAddress, actualFileSize, out errorCode); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errorCode, ""); } } }); Assert.Equal(PalFlags.FailCodes.FailBrokenLink, ret); }
public void TruncateJournal_WhenCalled_ShouldTruncate() { var file = Path.Combine(NewDataPath(forceCreateDir: true), $"test_journal.{Guid.NewGuid()}"); PalFlags.FailCodes ret; const long initSize = 2 * 4096L; ret = Pal.rvn_open_journal_for_writes(file, PalFlags.JournalMode.Safe, initSize, PalFlags.DurabilityMode.DurabililtySupported, out var handle, out var actualSize, out var errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } const long truncateSize = 4096L; ret = Pal.rvn_truncate_journal(handle, truncateSize, out errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } var actual = new FileInfo(file).Length; Assert.Equal(truncateSize, actual); handle.Dispose(); }
public unsafe void ReadJournal_WhenDo_ShouldRead() { var file = Path.Combine(NewDataPath(forceCreateDir: true), $"test_journal.{Guid.NewGuid()}"); const int fileSize = 3 * 4096; const int offset = 4096; const int length = 4096; PalFlags.FailCodes ret; ret = Pal.rvn_open_journal_for_writes(file, PalFlags.JournalMode.Safe, fileSize, PalFlags.DurabilityMode.DurabililtySupported, out var handle, out var actualSize, out var errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } var buffer = PlatformSpecific.NativeMemory.Allocate4KbAlignedMemory(4096, out var stats); for (var i = 0; i < 4096 / sizeof(int); i++) { *((int *)buffer + i) = i; } var expected = new byte[fileSize]; Marshal.Copy((IntPtr)buffer, expected, offset, length); try { ret = Pal.rvn_write_journal(handle, buffer, length, offset, out errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } } finally { PlatformSpecific.NativeMemory.Free4KbAlignedMemory(buffer, 4096, stats); } handle.Dispose(); var actual = new byte[length]; fixed(byte *pActual = actual) { ret = Pal.rvn_open_journal_for_reads(file, out var rHandle, out errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } ret = Pal.rvn_read_journal(rHandle, pActual, length, offset, out var readActualSize, out errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } Assert.Equal(length, readActualSize); } Assert.Equal(expected, expected); }
public void Dispose() { if (!_disposed.Raise()) { return; } GC.SuppressFinalize(this); _options.IoMetrics.FileClosed(FileName.FullPath); List <Exception> exceptions = null; TryExecute(() => { _readHandle.Dispose(); if (_readHandle.FailCode != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(_readHandle.FailCode, _readHandle.ErrorNo, $"Attempted to close 'read journal handle' - Path: {FileName.FullPath}"); } }); TryExecute(() => { _writeHandle.Dispose(); if (_writeHandle.FailCode != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(_writeHandle.FailCode, _writeHandle.ErrorNo, $"Attempted to close 'write journal handle' - Path: {FileName.FullPath}"); } }); if (exceptions != null) { throw new AggregateException("Failed to dispose journal writer", exceptions); } if (DeleteOnClose) { _options.TryStoreJournalForReuse(FileName); } void TryExecute(Action a) { try { a(); } catch (Exception e) { if (exceptions == null) { exceptions = new List <Exception>(); } exceptions.Add(e); } } }
public void Truncate(long size) { var result = Pal.rvn_truncate_journal(_writeHandle, size, out var error); if (result != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(result, error, $"Attempted to write to journal file - Path: {FileName.FullPath} Size: {size}"); } }
public JournalWriter(StorageEnvironmentOptions options, VoronPathSetting filename, long size, PalFlags.JournalMode mode = PalFlags.JournalMode.Safe) { _options = options; FileName = filename; _log = LoggingSource.Instance.GetLogger <JournalWriter>(options.BasePath.FullPath); var result = Pal.rvn_open_journal_for_writes(filename.FullPath, mode, size, options.SupportDurabilityFlags, out _writeHandle, out var actualSize, out var error); if (result != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(result, error, $"Attempted to open journal file - Path: {filename.FullPath} Size :{size}"); } NumberOfAllocated4Kb = (int)(actualSize / (4 * Constants.Size.Kilobyte)); }
public override void ReleaseAllocationInfo(byte *baseAddress, long size) { base.ReleaseAllocationInfo(baseAddress, size); var rc = rvn_unmap(DeleteOnClose ? MmapOptions.DeleteOnClose : MmapOptions.None, baseAddress, size, out var errorCode); if (rc != FailCodes.Success) { PalHelper.ThrowLastError(rc, errorCode, $"Failed to unmap {FileName.FullPath}. DeleteOnClose={DeleteOnClose}"); } NativeMemory.UnregisterFileMapping(FileName?.FullPath, new IntPtr(baseAddress), size); }
protected override void DisposeInternal() { _handle.Dispose(); // _handle.FailCode != success, we cannot delete the file probably, and there's nothing much we can do here. // just add to log and continue if (_handle.FailCode == FailCodes.Success) { return; } if (_logger.IsInfoEnabled) { _logger.Info($"Unable to dispose handle for {FileName.FullPath} (ignoring). rc={_handle.FailCode}. DeleteOnClose={DeleteOnClose}, " + $"errorCode={PalHelper.GetNativeErrorString(_handle.ErrorNo, "Unable to dispose handle for {FileName.FullPath} (ignoring).", out _)}", new IOException($"Unable to dispose handle for {FileName.FullPath} (ignoring).")); } }
public static unsafe void AllocateFileSpace(StorageEnvironmentOptions options, int fd, long size, string file) { bool usingLseek; var result = Syscall.AllocateFileSpace(fd, size, file, out usingLseek); if (result == (int)Errno.ENOSPC) { var diskSpaceResult = DiskSpaceChecker.GetDiskSpaceInfo(file); // Use Pal's detailed error string (until PosixHelper will be entirely removed) var nativeMsg = PalHelper.GetNativeErrorString(result, "Failed to AllocateFileSpace (PosixHelper)", out _); throw new DiskFullException(file, size, diskSpaceResult?.TotalFreeSpace.GetValue(SizeUnit.Bytes), nativeMsg); } if (result != 0) { Syscall.ThrowLastError(result, $"posix_fallocate(\"{file}\", {size})"); } }
public unsafe void WriteJournal_WhenCalled_ShouldWriteOnFile() { var file = Path.Combine(NewDataPath(forceCreateDir: true), $"test_journal.{Guid.NewGuid()}"); var fileSize = 4096L; PalFlags.FailCodes ret; ret = Pal.rvn_open_journal_for_writes(file, PalFlags.JournalMode.Safe, fileSize, PalFlags.DurabilityMode.DurabililtySupported, out var handle, out var actualSize, out var errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } var buffer = PlatformSpecific.NativeMemory.Allocate4KbAlignedMemory(4096, out var stats); for (var i = 0; i < 4096 / sizeof(int); i++) { *((int *)buffer + i) = i; } var expected = new byte[4096]; Marshal.Copy((IntPtr)buffer, expected, 0, 4096); try { ret = Pal.rvn_write_journal(handle, buffer, 4096, 0, out errno); if (ret != PalFlags.FailCodes.Success) { PalHelper.ThrowLastError(ret, errno, ""); } } finally { PlatformSpecific.NativeMemory.Free4KbAlignedMemory(buffer, 4096, stats); } handle.Dispose(); var bytesFromFile = File.ReadAllBytes(file); Assert.Equal(expected, bytesFromFile); }
protected internal override PagerState AllocateMorePages(long newLength) { if (DisposeOnceRunner.Disposed) { ThrowAlreadyDisposedException(); } var newLengthAfterAdjustment = NearestSizeToPageSize(newLength); if (newLengthAfterAdjustment <= _totalAllocationSize) { return(null); } var rc = rvn_allocate_more_space(newLengthAfterAdjustment, _handle, out var newAddress, out var errorCode); if (rc != FailCodes.Success) { PalHelper.ThrowLastError(rc, errorCode, $"can't allocate more pages (rc={rc}) for '{FileName.FullPath}'. Requested {newLength} (adjusted to {newLengthAfterAdjustment})"); } // TODO : Get rid of allocation info var allocationInfo = new PagerState.AllocationInfo { BaseAddress = (byte *)newAddress, Size = newLengthAfterAdjustment, MappedFile = null }; var newPagerState = new PagerState(this, Options.PrefetchSegmentSize, Options.PrefetchResetThreshold, allocationInfo); newPagerState.DebugVerify(newLengthAfterAdjustment); newPagerState.CopyPrefetchState(_pagerState); SetPagerState(newPagerState); _totalAllocationSize = newLengthAfterAdjustment; NumberOfAllocatedPages = _totalAllocationSize / Constants.Storage.PageSize; return(newPagerState); }
public RvnMemoryMapPager(StorageEnvironmentOptions options, VoronPathSetting file, long?initialFileSize = null, bool canPrefetchAhead = true, bool usePageProtection = false, bool deleteOnClose = false) : base(options, canPrefetchAhead, usePageProtection) { DeleteOnClose = deleteOnClose; FileName = file; var copyOnWriteMode = options.CopyOnWriteMode && FileName.FullPath.EndsWith(Constants.DatabaseFilename); _logger = LoggingSource.Instance.GetLogger <StorageEnvironment>($"Pager-{file}"); if (initialFileSize.HasValue == false || initialFileSize.Value == 0) { initialFileSize = Math.Max(SysInfo.PageSize * 16, 64 * 1024); } if (initialFileSize % SysInfo.PageSize != 0) { initialFileSize += SysInfo.PageSize - initialFileSize % SysInfo.PageSize; } Debug.Assert(file != null); var mmapOptions = copyOnWriteMode ? MmapOptions.CopyOnWrite : MmapOptions.None; if (DeleteOnClose) { mmapOptions |= MmapOptions.DeleteOnClose; } var rc = rvn_create_and_mmap64_file( file.FullPath, initialFileSize.Value, mmapOptions, out _handle, out var baseAddress, out _totalAllocationSize, out var errorCode); if (rc != FailCodes.Success) { try { PalHelper.ThrowLastError(rc, errorCode, $"rvn_create_and_mmap64_file failed on {rc} for '{file.FullPath}'"); } catch (DiskFullException dfEx) { var diskSpaceResult = DiskSpaceChecker.GetDiskSpaceInfo(file.FullPath); throw new DiskFullException(file.FullPath, initialFileSize.Value, diskSpaceResult?.TotalFreeSpace.GetValue(SizeUnit.Bytes), dfEx.Message); } } NumberOfAllocatedPages = _totalAllocationSize / Constants.Storage.PageSize; NativeMemory.RegisterFileMapping(FileName.FullPath, new IntPtr(baseAddress), _totalAllocationSize, GetAllocatedInBytes); var allocationInfo = new PagerState.AllocationInfo { BaseAddress = (byte *)baseAddress, Size = _totalAllocationSize, MappedFile = null }; var pager = new PagerState(this, Options.PrefetchSegmentSize, Options.PrefetchResetThreshold, allocationInfo); SetPagerState(pager); }