dotnet / dotnet/orleans

ReferenceNotFoundException when using a converter against a foreign interface

Open
#8,554 1 comment 0 reactions 0 assignees View on GitHub
Needs: triage :mag:
Dominant language
C#
Stars
10.9k
Forks
2.1k
Avg merge
15h 1m
Merged PRs (30d)
345

Description

A `Orleans.Serialization.ReferenceNotFound` exception is thrown upon the deserialization of a grain response on the client. This error does not occur when running the behaviour on the silo itself, but only through a remote silo client. In this case the serializer in use is Protobuf.

## The Error
The exception message itself is `Reference with id 4 and type FluentResults.Error not found.`.

Relevant portion of the StackTrace:

at Orleans.Serialization.Codecs.ReferenceCodec.ReadReference[TInput](Reader`1& reader, Type fieldType, UInt32 reference)
at Orleans.Serialization.Codecs.ReferenceCodec.ReadReference[TInput](Reader`1& reader, Type fieldType)
at Orleans.Serialization.Codecs.ReferenceCodec.ReadReference[T,TInput](Reader`1& reader, Field field)
at Orleans.Serialization.Serializers.SurrogateCodec`3.ReadValue[TInput](Reader`1& reader, Field field)
at Orleans.Serialization.Codecs.ListCodec`1.ReadValue[TInput](Reader`1& reader, Field field)
at OrleansCodeGen.ProjectName.Orleans.Interfaces.Surrogates.Codec_GenericResultSurrogate`1.Deserialize[TReaderInput](Reader`1& reader, GenericResultSurrogate`1& instance) in C:\Users\UserAccount\Projects\ProjectName\libs\ProjectName.Orleans.Interfaces\Orleans.CodeGenerator\Orleans.CodeGenerator.OrleansSerializationSourceGenerator\ProjectName.Orleans.Interfaces.orleans.g.cs:line 1272
at Orleans.Serialization.ServiceCollectionExtensions.ValueSerializerHolder`1.Deserialize[TInput](Reader`1& reader, TField& value)
at Orleans.Serialization.Serializers.SurrogateCodec`3.ReadValue[TInput](Reader`1& reader, Field field)
at Orleans.Serialization.Codecs.IFieldCodec`1.Orleans.Serialization.Codecs.IFieldCodec.ReadValue[TInput](Reader`1& reader, Field field)
at Orleans.Serialization.Serializers.AbstractTypeSerializer.ReadValue[TInput](Reader`1& reader, Field field)
at Orleans.Serialization.Serializers.AbstractTypeSerializerWrapper`1.ReadValue[TInput](Reader`1& reader, Field field)
at Orleans.Serialization.Invocation.PooledResponseCodec`1.ReadRaw[TInput](Reader`1& reader, Field& field)
at Orleans.Runtime.Messaging.MessageSerializer.ReadBodyObject[TInput](Message message, Reader`1& reader)
at Orleans.Runtime.Messaging.MessageSerializer.TryRead(ReadOnlySequence`1& input, Message& message)
at Orleans.Runtime.Messaging.Connection.d__57.MoveNext()
at Orleans.Serialization.Invocation.ResponseCompletionSource`1.GetResult(Int16 token)
at System.Threading.Tasks.ValueTask`1.ValueTaskSourceAsTask.<>c.<.cctor>b__4_0(Object state)

## Scenario
What is being attempted is to use an `IConverter` to allow the (de)serialization of an object that is wrapped within a generic `Result` object from the `FluentResults` library:

```csharp
[RegisterConverter]
public sealed class GenericResultConverter : IConverter, GenericResultSurrogate>
{
public Result ConvertFromSurrogate(in GenericResultSurrogate surrogate)
=> new Result()
.WithValue(surrogate.Value)
.WithReasons(surrogate.Reasons);

public GenericResultSurrogate ConvertToSurrogate(in Result value)
=> new GenericResultSurrogate()
{
Reasons = value.Reasons,
Value = value.ValueOrDefault
};
}

[GenerateSerializer]
public struct GenericResultSurrogate
{
[Id(0)] public List Reasons;
[Id(1)] public T Value;
}
```

The problem is with the converter for the `IReason` type. From the behaviour I observed it seems like serialization works fine, but deserialization fails with the error mentioned above. The converter for `IError` types looks as follows (`IError` derives from `IReason`):

```csharp
[RegisterConverter]
public sealed class ReasonConverter : IConverter
{
public IReason ConvertFromSurrogate(in ReasonSurrogate surrogate)
{
if (surrogate.Reasons == null)
{
// Success!
return new Success(surrogate.Message)
.WithMetadata(surrogate.Metadata);
}
else
{
// Failure
var error = new Error(surrogate.Message)
.WithMetadata(surrogate.Metadata);

surrogate.Reasons.ForEach(reason => error.CausedBy(reason));

return error;
}
}

public ReasonSurrogate ConvertToSurrogate(in IReason value)
{
if (value is ISuccess s)
{
return new ReasonSurrogate
{
Message = s.Message,
Metadata = s.Metadata,
Reasons = null
};
}
else if (value is IError e)
{
return new ReasonSurrogate
{
Message = e.Message,
Metadata = e.Metadata,
Reasons = e.Reasons
};
}

throw new Exception("Eeehh....? That's awkward.");
}
}

[GenerateSerializer]
public struct ReasonSurrogate
{
[Id(0)] public string Message;
[Id(1)] public Dictionary Metadata;
[Id(2)] public List? Reasons;
}
```

I observed the `ConvertToSurrogate` method from the `ReasonConverter` being executed properly. Deserialization however does not work.

## Minimal Reproduction
A test case reproducing this failure against the Orleans codebase had been created and can be found here: https://github.com/corstian/orleans/pull/1. The gist of it is that converters against foreign interfaces do not seem to work correctly.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.