예제 #1
0
        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();
        }
예제 #2
0
        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);
        }
예제 #3
0
        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();
            }
        }
예제 #4
0
        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}");
                }
            }
        }
예제 #5
0
        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();
        }
예제 #6
0
        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);
        }
예제 #7
0
        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);
        }
예제 #8
0
        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();
        }
예제 #9
0
        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);
        }
예제 #10
0
        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);
                }
            }
        }
예제 #11
0
        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}");
            }
        }
예제 #12
0
        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));
        }
예제 #13
0
        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);
        }
예제 #14
0
        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)."));
            }
        }
예제 #15
0
        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})");
            }
        }
예제 #16
0
        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);
        }
예제 #17
0
        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);
        }
예제 #18
0
        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);
        }