[CSHARP-1296] StackOverflowException when serializing a class C that implements IEnumerable<C> Created: 23/May/15  Updated: 13/Apr/16  Resolved: 26/May/15

Status: Closed
Project: C# Driver
Component/s: Serialization
Affects Version/s: None
Fix Version/s: 2.1

Type: Bug Priority: Minor - P4
Reporter: Lenny Granovsky Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: File JObject_bug.linq    
Issue Links:
Depends
Related
is related to CSHARP-1385 Unable to explicitly set serializer f... Closed
is related to CSHARP-1281 Integrate with Json.NET Closed

 Description   

If the object that is being inserted has a dynamic type with a JObject type in it (this is the default type used by JSON.NET for deserialize the JSON), then the InsertOneAsync method throws the StackOverflowException.

It would be OK if driver does not support JObject and throws an appropriate exception, but since the StackOverflowException crashes the application and is very hard to debug, at very least the driver should not cause this type of exception.

This is the code to replicate it:

async Task Main()
{
string msg = @"

{ ""PayrollId"":463236,""Message"":""Fine"",""Success"":true,""MessageCode"":200,""IsError"":false }

";
dynamic details = Newtonsoft.Json.JsonConvert.DeserializeObject(msg);

var data = new DomainEventData()

{ AggregateId = Guid.NewGuid(), TenantId = Guid.NewGuid(), AggregateName = "Just a test", EventName = "payrollunit-schedule-missed", Details = details }

;

MongoClient client = new MongoClient("mongodb://localhost/tests");
IMongoDatabase mongo = client.GetDatabase("tests");
IMongoCollection<DomainEventData> items = mongo.GetCollection<DomainEventData>("entries");

await items.InsertOneAsync(data);
}

// Define other methods and classes here
[Serializable]
public class DomainEventData
{
public Guid Id

{ get; set; }
[BsonElement("eventName")]
public string EventName { get; set; }

[BsonElement("tenantId")]
public Guid TenantId

{ get; set; }
[BsonElement("aggregateId")]
public Guid AggregateId { get; set; }

[BsonElement("aggregateName")]
public string AggregateName

{ get; set; }
[BsonElement("details")]
public dynamic Details { get; set; }

[BsonElement("utcTimestamp")]
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
public DateTime UtcTimestamp

{ get; set; }

public DomainEventData()

{ this.Id = Guid.NewGuid(); this.TenantId = Guid.Empty; this.AggregateId = Guid.Empty; this.EventName = string.Empty; this.UtcTimestamp = DateTime.UtcNow; this.AggregateName = string.Empty; }

}

BTW, changing the:
dynamic details = Newtonsoft.Json.JsonConvert.DeserializeObject(msg);
to:
dynamic details = Newtonsoft.Json.JsonConvert.DeserializeObject<ExpandoObject>(msg);
solves the issue, but if application is dealing with unknown data coming in, this might not be an option.



 Comments   
Comment by Githook User [ 26/May/15 ]

Author:

{u'username': u'rstam', u'name': u'rstam', u'email': u'robert@robertstam.org'}

Message: CSHARP-1296: Delay more serializer lookups to avoid stack overflows.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/c6ebb182517e128d1bd37da8a6970f3c7d4f6168

Comment by Githook User [ 26/May/15 ]

Author:

{u'username': u'rstam', u'name': u'rstam', u'email': u'robert@robertstam.org'}

Message: CSHARP-1296: Add delayed lookup of child serializers to prevent stack overflows when there are cyclic types.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/51587ec74f50e80578d4575735a4f2f3e73f76f0

Comment by Lenny Granovsky [ 23/May/15 ]

Thank you for the explanation. It is good to know the actual reason. I don't see any problem with not supporting JObject by default. The only issue is the StackOverflow by itself. Maybe going into limited number of loops and throwing an exception upon that limit has more advantage. Based on documentation, the MongoDB document supports up to 100 nested levels, therefore it seems like a natural limit to stop and throw an exception after 100 recursive iterations.

I'm sure you'll find the best option, I appreciate fixing the bug itself.

Comment by Robert Stam [ 23/May/15 ]

The problematic type for us is JToken, which is declared as:

public abstract class JToken : IEnumerable<JToken>, ...

The problem can be reproduced with any type that implements IEnumerable of itself:

public class C: IEnumerable<C>

In the process of constructing a serializer for C we recursively create a serializer for the IEnumerable's items. But when the items are of the same type we get stuck in an infinite recursion loop.

Comment by Craig Wilson [ 23/May/15 ]

Thanks Lenny. Yes, a stackoverflow is not good. We'll figure it out. However, we can't support just any dynamic type because we don't know that the callsite will be in C#. However, a serializer can be registered for JObject. As part of CSHARP-1281, we'll be including a Json.NET serializer to handle this.

For this ticket, I'll make sure we fix the stackoverflow problem. Thanks for the report,
Craig

Comment by Lenny Granovsky [ 23/May/15 ]

The file with code attached

Generated at Wed Feb 07 21:39:12 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.