// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Runtime.CompilerServices; using System.Threading; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Runtime.InteropServices; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace FASTER.core { [StructLayout(LayoutKind.Sequential, Pack=1)] public struct Record { public RecordInfo info; public Key key; public Value value; } public unsafe sealed class GenericAllocator : AllocatorBase where Key : new() where Value : new() { // Circular buffer definition internal Record[][] values; // Object log related variables private readonly IDevice objectLogDevice; // Size of object chunks beign written to storage private const int ObjectBlockSize = 100 * (1 << 20); // Tail offsets per segment, in object log public readonly long[] segmentOffsets; // Record sizes private static readonly int recordSize = Utility.GetSize(default(Record)); private readonly SerializerSettings SerializerSettings; private readonly bool keyBlittable = Utility.IsBlittable(); private readonly bool valueBlittable = Utility.IsBlittable(); public GenericAllocator(LogSettings settings, SerializerSettings serializerSettings, IFasterEqualityComparer comparer, Action evictCallback = null, LightEpoch epoch = null, Action flushCallback = null) : base(settings, comparer, evictCallback, epoch, flushCallback) { SerializerSettings = serializerSettings; if ((!keyBlittable) && (settings.LogDevice as NullDevice == null) && ((SerializerSettings == null) || (SerializerSettings.keySerializer == null))) { throw new Exception("Key is not blittable, but no serializer specified via SerializerSettings"); } if ((!valueBlittable) && (settings.LogDevice as NullDevice == null) && ((SerializerSettings == null) || (SerializerSettings.valueSerializer == null))) { throw new Exception("Value is not blittable, but no serializer specified via SerializerSettings"); } values = new Record[BufferSize][]; segmentOffsets = new long[SegmentBufferSize]; objectLogDevice = settings.ObjectLogDevice; if ((settings.LogDevice as NullDevice == null) && (KeyHasObjects() || ValueHasObjects())) { if (objectLogDevice == null) throw new Exception("Objects in key/value, but object log not provided during creation of FASTER instance"); } } public override void Initialize() { Initialize(recordSize); } /// /// Get start logical address /// /// /// public override long GetStartLogicalAddress(long page) { return page << LogPageSizeBits; } /// /// Get first valid logical address /// /// /// public override long GetFirstValidLogicalAddress(long page) { if (page == 0) return (page << LogPageSizeBits) + recordSize; return page << LogPageSizeBits; } public override ref RecordInfo GetInfo(long physicalAddress) { // Offset within page int offset = (int)(physicalAddress & PageSizeMask); // Index of page within the circular buffer int pageIndex = (int)((physicalAddress >> LogPageSizeBits) & BufferSizeMask); return ref values[pageIndex][offset/recordSize].info; } public override ref RecordInfo GetInfoFromBytePointer(byte* ptr) { return ref Unsafe.AsRef>(ptr).info; } public override ref Key GetKey(long physicalAddress) { // Offset within page int offset = (int)(physicalAddress & PageSizeMask); // Index of page within the circular buffer int pageIndex = (int)((physicalAddress >> LogPageSizeBits) & BufferSizeMask); return ref values[pageIndex][offset / recordSize].key; } public override ref Value GetValue(long physicalAddress) { // Offset within page int offset = (int)(physicalAddress & PageSizeMask); // Index of page within the circular buffer int pageIndex = (int)((physicalAddress >> LogPageSizeBits) & BufferSizeMask); return ref values[pageIndex][offset / recordSize].value; } public override int GetRecordSize(long physicalAddress) { return recordSize; } public override int GetAverageRecordSize() { return recordSize; } public override int GetInitialRecordSize(ref Key key, ref Input input) { return recordSize; } public override int GetRecordSize(ref Key key, ref Value value) { return recordSize; } /// /// Dispose memory allocator /// public override void Dispose() { if (values != null) { for (int i = 0; i < values.Length; i++) { values[i] = null; } values = null; } base.Dispose(); } /// /// Delete in-memory portion of the log /// internal override void DeleteFromMemory() { for (int i = 0; i < values.Length; i++) { values[i] = null; } values = null; } public override AddressInfo* GetKeyAddressInfo(long physicalAddress) { return (AddressInfo*)Unsafe.AsPointer(ref Unsafe.AsRef>((byte*)physicalAddress).key); } public override AddressInfo* GetValueAddressInfo(long physicalAddress) { return (AddressInfo*)Unsafe.AsPointer(ref Unsafe.AsRef>((byte*)physicalAddress).value); } /// /// Allocate memory page, pinned in memory, and in sector aligned form, if possible /// /// internal override void AllocatePage(int index) { values[index] = AllocatePage(); } internal Record[] AllocatePage() { Record[] tmp; if (PageSize % recordSize == 0) tmp = new Record[PageSize / recordSize]; else tmp = new Record[1 + (PageSize / recordSize)]; Array.Clear(tmp, 0, tmp.Length); return tmp; } public override long GetPhysicalAddress(long logicalAddress) { return logicalAddress; } protected override bool IsAllocated(int pageIndex) { return values[pageIndex] != null; } protected override void TruncateUntilAddress(long toAddress) { base.TruncateUntilAddress(toAddress); objectLogDevice.TruncateUntilAddress(toAddress); } protected override void WriteAsync(long flushPage, IOCompletionCallback callback, PageAsyncFlushResult asyncResult) { WriteAsync(flushPage, (ulong)(AlignedPageSizeBytes * flushPage), (uint)PageSize, callback, asyncResult, device, objectLogDevice); } protected override void WriteAsyncToDevice (long startPage, long flushPage, int pageSize, IOCompletionCallback callback, PageAsyncFlushResult asyncResult, IDevice device, IDevice objectLogDevice) { // We are writing to separate device, so use fresh segment offsets WriteAsync(flushPage, (ulong)(AlignedPageSizeBytes * (flushPage - startPage)), (uint)pageSize, callback, asyncResult, device, objectLogDevice, flushPage, new long[SegmentBufferSize]); } protected override void ClearPage(long page, int offset) { Array.Clear(values[page % BufferSize], offset / recordSize, values[page % BufferSize].Length - offset / recordSize); // Close segments var thisCloseSegment = page >> (LogSegmentSizeBits - LogPageSizeBits); var nextCloseSegment = (page + 1) >> (LogSegmentSizeBits - LogPageSizeBits); if (thisCloseSegment != nextCloseSegment) { // We are clearing the last page in current segment segmentOffsets[thisCloseSegment % SegmentBufferSize] = 0; } } private void WriteAsync(long flushPage, ulong alignedDestinationAddress, uint numBytesToWrite, IOCompletionCallback callback, PageAsyncFlushResult asyncResult, IDevice device, IDevice objlogDevice, long intendedDestinationPage = -1, long[] localSegmentOffsets = null) { // Short circuit if we are using a null device if (device as NullDevice != null) { device.WriteAsync(IntPtr.Zero, 0, 0, numBytesToWrite, callback, asyncResult); return; } int start = 0, aligned_start = 0, end = (int)numBytesToWrite; if (asyncResult.partial) { start = (int)((asyncResult.fromAddress - (asyncResult.page << LogPageSizeBits))); aligned_start = (start / sectorSize) * sectorSize; end = (int)((asyncResult.untilAddress - (asyncResult.page << LogPageSizeBits))); } // Check if user did not override with special segment offsets if (localSegmentOffsets == null) localSegmentOffsets = segmentOffsets; var src = values[flushPage % BufferSize]; var buffer = bufferPool.Get((int)numBytesToWrite); if (aligned_start < start && (KeyHasObjects() || ValueHasObjects())) { // Do not read back the invalid header of page 0 if ((flushPage > 0) || (start > GetFirstValidLogicalAddress(flushPage))) { // Get the overlapping HLOG from disk as we wrote it with // object pointers previously. This avoids object reserialization PageAsyncReadResult result = new PageAsyncReadResult { handle = new CountdownEvent(1) }; device.ReadAsync(alignedDestinationAddress + (ulong)aligned_start, (IntPtr)buffer.aligned_pointer + aligned_start, (uint)sectorSize, AsyncReadPageCallback, result); result.handle.Wait(); } fixed (RecordInfo* pin = &src[0].info) { Debug.Assert(buffer.aligned_pointer + numBytesToWrite <= (byte*)buffer.handle.AddrOfPinnedObject() + buffer.buffer.Length); Buffer.MemoryCopy((void*)((long)Unsafe.AsPointer(ref src[0]) + start), buffer.aligned_pointer + start, numBytesToWrite - start, numBytesToWrite - start); } } else { fixed (RecordInfo* pin = &src[0].info) { Debug.Assert(buffer.aligned_pointer + numBytesToWrite <= (byte*)buffer.handle.AddrOfPinnedObject() + buffer.buffer.Length); Buffer.MemoryCopy((void*)((long)Unsafe.AsPointer(ref src[0]) + aligned_start), buffer.aligned_pointer + aligned_start, numBytesToWrite - aligned_start, numBytesToWrite - aligned_start); } } long ptr = (long)buffer.aligned_pointer; List addr = new List(); asyncResult.freeBuffer1 = buffer; MemoryStream ms = new MemoryStream(); IObjectSerializer keySerializer = null; IObjectSerializer valueSerializer = null; if (KeyHasObjects()) { keySerializer = SerializerSettings.keySerializer(); keySerializer.BeginSerialize(ms); } if (ValueHasObjects()) { valueSerializer = SerializerSettings.valueSerializer(); valueSerializer.BeginSerialize(ms); } for (int i=start/recordSize; iAddress = pos; key_address->Size = (int)(ms.Position - pos); addr.Add((long)key_address); } if (ValueHasObjects() && !src[i].info.Tombstone) { long pos = ms.Position; valueSerializer.Serialize(ref src[i].value); var value_address = GetValueAddressInfo((long)(buffer.aligned_pointer + i * recordSize)); value_address->Address = pos; value_address->Size = (int)(ms.Position - pos); addr.Add((long)value_address); } } if (ms.Position > ObjectBlockSize || i == (end / recordSize) - 1) { var memoryStreamLength = (int)ms.Position; var _objBuffer = bufferPool.Get(memoryStreamLength); asyncResult.done = new AutoResetEvent(false); var _alignedLength = (memoryStreamLength + (sectorSize - 1)) & ~(sectorSize - 1); var _objAddr = Interlocked.Add(ref localSegmentOffsets[(long)(alignedDestinationAddress >> LogSegmentSizeBits) % SegmentBufferSize], _alignedLength) - _alignedLength; if (KeyHasObjects()) keySerializer.EndSerialize(); if (ValueHasObjects()) valueSerializer.EndSerialize(); ms.Close(); fixed (void* src_ = ms.GetBuffer()) Buffer.MemoryCopy(src_, _objBuffer.aligned_pointer, memoryStreamLength, memoryStreamLength); foreach (var address in addr) ((AddressInfo*)address)->Address += _objAddr; if (i < (end / recordSize) - 1) { ms = new MemoryStream(); if (KeyHasObjects()) keySerializer.BeginSerialize(ms); if (ValueHasObjects()) valueSerializer.BeginSerialize(ms); objlogDevice.WriteAsync( (IntPtr)_objBuffer.aligned_pointer, (int)(alignedDestinationAddress >> LogSegmentSizeBits), (ulong)_objAddr, (uint)_alignedLength, AsyncFlushPartialObjectLogCallback, asyncResult); // Wait for write to complete before resuming next write asyncResult.done.WaitOne(); _objBuffer.Return(); } else { // need to write both page and object cache Interlocked.Increment(ref asyncResult.count); asyncResult.freeBuffer2 = _objBuffer; objlogDevice.WriteAsync( (IntPtr)_objBuffer.aligned_pointer, (int)(alignedDestinationAddress >> LogSegmentSizeBits), (ulong)_objAddr, (uint)_alignedLength, callback, asyncResult); } } } if (asyncResult.partial) { var aligned_end = (int)((asyncResult.untilAddress - (asyncResult.page << LogPageSizeBits))); aligned_end = ((aligned_end + (sectorSize - 1)) & ~(sectorSize - 1)); numBytesToWrite = (uint)(aligned_end - aligned_start); } var alignedNumBytesToWrite = (uint)((numBytesToWrite + (sectorSize - 1)) & ~(sectorSize - 1)); // Finally write the hlog page device.WriteAsync((IntPtr)buffer.aligned_pointer + aligned_start, alignedDestinationAddress + (ulong)aligned_start, alignedNumBytesToWrite, callback, asyncResult); } private void AsyncReadPageCallback(uint errorCode, uint numBytes, NativeOverlapped* overlap) { if (errorCode != 0) { Trace.TraceError("OverlappedStream GetQueuedCompletionStatus error: {0}", errorCode); } // Set the page status to flushed var result = (PageAsyncReadResult)Overlapped.Unpack(overlap).AsyncResult; result.handle.Signal(); Overlapped.Free(overlap); } protected override void ReadAsync( ulong alignedSourceAddress, int destinationPageIndex, uint aligned_read_length, IOCompletionCallback callback, PageAsyncReadResult asyncResult, IDevice device, IDevice objlogDevice) { asyncResult.freeBuffer1 = bufferPool.Get((int)aligned_read_length); asyncResult.freeBuffer1.required_bytes = (int)aligned_read_length; if (!(KeyHasObjects() || ValueHasObjects())) { device.ReadAsync(alignedSourceAddress, (IntPtr)asyncResult.freeBuffer1.aligned_pointer, aligned_read_length, callback, asyncResult); return; } asyncResult.callback = callback; if (objlogDevice == null) { Debug.Assert(objectLogDevice != null); objlogDevice = objectLogDevice; } asyncResult.objlogDevice = objlogDevice; device.ReadAsync(alignedSourceAddress, (IntPtr)asyncResult.freeBuffer1.aligned_pointer, aligned_read_length, AsyncReadPageWithObjectsCallback, asyncResult); } /// /// IOCompletion callback for page flush /// /// /// /// private void AsyncFlushPartialObjectLogCallback(uint errorCode, uint numBytes, NativeOverlapped* overlap) { if (errorCode != 0) { Trace.TraceError("OverlappedStream GetQueuedCompletionStatus error: {0}", errorCode); } // Set the page status to flushed PageAsyncFlushResult result = (PageAsyncFlushResult)Overlapped.Unpack(overlap).AsyncResult; result.done.Set(); Overlapped.Free(overlap); } private void AsyncReadPageWithObjectsCallback(uint errorCode, uint numBytes, NativeOverlapped* overlap) { if (errorCode != 0) { Trace.TraceError("OverlappedStream GetQueuedCompletionStatus error: {0}", errorCode); } PageAsyncReadResult result = (PageAsyncReadResult)Overlapped.Unpack(overlap).AsyncResult; Record[] src; // We are reading into a frame if (result.frame != null) { var frame = (GenericFrame)result.frame; src = frame.GetPage(result.page % frame.frameSize); } else src = values[result.page % BufferSize]; // Deserialize all objects until untilptr if (result.resumePtr < result.untilPtr) { MemoryStream ms = new MemoryStream(result.freeBuffer2.buffer); ms.Seek(result.freeBuffer2.offset, SeekOrigin.Begin); Deserialize(result.freeBuffer1.GetValidPointer(), result.resumePtr, result.untilPtr, src, ms); ms.Dispose(); result.freeBuffer2.Return(); result.freeBuffer2 = null; result.resumePtr = result.untilPtr; } // If we have processed entire page, return if (result.untilPtr >= result.maxPtr) { result.Free(); // Call the "real" page read callback result.callback(errorCode, numBytes, overlap); return; } // We will be re-issuing I/O, so free current overlap Overlapped.Free(overlap); // We will now be able to process all records until (but not including) untilPtr GetObjectInfo(result.freeBuffer1.GetValidPointer(), ref result.untilPtr, result.maxPtr, ObjectBlockSize, out long startptr, out long size); // Object log fragment should be aligned by construction Debug.Assert(startptr % sectorSize == 0); if (size > int.MaxValue) throw new Exception("Unable to read object page, total size greater than 2GB: " + size); var alignedLength = (size + (sectorSize - 1)) & ~(sectorSize - 1); var objBuffer = bufferPool.Get((int)alignedLength); result.freeBuffer2 = objBuffer; // Request objects from objlog result.objlogDevice.ReadAsync( (int)(result.page >> (LogSegmentSizeBits - LogPageSizeBits)), (ulong)startptr, (IntPtr)objBuffer.aligned_pointer, (uint)alignedLength, AsyncReadPageWithObjectsCallback, result); } /// /// Invoked by users to obtain a record from disk. It uses sector aligned memory to read /// the record efficiently into memory. /// /// /// /// /// /// protected override void AsyncReadRecordObjectsToMemory(long fromLogical, int numBytes, IOCompletionCallback callback, AsyncIOContext context, SectorAlignedMemory result = default(SectorAlignedMemory)) { ulong fileOffset = (ulong)(AlignedPageSizeBytes * (fromLogical >> LogPageSizeBits) + (fromLogical & PageSizeMask)); ulong alignedFileOffset = (ulong)(((long)fileOffset / sectorSize) * sectorSize); uint alignedReadLength = (uint)((long)fileOffset + numBytes - (long)alignedFileOffset); alignedReadLength = (uint)((alignedReadLength + (sectorSize - 1)) & ~(sectorSize - 1)); var record = bufferPool.Get((int)alignedReadLength); record.valid_offset = (int)(fileOffset - alignedFileOffset); record.available_bytes = (int)(alignedReadLength - (fileOffset - alignedFileOffset)); record.required_bytes = numBytes; var asyncResult = default(AsyncGetFromDiskResult>); asyncResult.context = context; asyncResult.context.record = result; asyncResult.context.objBuffer = record; objectLogDevice.ReadAsync( (int)(context.logicalAddress >> LogSegmentSizeBits), alignedFileOffset, (IntPtr)asyncResult.context.objBuffer.aligned_pointer, alignedReadLength, callback, asyncResult); } /// /// Read pages from specified device /// /// /// /// /// /// /// /// /// /// /// /// internal void AsyncReadPagesFromDeviceToFrame( long readPageStart, int numPages, long untilAddress, IOCompletionCallback callback, TContext context, GenericFrame frame, out CountdownEvent completed, long devicePageOffset = 0, IDevice device = null, IDevice objectLogDevice = null) { var usedDevice = device; IDevice usedObjlogDevice = objectLogDevice; if (device == null) { usedDevice = this.device; } completed = new CountdownEvent(numPages); for (long readPage = readPageStart; readPage < (readPageStart + numPages); readPage++) { int pageIndex = (int)(readPage % frame.frameSize); if (frame.GetPage(pageIndex) == null) { frame.Allocate(pageIndex); } else { frame.Clear(pageIndex); } var asyncResult = new PageAsyncReadResult() { page = readPage, context = context, handle = completed, maxPtr = PageSize, frame = frame, }; ulong offsetInFile = (ulong)(AlignedPageSizeBytes * readPage); uint readLength = (uint)AlignedPageSizeBytes; long adjustedUntilAddress = (AlignedPageSizeBytes * (untilAddress >> LogPageSizeBits) + (untilAddress & PageSizeMask)); if (adjustedUntilAddress > 0 && ((adjustedUntilAddress - (long)offsetInFile) < PageSize)) { readLength = (uint)(adjustedUntilAddress - (long)offsetInFile); asyncResult.maxPtr = readLength; readLength = (uint)((readLength + (sectorSize - 1)) & ~(sectorSize - 1)); } if (device != null) offsetInFile = (ulong)(AlignedPageSizeBytes * (readPage - devicePageOffset)); ReadAsync(offsetInFile, pageIndex, readLength, callback, asyncResult, usedDevice, usedObjlogDevice); } } #region Page handlers for objects /// /// Deseialize part of page from stream /// /// /// From pointer /// Until pointer /// /// Stream public void Deserialize(byte *raw, long ptr, long untilptr, Record[] src, Stream stream) { IObjectSerializer keySerializer = null; IObjectSerializer valueSerializer = null; long streamStartPos = stream.Position; long start_addr = -1; if (KeyHasObjects()) { keySerializer = SerializerSettings.keySerializer(); keySerializer.BeginDeserialize(stream); } if (ValueHasObjects()) { valueSerializer = SerializerSettings.valueSerializer(); valueSerializer.BeginDeserialize(stream); } while (ptr < untilptr) { ref Record record = ref Unsafe.AsRef>(raw + ptr); src[ptr / recordSize].info = record.info; if (!record.info.Invalid) { if (KeyHasObjects()) { var key_addr = GetKeyAddressInfo((long)raw + ptr); if (start_addr == -1) start_addr = key_addr->Address; if (stream.Position != streamStartPos + key_addr->Address - start_addr) { stream.Seek(streamStartPos + key_addr->Address - start_addr, SeekOrigin.Begin); } src[ptr / recordSize].key = new Key(); keySerializer.Deserialize(ref src[ptr/recordSize].key); } else { src[ptr / recordSize].key = record.key; } if (!record.info.Tombstone) { if (ValueHasObjects()) { var value_addr = GetValueAddressInfo((long)raw + ptr); if (start_addr == -1) start_addr = value_addr->Address; if (stream.Position != streamStartPos + value_addr->Address - start_addr) { stream.Seek(streamStartPos + value_addr->Address - start_addr, SeekOrigin.Begin); } src[ptr / recordSize].value = new Value(); valueSerializer.Deserialize(ref src[ptr / recordSize].value); } else { src[ptr / recordSize].value = record.value; } } } ptr += GetRecordSize(ptr); } if (KeyHasObjects()) { keySerializer.EndDeserialize(); } if (ValueHasObjects()) { valueSerializer.EndDeserialize(); } } /// /// Get location and range of object log addresses for specified log page /// /// /// /// /// /// /// public void GetObjectInfo(byte* raw, ref long ptr, long untilptr, int objectBlockSize, out long startptr, out long size) { long minObjAddress = long.MaxValue; long maxObjAddress = long.MinValue; while (ptr < untilptr) { ref Record record = ref Unsafe.AsRef>(raw + ptr); if (!record.info.Invalid) { if (KeyHasObjects()) { var key_addr = GetKeyAddressInfo((long)raw + ptr); var addr = key_addr->Address; // If object pointer is greater than kObjectSize from starting object pointer if (minObjAddress != long.MaxValue && (addr - minObjAddress > objectBlockSize)) { break; } if (addr < minObjAddress) minObjAddress = addr; addr += key_addr->Size; if (addr > maxObjAddress) maxObjAddress = addr; } if (ValueHasObjects() && !record.info.Tombstone) { var value_addr = GetValueAddressInfo((long)raw + ptr); var addr = value_addr->Address; // If object pointer is greater than kObjectSize from starting object pointer if (minObjAddress != long.MaxValue && (addr - minObjAddress > objectBlockSize)) { break; } if (addr < minObjAddress) minObjAddress = addr; addr += value_addr->Size; if (addr > maxObjAddress) maxObjAddress = addr; } } ptr += GetRecordSize(ptr); } // Handle the case where no objects are to be written if (minObjAddress == long.MaxValue && maxObjAddress == long.MinValue) { minObjAddress = 0; maxObjAddress = 0; } startptr = minObjAddress; size = maxObjAddress - minObjAddress; } /// /// Retrieve objects from object log /// /// /// /// protected override bool RetrievedFullRecord(byte* record, ref AsyncIOContext ctx) { if (!KeyHasObjects()) { ShallowCopy(ref Unsafe.AsRef>(record).key, ref ctx.key); } if (!ValueHasObjects()) { ShallowCopy(ref Unsafe.AsRef>(record).value, ref ctx.value); } if (!(KeyHasObjects() || ValueHasObjects())) return true; if (ctx.objBuffer == null) { // Issue IO for objects long startAddress = -1; long endAddress = -1; if (KeyHasObjects()) { var x = GetKeyAddressInfo((long)record); startAddress = x->Address; endAddress = x->Address + x->Size; } if (ValueHasObjects() && !GetInfoFromBytePointer(record).Tombstone) { var x = GetValueAddressInfo((long)record); if (startAddress == -1) startAddress = x->Address; endAddress = x->Address + x->Size; } // We are limited to a 2GB size per key-value if (endAddress-startAddress > int.MaxValue) throw new Exception("Size of key-value exceeds max of 2GB: " + (endAddress - startAddress)); AsyncGetFromDisk(startAddress, (int)(endAddress - startAddress), ctx, ctx.record); return false; } // Parse the key and value objects MemoryStream ms = new MemoryStream(ctx.objBuffer.buffer); ms.Seek(ctx.objBuffer.offset + ctx.objBuffer.valid_offset, SeekOrigin.Begin); if (KeyHasObjects()) { ctx.key = new Key(); var keySerializer = SerializerSettings.keySerializer(); keySerializer.BeginDeserialize(ms); keySerializer.Deserialize(ref ctx.key); keySerializer.EndDeserialize(); } if (ValueHasObjects() && !GetInfoFromBytePointer(record).Tombstone) { ctx.value = new Value(); var valueSerializer = SerializerSettings.valueSerializer(); valueSerializer.BeginDeserialize(ms); valueSerializer.Deserialize(ref ctx.value); valueSerializer.EndDeserialize(); } ctx.objBuffer.Return(); return true; } /// /// Whether KVS has keys to serialize/deserialize /// /// public override bool KeyHasObjects() { return SerializerSettings.keySerializer != null; } /// /// Whether KVS has values to serialize/deserialize /// /// public override bool ValueHasObjects() { return SerializerSettings.valueSerializer != null; } #endregion public override IHeapContainer GetKeyContainer(ref Key key) => new StandardHeapContainer(ref key); public override IHeapContainer GetValueContainer(ref Value value) => new StandardHeapContainer(ref value); public override long[] GetSegmentOffsets() { return segmentOffsets; } internal override void PopulatePage(byte* src, int required_bytes, long destinationPage) { PopulatePage(src, required_bytes, ref values[destinationPage % BufferSize]); } internal void PopulatePageFrame(byte* src, int required_bytes, Record[] frame) { PopulatePage(src, required_bytes, ref frame); } internal void PopulatePage(byte* src, int required_bytes, ref Record[] destinationPage) { fixed (RecordInfo* pin = &destinationPage[0].info) { Debug.Assert(required_bytes <= recordSize * destinationPage.Length); Buffer.MemoryCopy(src, Unsafe.AsPointer(ref destinationPage[0]), required_bytes, required_bytes); } } /// /// Iterator interface for scanning FASTER log /// /// /// /// /// public override IFasterScanIterator Scan(long beginAddress, long endAddress, ScanBufferingMode scanBufferingMode) { return new GenericScanIterator(this, beginAddress, endAddress, scanBufferingMode); } } }