boot4life
boot4life

Reputation: 5324

Cannot deserialize ProtoBuf message in child process, works fine in parent

I'm trying to send a protobuf message to a child process. Deserialization works fine in the parent, like this:

var deserialized = Serializer.Deserialize(typeof(ChildProcessTestMessage), new MemoryStream(new byte[] { 8, 1 }));

[ProtoContract]
public class ChildProcessTestMessage
{
    [ProtoMember(1)]
    public int Int { get; set; }
}

I have hardcoded the bytes 0x0801 in this sample code and in the child process in order to make sure that the bytes are correct. I obtained these bytes by serializing a test instance.

Deserialization code in the child is different in one important way: The parent communicates the type to use for deserialization over a pipe. The type can be loaded successfully in the child. I then call protobuf-net like this:

var (assemblyName, typeName, methodName, argument) = protocol.ReadExecuteMethod();

Assembly assembly;
try
{
    var name = new AssemblyName(assemblyName);
    assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Directory.GetCurrentDirectory(), name.Name + ".dll"));
}
catch (Exception ex)
{
    //...
}

var type = assembly.GetType(typeName);
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);

var parameterType = method.GetParameters().Single().ParameterType;

var message = Serializer.Deserialize(new MemoryStream(new byte[] { 8, 1 }), parameterType);

The child reports the following error:

Failed to invoke method 'TestMethod': ProtoBuf.ProtoException: Invalid wire-type (Varint); this usually means you have over-written a file without truncating or setting the length; see Using Protobuf-net, I suddenly got an exception about an unknown wire-type at ProtoBuf.ProtoReader.State.ThrowProtoException(String message) in //src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 764
at ProtoBuf.ProtoReader.State.ThrowWireTypeException() in /
/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 758
at ProtoBuf.Internal.PrimaryTypeProvider.ProtoBuf.Serializers.ISerializer<System.Type>.Read(State& state, Type value) in //src/protobuf-net.Core/Internal/PrimaryTypeProvider.Primitives.cs:line 292 at ProtoBuf.ProtoReader.State.g__ReadFieldOne|102_0[T](State& state, SerializerFeatures features, T value, ISerializer1 serializer) in /_/src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1075 at ProtoBuf.ProtoReader.State.ReadAsRoot[T](T value, ISerializer1 serializer) in //src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1059
at ProtoBuf.ProtoReader.State.DeserializeRoot[T](T value, ISerializer`1 serializer) in //src/protobuf-net.Core/ProtoReader.State.ReadMethods.cs:line 1036
at ProtoBuf.Serializer.Deserialize[T](Stream source, T value, Object userState, Int64 length) in /
/src/protobuf-net/Serializer.Deserialize.cs:line 43 at ChildProcess.Program.Main(String[] args) in ...\Program.cs:line 324

I have added debug output to make sure that parameterType == typeof(ChildProcessTestMessage).

I'm currently at a loss at why it's not working and how to debug this. There must be a difference in the child process somehow but I have no lead what it could be.

One idea was that maybe assembly loading is different. Could this manifest as this kind of failure?

This is on .NET Core 5.

I'd appreciate your help.


Update: I have now hardcoded a copy of that proto contract into the child. Everything coming over the wire is now ignored. The deserialization call looks like in the parent. Same error as before...

The child process exe is a project in this solution. I'm invoking it from a unit test project, and it lives in the same directory as the unit test DLL and current directory. Could this mess up the child? There are lots of files there which the child maybe didn't expect.

Upvotes: 1

Views: 604

Answers (1)

boot4life
boot4life

Reputation: 5324

OK, this was a pretty nasty but interesting bug. I kept bisecting by trying to remove differences between the parent and the child. So I kept hardcoding more deserializing code pasting it as the first line in Main. That worked, yet at a later point in the program the (seemingly!) same code didn't.

When I brought the working and the non-working code closer and closer, until the lines were adjacent, I finally spotted the issue:

When you say Serializer.Deserialize(type, stream) it picks a different overload than when you say Serializer.Deserialize(stream, type). Both will compile, but the latter will use type as the "prototype" object. This will cause the library to try to deserialize stream as a System.Type instance. This is a system type that cannot be deserialized. But protobuf-net attempted to do so anyway finding that the serialized bytes and the type structure are incompatible.

So in the end, all of this was caused by switched argument order. It had nothing to do with being in a child process.

Upvotes: 1

Related Questions