Skip to content

Interlocked.Read takes readonly ref despite requiring write access which causes crashes #11658

Description

@leander-behr-blag

Description

Interlocked.Read uses CompareExchange to read a value atomically and with memory ordering guarantees. It takes a readonly ref, which seems to make sense given that it never changes the value.

One could very reasonably use Interlocked.Read to read from a shared memory view with read only access. This causes a crash however because CompareExchange actually does write to the address.

Reproduction Steps

Open a shared memory block with read only access and try to read from it with Interlocked.Read.

I haven't written any C# in a long time so excuse oddities in the example.

using System.IO.MemoryMappedFiles;

Console.WriteLine("Hello, World!");

// Create or open a memory-mapped file
using var memoryMappedFile = MemoryMappedFile.CreateOrOpen("TestFile42", 100, MemoryMappedFileAccess.Read);

// Create a view accessor to access the memory-mapped file
using var accessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read);


// Function to get a reference to the n-th long in the memory-mapped file
unsafe ref long GetNthLong(MemoryMappedViewAccessor accessor, int n)
{
    if (n < 0 || n * sizeof(long) >= accessor.Capacity)
    {
        throw new ArgumentOutOfRangeException(nameof(n), "Index is out of bounds.");
    }

    byte* pointer = null;
    accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
    try
    {
        return ref *(long*)(pointer + n * sizeof(long));
    }
    finally
    {
        accessor.SafeMemoryMappedViewHandle.ReleasePointer();
    }
}

ref readonly long firstLong = ref GetNthLong(accessor, 0); // Get the first long (n = 0)

long readValue = Interlocked.Read(in firstLong); // Atomic read ==> AccessViolationException
//long readValue = firstLong; // Normal read (not atomic)

Console.WriteLine($"first long: {readValue}");

Expected behavior

The function should not advertise itself as non-modifying or should somehow be implemented to actually not require write access.

Actual behavior

The function takes a readonly ref but requires write access to the memory.

Regression?

No response

Known Workarounds

No response

Configuration

I tried this in a .NET 9 console app on Win11 23h2 on x86.

Other information

No response

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions