Apm-agent-dotnet: "Memory leak" in SqlEventListener

Created on 14 May 2020  路  5Comments  路  Source: elastic/apm-agent-dotnet

We're experiencing memory growth issues when using the SqlClientDiagnosticSubscriber.
Took a dump of the running process and analyzed it in dotMemory.
Turns out a lot memory is allocated by _processingSpans.

dotmemory

When looking at the code I see that "ended" spans are never removed from the dictionary.
Perhaps there is a good reason to leave the span in the dictionary after it has ended. I don't know.
But if there isn't, and the span should be removed from the dictionary, then a quick fix would be to replace
if (!_processingSpans.TryGetValue(id, out var item))
with
if (!_processingSpans.TryRemove(id, out var item))

private void ProcessEndExecute(IReadOnlyList<object> payload)
{
    if (payload.Count != 3)
    {
        _logger?.Debug()
            ?.Log("EndExecute event has {PayloadCount} payload items instead of 3. Event processing is skipped.", payload.Count);
        return;
    }

    var id = Convert.ToInt32(payload[0]);
    var compositeState = Convert.ToInt32(payload[1]);
    var sqlExceptionNumber = Convert.ToInt32(payload[2]);
    var stop = Stopwatch.GetTimestamp();

    _logger?.Trace()
        ?.Log("Process EndExecute event. Id: {Id}. Composite state: {CompositeState}. Sql exception number: {SqlExceptionNumber}.", id,
            compositeState, sqlExceptionNumber);

    if (!_processingSpans.TryGetValue(id, out var item))
    {
        _logger?.Warning()
            ?.Log("Failed capturing sql statement (failed to remove from ProcessingSpans).");
        return;
    }

    var isSuccess = (compositeState & 1) == 1;
    var isSqlException = (compositeState & 2) == 2;
    // 4 - is synchronous

    item.Span.Duration = ((stop - item.Start) / (double)Stopwatch.Frequency) * 1000;

    if (isSqlException)
    {
        item.Span.CaptureError("Exception has occurred", sqlExceptionNumber != 0 ? $"SQL Exception {sqlExceptionNumber}" : null,
            null);
    }

    item.Span.End();
}
bug

All 5 comments

Thanks for the analysis @nilsgstrabo!

When looking at the code I see that "ended" spans are never removed from the dictionary.

Great catch - yeah I also think that's the issue.

Perhaps there is a good reason to leave the span in the dictionary

I don't think there is a good reason - I also think that's the bug.

So, overall I agree with your analysis and suggestion.

I opened #851 which fixes this. Will push a patch for this very soon.

Patch release solving this is out.

I close this now.

@nilsgstrabo it'd be nice if you could verify that the issue is indeed fixed in your environment - you can just leave a comment here.

If it's not fixed feel free to reopen.

Thank you @gregkalapos! I will test tomorrow and let you know.
Really appreciate your quick response!

Can confirm that 1.5.1 fixes the memory issue @gregkalapos
Tested 1.5.0 and 1.5.1 with the same requests, and as you can see the spans are successfully removed from the dictionary (as expected).

With version 1.5.0
apm_1_5_0

With version 1.5.1
apm_1_5_1

Thanks for confirming @nilsgstrabo.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

SergeyKleyman picture SergeyKleyman  路  8Comments

SergeyKleyman picture SergeyKleyman  路  8Comments

luisfernandomoraes picture luisfernandomoraes  路  7Comments

graphaelli picture graphaelli  路  8Comments

gregkalapos picture gregkalapos  路  5Comments