3

Upgraded some code to be asynchronous and realized that Span<T> cannot be used in an async method.

Invoking Span<T>.ToArray() fixes the issue at the cost of allocating every single time.

Can you suggest a pattern on how to avoid allocating in this case?

using ISO9660.Logical;

namespace ISO9660.Physical;

public static class DiscExtensions
{
    public static async Task ReadFileRawAsync(this Disc disc, IsoFileSystemEntryFile file, Stream stream)
    {
        await ReadFileAsync(disc, file, stream, ReadFileRaw);
    }

    public static async Task ReadFileUserAsync(this Disc disc, IsoFileSystemEntryFile file, Stream stream)
    {
        await ReadFileAsync(disc, file, stream, ReadFileUser);
    }

    private static async Task ReadFileAsync(Disc disc, IsoFileSystemEntryFile file, Stream stream, ReadFileHandler handler)
    {
        int position = (int)file.Position;

        Track track = disc.Tracks.FirstOrDefault(s => position >= s.Position)
                      ?? throw new InvalidOperationException("Failed to determine track for file.");

        int sectors = (int)Math.Ceiling((double)file.Length / track.Sector.GetUserDataLength());

        for (int i = position; i < position + sectors; i++)
        {
            ISector sector = await track.ReadSectorAsync(i);

            byte[] array = handler(file, stream, sector).ToArray(); // sucks

            await stream.WriteAsync(array);
        }
    }

    private static Span<byte> ReadFileRaw(IsoFileSystemEntryFile file, Stream stream, ISector sector)
    {
        Span<byte> data = sector.GetData();

        int size = data.Length;

        Span<byte> span = data[..size];

        return span;
    }

    private static Span<byte> ReadFileUser(IsoFileSystemEntryFile file, Stream stream, ISector sector)
    {
        Span<byte> data = sector.GetUserData();

        int size = (int)Math.Min(Math.Max(file.Length - stream.Length, 0), data.Length);

        Span<byte> span = data[..size];

        return span;
    }

    private delegate Span<byte> ReadFileHandler(IsoFileSystemEntryFile file, Stream stream, ISector sector);
}

ISector interface:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace ISO9660.Physical;

public interface ISector
{
    int Length { get; }

    Span<byte> GetData();

    Span<byte> GetUserData();

    int GetUserDataLength();

    public static Span<byte> GetSpan<T>(scoped ref T sector, int start, int length)
        where T : struct, ISector
    {
        var span = MemoryMarshal.CreateSpan(ref sector, 1);

        var bytes = MemoryMarshal.AsBytes(span);

        var slice = bytes.Slice(start, length);

        return slice;
    }
    
    public static ISector Read<T>(Stream stream)
        where T : struct, ISector
    {
        var size = Unsafe.SizeOf<T>();

        Span<byte> span = stackalloc byte[size];

        stream.ReadExactly(span);

        var read = MemoryMarshal.Read<T>(span);

        return read;
    }

    public static async Task<ISector> ReadAsync<T>(Stream stream)
        where T : struct, ISector
    {
        var buffer = new byte[Unsafe.SizeOf<T>()];

        await stream.ReadExactlyAsync(buffer);

        var sector = MemoryMarshal.Read<T>(buffer);

        return sector;
    }
}

ISector implementation example:

using JetBrains.Annotations;

namespace ISO9660.Physical;

public unsafe struct SectorRawMode1 : ISector
{
    private const int UserDataLength = 2048;

    private const int UserDataPosition = 16;

    [UsedImplicitly]
    public fixed byte Sync[12];

    [UsedImplicitly]
    public fixed byte Header[4];

    [UsedImplicitly]
    public fixed byte UserData[UserDataLength];

    [UsedImplicitly]
    public fixed byte Edc[4];

    [UsedImplicitly]
    public fixed byte Intermediate[8];

    [UsedImplicitly]
    public fixed byte PParity[172];

    [UsedImplicitly]
    public fixed byte QParity[104];

    public readonly int Length => 2352;

    public Span<byte> GetData()
    {
        return ISector.GetSpan(ref this, 0, Length);
    }

    public Span<byte> GetUserData()
    {
        return ISector.GetSpan(ref this, UserDataPosition, UserDataLength);
    }

    public readonly int GetUserDataLength()
    {
        return UserDataLength;
    }
}
10
  • 3
    Please replace all of the var keywords with their actual compile-time static types. Commented Nov 7, 2023 at 2:01
  • Is there a reason you're not passing a CancellationToken? Commented Nov 7, 2023 at 2:01
  • 1
    var position = (int)file.Position; <-- ISO 9660 allows volumes up to 8TB in length, but your code assumes a 32-bit (4GB) limit. You should be using Int64 here. Commented Nov 7, 2023 at 2:02
  • What is ReadFileHandler? Commented Nov 7, 2023 at 2:03
  • 1
    Potential duplicate of stackoverflow.com/questions/57229123/… Commented Nov 7, 2023 at 2:08

1 Answer 1

2

Change Span<T> to Memory<T> and push the change upwards. Memory<T> is analogous to Span<T> for async (in practice). The idea is unlike a Span, a Memory can live on the heap (and thus can't refer to a stackalloc).

There is no MemoryMarshal.CreateMemory so you have to do it the old fashioned way: pin, take address, and construct a Memory form a pointer. Example how to do this: https://stackoverflow.com/a/52191681/14768 Note that the MemoryManager allocation is far smaller than the buffer allocation and you don't need to copy.

Be very careful how you write your code; this MemoryManager's constructor does indeed take a Span that is the entire ISector, and it will live across async calls; but if you fail to dispose it, memory leak. Also, if the ISector came from the stack rather than the heap, that's on you and you will trash stack.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.