Skip to content

IDisposable interface ambiguity on receiver side when marshaling with RpcMarshalableAttribute #931

Open
@Jemmy1228

Description

@Jemmy1228

Summary

This is an issue related to the RpcMarshalableAttribute which is proposed in #774 and merged in #777
According to my understanding, the IDisposable interface is ambiguous on the receiver side when marshaling with RpcMarshalableAttribute.

Example

Let's think of a scenario that the ITest interface should be marshaled instead of serialized.

[RpcMarshalable]
public interface ITest : IDisposable
{
    /* Omitted */
}

The RPC server (sender):

public interface IServer
{
    Task<ITest> GetTest();
}

And the RPC client (receiver) uses it as:

ITest test = await jsonRpc.GetTest();
/* Do something with the ITest */
test.Dispose();

Problem

RpcMarshalableAttribute required the marshaled interface to derive from IDisposable interface (to dispose the proxy?).
It seems to me that, the Dispose() method called on the receiver side would cause two things to happen:

  1. Disposal of the generated proxy on the receiver side, causing JsonRpc on both sides mark the RPC handle as disposed, and the sender side removes the corresponding Object out of context.
  2. Calling IDisposable.Dispose() on the original object on the sender side.

So, calling Dispose() method on the receiver side is rather ambiguous.
Sometimes we only want to dispose the proxy on receiver side without disposing the original object on sender side. However, I don't know how to achieve this.

Probable Solution?

How about defining a dedicated interface for marshaling like this:

public interface IRpcMarshalable
{
    /* This method should be overridden by the Proxy generator on receiver side */
    void DisposeProxy()
    {
        throw new InvalidOperationException("Disposal of proxy should only be done on the receiver side.");
    }
}

And require any interface to be marshaled derive from this interface:

public interface ITest : IRpcMarshalable
{
    /* Omitted */
}

The IRpcMarshalable interface has a default implementation of DisposeProxy(), which won't be overridden on the sender side.
The receiver side proxy generate should override DisposeProxy() method to dispose the proxy and handle in JsonRpc context.

With this approach, IDisposable won't be a required interface to derive from, and the ambiguity disappears.

Remind receiver to dispose proxies

In #774 (comment) concerned about to remind the receiver to dispose the proxies

I think we should start with requiring any additional interfaces to derive from IDisposable, since that makes it more obvious to the receiver that they should dispose of these proxies. If we need to we can always remove that requirement, but adding it later would be a breaking change, so I prefer to start conservatively.

Maybe we can write an Roslyn Code Analyzer to warn that these proxies should be disposed?

If this approach is acceptable, I would be happy to implement it and create a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions