microsoft / microsoft/vs-streamjsonrpc

AddRemoteRpcTarget doesn't work with methods returning IAsyncEnumerable

Open
#791 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C#
Stars
937
Forks
178
Avg merge
3d 1h
Merged PRs (30d)
28

Description

When using AddRemoteRpcTarget to forward messages to another JsonRpc instance, calling methods that return an IAsyncEnumerable fails.
This is because JsonRpc.rpcTargetInfo.TryGetTargetMethod will always return a match for the special methods that are used to implement the async enumerable support.

This can easily be reproduced with the following code:

using Nerdbank.Streams;
using StreamJsonRpc;
using StreamJsonRpc.Protocol;
using System.Diagnostics;

var (clientStream, manInTheMiddleClientStream) = FullDuplexStream.CreatePair();
var (manInTheMiddleServerStream, serverStream) = FullDuplexStream.CreatePair();

JsonRpc clientRpc;

// client
{
    var clientFormatter = new JsonMessageFormatter();
    var clientHandler = new LengthHeaderMessageHandler(clientStream.UsePipe(), clientFormatter);
    clientRpc = new JsonRpc(clientHandler);
    clientRpc.TraceSource = new TraceSource("Client", SourceLevels.Verbose);
    clientRpc.StartListening();
}

// man in the middle
{
    var manInTheMiddleClientFormatter = new JsonMessageFormatter();
    var manInTheMiddleClientHandler = new LengthHeaderMessageHandler(manInTheMiddleClientStream.UsePipe(), manInTheMiddleClientFormatter);
    var manInTheMiddleClientRpc = new JsonRpc(manInTheMiddleClientHandler);
    manInTheMiddleClientRpc.TraceSource = new TraceSource("ManInTheMiddleClient", SourceLevels.Verbose);

    var manInTheMiddleServerFormatter = new JsonMessageFormatter();
    var manInTheMiddleServerHandler = new LengthHeaderMessageHandler(manInTheMiddleServerStream.UsePipe(), manInTheMiddleServerFormatter);
    var manInTheMiddleServerRpc = new JsonRpc(manInTheMiddleServerHandler);
    manInTheMiddleServerRpc.TraceSource = new TraceSource("ManInTheMiddlServer", SourceLevels.Verbose);

    manInTheMiddleClientRpc.AddRemoteRpcTarget(manInTheMiddleServerRpc);
    manInTheMiddleServerRpc.AddRemoteRpcTarget(manInTheMiddleClientRpc);

    manInTheMiddleClientRpc.StartListening();
    manInTheMiddleServerRpc.StartListening();
}

// server
{
    var serverFormatter = new JsonMessageFormatter();
    var serverHandler = new LengthHeaderMessageHandler(serverStream.UsePipe(), serverFormatter);
    var mserverRpc = new JsonRpc(serverHandler, new Server());
    mserverRpc.TraceSource = new TraceSource("Server", SourceLevels.Verbose);
    mserverRpc.StartListening();
}

// This one works
var result = await clientRpc.InvokeAsync<int>("DoSomethingAsync", 5);

// This doesn't work because JsonRpc.rpcTargetInfo.TryGetTargetMethod has a few hardcoded methods that are never passed to remote targets.
await foreach (var number in await clientRpc.InvokeAsync<IAsyncEnumerable<int>>("EnumerateAsync", 5))
{
    Console.WriteLine(number);
}

Console.ReadKey();

public interface IServer
{
    Task<int> DoSomethingAsync(int val);

    IAsyncEnumerable<int> EnumerateAsync(int val);
}

public class Server : IServer
{
    public Task<int> DoSomethingAsync(int val)
    {
        return Task.FromResult(val);
    }

    public async IAsyncEnumerable<int> EnumerateAsync(int val)
    {
        for (int i = 0; i < val; i++)
        {
            await Task.Yield();
            yield return i;
        }
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in JsonRpc.cs at rpcTargetInfo.TryGetTargetMethod, linked from the issue, and compare how normal calls and the special IAsyncEnumerable methods are resolved. Run the supplied client, middleman, and server reproduction, then verify that EnumerateAsync is forwarded through AddRemoteRpcTarget and yields all expected values.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.