Files
UnrealEngineUWP/Engine/Source/Programs/Horde/HordeCommon/MemoryMappedFileView.cs
Ben Marsh 16ea9a894c Horde: Add an LRU cache for CAS objects, backed by a memory-mapped file on disk. Cache reads are lock free. Writes require a lock but complete in constant time (and do not prohibit reads).
The cache is transactional, and is designed not to lose data if the process is terminated. The cache index and bulk store are kept separate, and blocks in the bulk store are not overwritten until the index has been flushed.

The age of items is tracked via "generations". An 8-bit generation counter is incremented once a certain size of items has been added to the cache, and older entries in the cache can be trimmed periodically by calling TrimAsync().

Since blocks of memory are allocated via memory mapped files and references are not visible to the garbage collector, clients can create locks over the cache which prevents trim operations from running.

[CL 17515095 by Ben Marsh in ue5-main branch]
2021-09-14 22:32:38 -04:00

77 lines
1.8 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Text;
namespace HordeCommon
{
/// <summary>
/// Implements an unmarshlled view of a memory mapped file
/// </summary>
unsafe class MemoryMappedFileView : IDisposable
{
sealed unsafe class MemoryWrapper : MemoryManager<byte>
{
private readonly byte* Pointer;
private readonly int Length;
public MemoryWrapper(byte* Pointer, int Length)
{
this.Pointer = Pointer;
this.Length = Length;
}
/// <inheritdoc/>
public override Span<byte> GetSpan() => new Span<byte>(Pointer, Length);
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex) => new MemoryHandle(Pointer + elementIndex);
/// <inheritdoc/>
public override void Unpin() { }
/// <inheritdoc/>
protected override void Dispose(bool disposing) { }
}
MemoryMappedViewAccessor MemoryMappedViewAccessor;
byte* Data;
/// <summary>
/// Constructor
/// </summary>
/// <param name="MemoryMappedViewAccessor"></param>
public MemoryMappedFileView(MemoryMappedViewAccessor MemoryMappedViewAccessor)
{
this.MemoryMappedViewAccessor = MemoryMappedViewAccessor;
MemoryMappedViewAccessor.SafeMemoryMappedViewHandle.AcquirePointer(ref Data);
}
/// <summary>
/// Gets a memory object for the given range
/// </summary>
/// <param name="Offset"></param>
/// <param name="Length"></param>
/// <returns></returns>
public Memory<byte> GetMemory(long Offset, int Length)
{
MemoryWrapper Wrapper = new MemoryWrapper(Data + Offset, Length);
return Wrapper.Memory;
}
/// <inheritdoc/>
public void Dispose()
{
if (Data != null)
{
MemoryMappedViewAccessor.SafeMemoryMappedViewHandle.ReleasePointer();
Data = null;
}
}
}
}