Csvhelper: Stackoverflow Exception on AutoMapMembers

Created on 8 Jan 2019  路  25Comments  路  Source: JoshClose/CsvHelper

I didn't actually hit a stackoverflow exception after running this code for like an hour. I assume eventually it will occur. Something is causing a circular dependency. I spent some trying to debug with no luck. If you provide me some guidance, I can create a full repro. In the meantime, here is just a snapshot of the stacktrace while it just keeps growing.

   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapMembers(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMapConstructorParameters(ClassMap map, Configuration configuration, LinkedList`1 mapParents, Int32 indexStart)
   at CsvHelper.Configuration.ClassMap.AutoMap(Configuration configuration)
   at CsvHelper.Configuration.Configuration.AutoMap(Type type)
   at CsvHelper.CsvWriter.WriteHeader(Type type)
   at CsvHelper.CsvWriter.WriteRecords[T](IEnumerable`1 records)

All 25 comments

Can you provide the class structure that it's trying to map?

@JoshClose I'm having a really difficult time creating a repro... this is a true heisenbug!

The model that I'm trying to write has a single ctor that accepts a single object.

This "object" has property that links to another object....which links to another object, etc. Just your standard entity framework models, nothing really special, no crazy reflection etc.

Every time I attempt to narrow down the chain, and eliminate every property that is causing the infinite loop, I just inevitably end up at the start of the chain again. I realize this is probably an awful explanation, and I'm sorry for that.

Anyway, if you have any suggestions for how to narrow this down or if I should attempt to log certain lines in csvhelper, let me know.

Do you want to email me an example so it isn't public?

Meaning, the full class structure that causes the failure.

@JoshClose have you found anything on this issue yet?

We've recently begun hitting SIGSEGV faults that appear to be related to the inclusion of a ClassMap when deserializing. When we comment out the registration of the ClassMap, we don't get the issue, but when we include it, we typically do.

It appears to be something that's timing related, as there isn't a solid repro case, and the frequency of hitting it increases as we spin up more threads doing simultaneous parsing. For example, running 12 threads right now repeatedly, parsing the same files over and over, I'll hit the SIGSEGV approximately 75% of the runs.

I'm trying to better info, but unfortunately .NET Core doesn't throw a StackOverflowException - just hands back the dump, which has precious little info!

Can you reproduce it while debugging? If so, the PDB files are on NuGet. You can set your symbols server and debug into the source code.

https://docs.microsoft.com/en-us/visualstudio/debugger/specify-symbol-dot-pdb-and-source-files-in-the-visual-studio-debugger?view=vs-2017

@JoshClose yes, I just pulled in the entire repo and dumped a ton of logs in there. I'm not knowledgeable enough about the code to know what I should be looking for though. All I know is that method kept cycling in a loop, albeit over a sequence of different reflected types.

Do you have some code you could zip up and email me so I can reproduce it?

For my part, I have not yet been able to reproduce it consistently. I don't believe there are any shared objects that I'm using across the threads, yet the number of threads running at once (each operating on separate CSV's) seems to have a significant impact on the likelihood of hitting the SIGSEGV.

I'm feeling like the issue I'm chasing here isn't related to the one originally reported by @bradmarder. His is happening on writes, mine on reads. It also doesn't seem related to using a ClassMap any longer - I've been able to hit it without that.

@JoshClose just for my sanity - is the library thread-safe at a module level? Meaning, no shared state across instances (statics/globals), etc.?

@philipflesher I would have to go through and do an audit. There should be locks in places where there are statics, but it's possible I've added something at some point and forgot it.

Could you point me to any specific places in code where you might suspect a recursive dive? Or maybe some of the static/shared variables I could put specific watches on?

This is a place when auto mapping that it could happen. https://github.com/JoshClose/CsvHelper/blob/6b339704fe22eb8c90b2d3da6e8470561d7c708a/src/CsvHelper/Configuration/ClassMap.cs#L145

There is a CheckForCircularReference method that is called to ensure that doesn't happen. There could be a bug there though.

We've just come across this same one whilst writing, and we've resolved it by adding in a parameterless public constructor to our class for the fields and it solved the issue.

If you remove the Public Sub New() then you it still fail with the exception

We ran it through with the CsvHelpersource code and it's in the automap constructor that then gets caught in a loop doing the AutoMapMembers

Private Class ExportItem
        Public Property StartDate As String
        Public Property Duration As Double
        Public Property MediaId As Integer
        Public Property Title As String
        Public Property ArtistName As String
        Public Property StationId As Integer
        Public Property StationName As String
        Public Property Location As String
        Public Property LocationNetworkName As String
        Public Property Login As String
        Public Property OnAir As String
        Public Property PlayMode As String
        Public Property Preview As Boolean
        Public Property Reference As String
        Public Property Player As Integer
        Public Property Hardware As String
        Public Sub New()
        End Sub
        Public Sub New(playlogVm As PlayLogItemViewModel)
            With playlogVm
                Me.StartDate = .PlayLog.StartDateTimeUtc.ToString("o")
                Me.Duration = .PlayLog.PlayedDuration.TotalSeconds
                Me.MediaId = .PlayLog.MediaId
                Me.Title = .ItemTitle
                Me.ArtistName = .FirstArtistName
                Me.StationId = .PlayLog.StationId
                Me.StationName = .StationName
                Me.Location = .LocationName
                Me.LocationNetworkName = .LocationNetworkName
                Me.Login = .PlayLog.MyriadLoginName
                Me.OnAir = .OnAirCaption
                Me.PlayMode = .PlayLog.PlayMode.ToString()
                Me.Preview = .PlayLog.PreviewMode
                Me.Reference = .PlayLog.ExtSchedulerReference
                Me.Player = .PlayLog.PlayerNumber
            End With
        End Sub
    End Class

    Public Sub ExportToCSV
            Using writer As TextWriter = File.CreateText(filename)
                    Using csvWriter As New CsvHelper.CsvWriter(writer)
                        With csvWriter.Configuration
                            .HasHeaderRecord = True
                        End With
                        csvWriter.WriteHeader(GetType(ExportItem))
                        Await csvWriter.NextRecordAsync()

                        For Each item In .Results
                            Try
                                csvWriter.WriteRecord(New ExportItem(item))
                                Await csvWriter.NextRecordAsync()
                            Catch ex As Exception

                            End Try
                        Next
                    End Using
                    writer.Close()
                End Using
     End Sub

What is the definition for PlayLogItemViewModel?

Are you able to create a unit test that fails?

Hello

Same issue: I have a class with 20 properties (only int, string and DateTime)
A csv.WriteRecords(..) call and it crashes with stackoverflow.

I tried a mapper class (and registered it in the configuration) with only having a AutoMap() in the constructor:
=> it crashes with stackoverflow during the AutoMap

Then I mapped every single property ("Map(m => m.Name")) in the mapper class instead of the AutoMap.
=> it works

Next try (like Mostlypyjamas said):
Instead of the mapper class, I only added a parameterless constructor to the data class
=> it works

Greetings
Robert

I'm running into this with this class: https://gist.github.com/eltiare/54b0bf78ec16adb9027c7f85a1ae1368

Everything works fine until I go to write an instance of OrderExport.

Found a way of creating a stack overflow with the following code (and using AutoMap):

public class BiOperation
{
    private readonly Operation _operation;

    public BiOperation(Operation operation)
    {
        _operation = operation;
    }

    public string Resource => _operation.Resource;
}

Stackoverflow exception on self referencing class with property of other class:

private class C
{
    public string Id { get; set; }
}

private class SelfCircularZ
{
    public C Other { get; set; }

    public SelfCircularZ Circular { get; set; }
}

[TestMethod]
public void SelfCircularZ()
{
    var config = new CsvHelper.Configuration.Configuration();
    config.AutoMap<SelfCircularZ>();
}

Field Other of custom class C causes stackoverflow, if property is simple type (like string) - no problems.

This line has 0 mapParents and CheckForCircularReference is not skipping processing:
https://github.com/JoshClose/CsvHelper/blob/2914f6856febbf7488c53ed65948a00a39f95a22/src/CsvHelper/Configuration/ClassMap.cs#L297

I've also run into this problem recently.
When trying to AutoMap() a class with complex dependencies (ex: Entity Framework model class), it would go deep down until StackOverflowException is thrown at:

CsvHelper/src/CsvHelper/Configuration/ClassMap.cs

with indexStart=1786260400

My test code is something like this:
````
public class MyCsvMap : ClassMap
{
public MyCsvMap()
{
AutoMap();
Map(e => e.object1).Ignore();
// Ignore other fields ...
}
}

//Test block
var csvConfig = new CsvHelper.Configuration.Configuration();
csvConfig.RegisterClassMap();
````

Also having this issue. Whilst tracing it, I found a typo (paramter)...

if (CheckForCircularReference(parameter.ParameterType, mapParents))
                    {
                        throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." +
                                                              "Since all parameters must be supplied for a constructor, this parameter can't be skipped.");
                    }

I think the issue is simple to reproduce by having a property that is typed as a Uri. I've worked around it by temporarily using a string instead.

I'm getting this on AutoMap() and I'm thinking due to the comments above that it is due to the presence of an Exception member, albeit with an [Ignore] attribute, on the class in question. There could potentially be infinite recursion on the InnerException field, but I'm not sure why it ignores the [Ignore].

I'm getting this StackOverflow on WriteRecords as well.

Simplest class:

public class ReconcileRow
{
    public int RegistrationId => Registration.Id;

    [Ignore]
    public Registration Registration { get; set; }
}

The Registration object has properties that have references to the Registration. Regardless of whether I use the [Ignore] attribute, or a ClassMap with Map(x => x.Registration).Ignore(), it causes a StackOverflow.

The confusing part is this version of the class ALSO causes a StackOverflow:

public class ReconcileRow
{
    public ReconcileRow(Registration reg)
    {
        RegistrationId = reg.Id;
    }

    public int RegistrationId { get; set; }
}

I don't see why it would need to reflect into the Registration class in order to write these records! Very bizarre to me..

Changing the writing to be like this:

        var csv = new CsvWriter(writer);
        csv.Configuration.IgnoreReferences = true;
        csv.WriteRecords(result);

Does work - but of course with this I can't use any reference types.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

viveknuna picture viveknuna  路  23Comments

Codehhh picture Codehhh  路  15Comments

jzabroski picture jzabroski  路  51Comments

tdjastrzebski picture tdjastrzebski  路  31Comments

dshevani picture dshevani  路  24Comments