// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Threading; using System.Diagnostics; namespace FASTER.core { /// /// Scan iterator for hybrid log /// public class VariableLengthBlittableScanIterator : IFasterScanIterator where Key : new() where Value : new() { private readonly int frameSize; private readonly VariableLengthBlittableAllocator hlog; private readonly long beginAddress, endAddress; private readonly BlittableFrame frame; private readonly CountdownEvent[] loaded; private bool first = true; private long currentAddress, nextAddress; private long currentPhysicalAddress; /// /// Current address /// public long CurrentAddress => currentAddress; /// /// Constructor /// /// /// /// /// public unsafe VariableLengthBlittableScanIterator(VariableLengthBlittableAllocator hlog, long beginAddress, long endAddress, ScanBufferingMode scanBufferingMode) { this.hlog = hlog; if (beginAddress == 0) beginAddress = hlog.GetFirstValidLogicalAddress(0); this.beginAddress = beginAddress; this.endAddress = endAddress; currentAddress = -1; nextAddress = beginAddress; if (scanBufferingMode == ScanBufferingMode.SinglePageBuffering) frameSize = 1; else if (scanBufferingMode == ScanBufferingMode.DoublePageBuffering) frameSize = 2; else if (scanBufferingMode == ScanBufferingMode.NoBuffering) { frameSize = 0; return; } frame = new BlittableFrame(frameSize, hlog.PageSize, hlog.GetDeviceSectorSize()); loaded = new CountdownEvent[frameSize]; // Only load addresses flushed to disk if (nextAddress < hlog.HeadAddress) { var frameNumber = (nextAddress >> hlog.LogPageSizeBits) % frameSize; hlog.AsyncReadPagesFromDeviceToFrame (nextAddress >> hlog.LogPageSizeBits, 1, endAddress, AsyncReadPagesCallback, Empty.Default, frame, out loaded[frameNumber]); } } /// /// Gets reference to current key /// /// public ref Key GetKey() { return ref hlog.GetKey(currentPhysicalAddress); } /// /// Gets reference to current value /// /// public ref Value GetValue() { return ref hlog.GetValue(currentPhysicalAddress); } /// /// Get next record in iterator /// /// /// public bool GetNext(out RecordInfo recordInfo) { recordInfo = default(RecordInfo); currentAddress = nextAddress; while (true) { // Check for boundary conditions if (currentAddress >= endAddress) { return false; } if (currentAddress < hlog.BeginAddress) { throw new Exception("Iterator address is less than log BeginAddress " + hlog.BeginAddress); } if (frameSize == 0 && currentAddress < hlog.HeadAddress) { throw new Exception("Iterator address is less than log HeadAddress in memory-scan mode"); } var currentPage = currentAddress >> hlog.LogPageSizeBits; var offset = currentAddress & hlog.PageSizeMask; if (currentAddress < hlog.HeadAddress) BufferAndLoad(currentAddress, currentPage, currentPage % frameSize); var physicalAddress = default(long); if (currentAddress >= hlog.HeadAddress) physicalAddress = hlog.GetPhysicalAddress(currentAddress); else physicalAddress = frame.GetPhysicalAddress(currentPage % frameSize, offset); // Check if record fits on page, if not skip to next page var recordSize = hlog.GetRecordSize(physicalAddress); if ((currentAddress & hlog.PageSizeMask) + recordSize > hlog.PageSize) { currentAddress = (1 + (currentAddress >> hlog.LogPageSizeBits)) << hlog.LogPageSizeBits; continue; } ref var info = ref hlog.GetInfo(physicalAddress); if (info.Invalid || info.IsNull()) { currentAddress += recordSize; continue; } currentPhysicalAddress = physicalAddress; recordInfo = info; nextAddress = currentAddress + recordSize; return true; } } /// /// Get next record in iterator /// /// /// /// /// public bool GetNext(out RecordInfo recordInfo, out Key key, out Value value) { throw new NotSupportedException("Use GetNext(out RecordInfo) to retrieve references to key/value"); } /// /// Dispose the iterator /// public void Dispose() { frame?.Dispose(); } private unsafe void BufferAndLoad(long currentAddress, long currentPage, long currentFrame) { if (first || (currentAddress & hlog.PageSizeMask) == 0) { // Prefetch pages based on buffering mode if (frameSize == 1) { if (!first) { hlog.AsyncReadPagesFromDeviceToFrame(currentAddress >> hlog.LogPageSizeBits, 1, endAddress, AsyncReadPagesCallback, Empty.Default, frame, out loaded[currentFrame]); } } else { var endPage = endAddress >> hlog.LogPageSizeBits; if ((endPage > currentPage) && ((endPage > currentPage + 1) || ((endAddress & hlog.PageSizeMask) != 0))) { hlog.AsyncReadPagesFromDeviceToFrame(1 + (currentAddress >> hlog.LogPageSizeBits), 1, endAddress, AsyncReadPagesCallback, Empty.Default, frame, out loaded[(currentPage + 1) % frameSize]); } } first = false; } loaded[currentFrame].Wait(); } private unsafe void AsyncReadPagesCallback(uint errorCode, uint numBytes, NativeOverlapped* overlap) { if (errorCode != 0) { Trace.TraceError("OverlappedStream GetQueuedCompletionStatus error: {0}", errorCode); } var result = (PageAsyncReadResult)Overlapped.Unpack(overlap).AsyncResult; if (result.freeBuffer1 != null) { hlog.PopulatePage(result.freeBuffer1.GetValidPointer(), result.freeBuffer1.required_bytes, result.page); result.freeBuffer1.Return(); result.freeBuffer1 = null; } if (result.handle != null) { result.handle.Signal(); } Interlocked.MemoryBarrier(); Overlapped.Free(overlap); } } }