ReferenceNotFoundException when using a converter against a foreign interface
- 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
Assessment
This issue has not been assessed yet.