Table of Contents

NBMsgPack035: Async converters should return readers

Custom converters (classes that derive from MessagePackConverter<T>) that override the ReadAsync method should return the MessagePackReader or MessagePackStreamingReader struct that it creates with CreateBufferedReader or CreateStreamingReader, respectively.

Consider that the location of the diagnostic may not always indicate the location of the underlying issue. When resolving this violation, consider the various branching, loops, etc. as the problem may only be present when the code takes certain code paths.

Example violation

In the following example, the ReadAsync method creates a MessagePackStreamingReader then switches to a MessagePackReader. Neither reader is returned, which is a bug.

public override async ValueTask<SomeCustomType?> ReadAsync(MessagePackAsyncReader reader, SerializationContext context)
{
    MessagePackStreamingReader streamingReader = reader.CreateStreamingReader();

    int count;
    while (streamingReader.TryReadArrayHeader(out count).NeedsMoreBytes())
    {
        streamingReader = new(await streamingReader.FetchMoreBytesAsync());
    }

    if (count != 1)
    {
        throw new MessagePackSerializationException();
    }

    await reader.BufferNextStructureAsync(context); // OOPS: streamingReader should have been returned first
    MessagePackReader bufferedReader = reader.CreateBufferedReader();
    int seedCount = bufferedReader.ReadInt32();

    return new SomeCustomType(seedCount); // OOPS: bufferedReader should have been returned first
}

Resolution

The streaming reader must be returned prior to switching to the buffered reader. Both readers must be returned prior to the method exiting or awaited expressions.

public override async ValueTask<SomeCustomType?> ReadAsync(MessagePackAsyncReader reader, SerializationContext context)
{
    MessagePackStreamingReader streamingReader = reader.CreateStreamingReader();

    int count;
    while (streamingReader.TryReadArrayHeader(out count).NeedsMoreBytes())
    {
        streamingReader = new(await streamingReader.FetchMoreBytesAsync());
    }

    if (count != 1)
    {
        throw new MessagePackSerializationException();
    }

    reader.ReturnReader(ref streamingReader);
    await reader.BufferNextStructureAsync(context);
    MessagePackReader bufferedReader = reader.CreateBufferedReader();
    int seedCount = bufferedReader.ReadInt32();

    reader.ReturnReader(ref bufferedReader);
    return new SomeCustomType(seedCount);
}

There is one exception to the rule of returning readers before they an await expression: when that await expression is specifically for fetching more bytes to that same reader.