This is the code to replicate the issue. Basically it fails on deserialization, with error "A reference-tracked object changed reference during deserialization". It's kind of interesting, if I remove the following line from IExecEnv setup:
metaType.AsReferenceDefault = true;
then the test passes. But this sounds odd. Is it a limitation of Protobuf-net?
using ProtoBuf;
using ProtoBuf.Meta;
namespace QRM.Analytics.Serialization.Commit.Tests;
public class UnitTest1
{
private interface IExecEnv
{
int GetId();
}
[Immutable]
private class ExecEnv : IExecEnv
{
public static readonly ExecEnv DefaultEnv = new ExecEnv(1);
private readonly int _id;
private ExecEnv(int id)
{
_id = id;
}
public int GetId()
{
return _id;
}
}
[Serializable]
private sealed class ExecEnvSurrogate
{
[ProtoConverter]
public static IExecEnv FromSurrogate(ExecEnvSurrogate surrogate)
{
if (surrogate == null)
return null;
return ExecEnv.DefaultEnv;
}
[ProtoConverter]
public static ExecEnvSurrogate ToSurrogate(IExecEnv env)
{
if (env == null)
return null;
return new ExecEnvSurrogate();
}
}
private class WithExecEnv
{
private IExecEnv _env;
public WithExecEnv(IExecEnv env)
{
_env = env;
}
public IExecEnv Env => _env;
}
private void SetupModel()
{
var metaType = RuntimeTypeModel.Default.Add(typeof(IExecEnv), false);
metaType.AsReferenceDefault = true;
var metaType3 = RuntimeTypeModel.Default.Add(typeof(WithExecEnv), false);
metaType3.UseConstructor = false;
metaType3.AddField(1, "_env");
var metaType4 = RuntimeTypeModel.Default.Add(typeof(ExecEnvSurrogate), false);
metaType4.UseConstructor = false;
metaType.SetSurrogate(typeof(ExecEnvSurrogate));
}
[Test]
public void CloneExecEnvWithSurrogate()
{
SetupModel();
var withExecEnv = new WithExecEnv(ExecEnv.DefaultEnv);
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, withExecEnv);
stream.Seek(0, SeekOrigin.Begin);
var clone = Serializer.Deserialize<WithExecEnv>(stream);
Assert.NotNull(clone);
Assert.Equal(withExecEnv.Env.GetId(), clone.Env.GetId());
}
}
}
Yes, reference-tracking is ... a massive PITA; it works in some limited scenarios, but there are situations where it becomes unreliable. It also isn't part of the core protobuf specification, but is hacked on top at the library level. For these reasons, it is not recommended, and is deprecated going forward; it cannot be made reliable, and I'd rather not encourage people to get into dangerous territory.