5

I get an exception trying to serialize object graph (not very deep). Meaningful part of it is like this:

[ERROR] FATAL UNHANDLED EXCEPTION: ProtoBuf.ProtoException: Possible recursion d etected (offset: 5 level(s)): red at ProtoBuf.ProtoWriter.CheckRecursionStackAndPush (object) <0x00127> at ProtoBuf.ProtoWriter.StartSubItem (object,ProtoBuf.ProtoWriter,bool) <0x0002f>

The graph represents file/directory structure and my model (simplified) looks like this:

[ProtoContract] 
[ProtoInclude(100, typeof(PackageDirectory))]
[ProtoInclude(200, typeof(PackageFile))]
public abstract class PackageMember
{
   [ProtoMember(1)] 
   public virtual string Name { get; protected set; }

   [ProtoMember(2, AsReference=true)] 
   public PackageDirectory ParentDirectory { get; protected set; }  
}

[ProtoContract]
public class PackageDirectory : PackageMember
{
   [ProtoMember(3)]
   private Dictionary<string, PackageMember> _children;

   public PackageDirectory()
   {
      _children = new Dictionary<string, PackageMember>();
   }

   public PackageDirectory (string name, PackageDirectory parentDirectory)
      : this()
   {
      this.ParentDirectory = parentDirectory;
      this.Name = name;         
   }

   public void Add (PackageMember member)
   {
      _children.Add(member.Name, member);
   }
}

[ProtoContract]
public class PackageFile : PackageMember
{
   private Stream _file;
   private BinaryReader _reader;

   private PackageFile() 
   {}

   public PackageFile (string name, int offset, int length, PackageDirectory directory,  Stream file)
   {
      this.Name = name;
      this.Length = length;
      this.Offset = offset;
      this.ParentDirectory = directory;

      _file = file;
      _reader = new BinaryReader(_file);
   }

   [OnDeserialized]
   protected virtual void OnDeserialized(SerializationContext context)
   {
      var deserializationContext = context.Context as DeserializationContext;

      if (deserializationContext != null)
      { 
         _file = deserializationContext.FileStream;
         _reader = new BinaryReader(_file);
      }
   }

   [ProtoMember(3)]
   public int Offset { get; private set; }

   [ProtoMember(4)]
   public int Length { get; private set; }
}

The depth of this tree is near 10-15 levels, which less than ProtoBuf.ProtoWriter.RecursionCheckDepth value (25). (So maybe this is a bug?) The version of protobuf-net used is one compiled from trunk v2 (rev 491).

Actually, i solved it with modification of protobuf-net code. I changed value of ProtoBuf.ProtoWriter.RecursionCheckDepth to 100 and everything seems to be ok.

The question is if there any "true" way to serialize such kind of graph without modification of protobuf code? Is such a behavior correct or it's a bug?

My platform is Mono-2.10-8 on Windows 7 Professional 64-bit

P.S. Also i found that if i deserizlie with thw following code, i should have PackageDirectory parameterless constructor to be public.

var value = new PackageDirectory();
RuntimeTypeModel.Default.Deserialize(ms, value, typeof(PackageDirectory), new SerializationContext {
   Context = new DeserializationContext {
   FileStream = _file,
}});

It's another topic but it's well illustrated with presented code. I think that in this case declaring private constructor should be allowed because now the behavior differs from one for Serializer.Deserialize(...).

ILya
  • 2,670
  • 4
  • 27
  • 40
  • The constructor issue sounds odd and unexpected - again, would need repro; what platform is this running on? regular .NET? or...? (SL and CF have some subtle differences here, due to platform limitations) – Marc Gravell Apr 09 '12 at 09:03
  • @MarcGravell, Sorry for incomplete information, i forgot to specify my platform. It's mono. I'll edit a question. – ILya Apr 09 '12 at 15:51
  • mono on...? Linux? iOS? Also: which **exact** version number are you using? – Marc Gravell Apr 09 '12 at 17:23
  • Mark, as i promissed, i did an update of my question right after the comment. You probably missed it. The version is already specified, i repeat it here: Mono-2.10-8 on Windows 7 Professional 64-bit – ILya Apr 09 '12 at 17:44
  • And the exact protobuf-net version? – Marc Gravell Apr 09 '12 at 17:52
  • As i pointed, it's today trunk version, i think it's v2.0 rev 491. I'll put it into question too. Should be more precise – ILya Apr 09 '12 at 17:59
  • Because directory is-a-member-of a dictionary is-a-member-of a directory, the recursive depth of actual objects is at least twice the depth of the directory tree you're modelling. – Ben Voigt Apr 09 '12 at 18:11
  • @BenVoigt, sure you are right, but is the described behavior is still ok? I think, it's reasonable, but it's still unclear, what to do in my case. – ILya Apr 10 '12 at 00:09

1 Answers1

6

This exception is thrown only when the same reference is seen in the data (twice in the same path), and tracking is only enabled when the depth is at least RecursionCheckDepth. Which immediately makes me suspicious of the 10-15 depth limit cited, although it is not necessarily the case that protobuf handles levels quite the same as you are counting. It does not make sense to me that raising this number to 100 should make it work - in fact, the very existence of this RecursionCheckDepth is purely an optimisation to limit the effort involved in "typical" graphs, only enabling the more rigorous checking if it starts to look deep.

I am, however, mindful that this might also suggest some subtle bug in inheritance-based handling, perhaps also related to AsReference. I use protobuf-net extensively and constantly, and I haven't seen such an issue. If you have a reproducible repro I would very much like to see it.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks for your reply! Changing RecursionCheckDepth was only a temporary solution to allow me move further with my project, so it's not really an option... I'll prepare reproduceable example of an error tomorrow and will share it somewhere. – ILya Apr 09 '12 at 15:56
  • You're actually trying to detect *cycles*, right? Recursion seems completely normal, although it too could be harmful if deep enough. (linked list anyone?) – Ben Voigt Apr 09 '12 at 18:09
  • @BenVoigt yes, it is cycles that are the issue here (since this is a tree-based serializer, with limited optional reference-tracking support) – Marc Gravell Apr 09 '12 at 18:17
  • @Marc: Then the error message and documentation should probably talk about *cycles*, as in "Cycle detected". And the offset in recursion depth is the *length of the cycle*. – Ben Voigt Apr 09 '12 at 18:28
  • @Marc: I don't mean to denigrate the excellent work you've done with protobuf. Please consider this a suggested improvement. – Ben Voigt Apr 09 '12 at 20:45
  • I have the same problem with Entity Framework Self Tracking entities during serialization. For example, a person as one or more addresses and each address relates to the person. I don't know yet how to tell EF to not reference its parent in a related entity when eager loading the related entities. –  Jul 09 '12 at 09:14
  • 2
    Just solved my circular reference problem. I've added "AsReference = true" to the ProtoMember attribute when the property is a navigation property. –  Jul 09 '12 at 12:58
  • 1
    @MarcGravell Since the "AsReference" property is now obsolete, what would be an alternative for this ? – Anu Viswan Jan 08 '22 at 03:14
  • 1
    @AnuViswan my recommendation is to normalize that protobuf is a tree serializer, and therefore not try to serialize cyclic graphs. Keep in mind that it is fairly normal to have a separate domain model and serialization model, so: even if your domain model is cyclic: the serialization doesn't need to be – Marc Gravell Jan 08 '22 at 10:02