File: LinearReadCache.cs
Web Access
Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers.Binary;

namespace Microsoft.Diagnostics.DataContractReader;
// Linear page cache used by the per-object heap walk.
public sealed class LinearReadCache
{
    // Typical page size
    private const uint PageSize = 0x1000;
    private readonly Target _target;
    private readonly byte[] _page = new byte[PageSize];
    private ulong _currPageStart;
    private uint _currPageSize;

    public LinearReadCache(Target target)
    {
        _target = target;
    }

    public bool TryReadPointer(ulong addr, out TargetPointer value)
    {
        Span<byte> buffer = stackalloc byte[sizeof(ulong)];
        buffer = buffer.Slice(0, _target.PointerSize);
        if (!TryRead(addr, buffer))
        {
            value = TargetPointer.Null;
            return false;
        }
        value = _target.ReadPointerFromSpan(buffer);
        return true;
    }

    public bool TryReadUInt32(ulong addr, out uint value)
    {
        Span<byte> buffer = stackalloc byte[sizeof(uint)];
        if (!TryRead(addr, buffer))
        {
            value = 0;
            return false;
        }
        value = _target.IsLittleEndian
            ? BinaryPrimitives.ReadUInt32LittleEndian(buffer)
            : BinaryPrimitives.ReadUInt32BigEndian(buffer);
        return true;
    }

    private bool TryRead(ulong addr, Span<byte> dest)
    {
        // If the request misses the currently-cached page, try to load the page
        // containing it. If that fails (e.g. the page is unmapped), or the request
        // straddles the end of the cached page, fall back to a direct read.
        if (addr < _currPageStart || addr - _currPageStart >= _currPageSize)
        {
            if (!MoveToPage(addr))
                return DirectRead(addr, dest);
        }

        ulong offset = addr - _currPageStart;
        if (offset + (ulong)dest.Length > _currPageSize)
            return DirectRead(addr, dest);

        _page.AsSpan((int)offset, dest.Length).CopyTo(dest);
        return true;
    }

    private bool MoveToPage(ulong addr)
    {
        ulong pageStart = addr - (addr % PageSize);
        try
        {
            _target.ReadBuffer(pageStart, _page.AsSpan(0, (int)PageSize));
            _currPageStart = pageStart;
            _currPageSize = PageSize;
            return true;
        }
        catch
        {
            _currPageStart = 0;
            _currPageSize = 0;
            return false;
        }
    }

    private bool DirectRead(ulong addr, Span<byte> dest)
    {
        try
        {
            _target.ReadBuffer(addr, dest);
            return true;
        }
        catch
        {
            return false;
        }
    }
}