Skip to content

Heavy GC thrashing in TdsParser #2120

@Havunen

Description

@Havunen

Describe the bug

Fetching a row from database which has varchar(max) typed column and contains about 1Mb of text causes ~8GB of garbage collector pressure.

I believe this issue is related to: #1864

As you can see from the screenshot of our real application this single method of this library dominates the memory usage:

image

Exception message:
Stack trace (v5.2.0-preview3.23201.1):

> System.Char[]
  Objects : n/a
  Bytes   : 1751536296

>99,9%  Rent • 1,63 GB / 1,63 GB • System.Buffers.TlsOverPerCoreLockedStacksArrayPool<T>.Rent(Int32)
  >99,9%  TryReadPlpUnicodeChars • 1,63 GB / - • Microsoft.Data.SqlClient.TdsParser.TryReadPlpUnicodeChars(Char[], Int32, Int32, TdsParserStateObject, Int32, Boolean, Boolean)
    >99,9%  TryReadSqlStringValue • 1,63 GB / - • Microsoft.Data.SqlClient.TdsParser.TryReadSqlStringValue(SqlBuffer, Byte, Int32, Encoding, Boolean, TdsParserStateObject)
      >99,9%  TryReadSqlValue • 1,63 GB / - • Microsoft.Data.SqlClient.TdsParser.TryReadSqlValue(SqlBuffer, SqlMetaDataPriv, Int32, TdsParserStateObject, SqlCommandColumnEncryptionSetting, String, SqlCommand)
        >99,9%  TryReadColumnInternal • 1,63 GB / - • Microsoft.Data.SqlClient.SqlDataReader.TryReadColumnInternal(Int32, Boolean, Boolean)
          >99,9%  ReadAsyncExecute • 1,63 GB / - • Microsoft.Data.SqlClient.SqlDataReader.ReadAsyncExecute(Task, Object)
            >99,9%  ContinueAsyncCall • 1,63 GB / - • Microsoft.Data.SqlClient.SqlDataReader.ContinueAsyncCall<T>(Task, SqlDataReader+SqlDataReaderBaseAsyncCallContext<T>)
              >99,9%  InnerInvoke • 1,63 GB / - • System.Threading.Tasks.ContinuationResultTaskFromResultTask<TAntecedentResult, TResult>.InnerInvoke()
                >99,9%  <.cctor>b__272_0 • 1,63 GB / - • System.Threading.Tasks.Task+<>c.<.cctor>b__272_0(Object)
                  >99,9%  RunFromThreadPoolDispatchLoop • 1,63 GB / - • System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread, ExecutionContext, ContextCallback, Object)
                    >99,9%  ExecuteWithThreadLocal • 1,63 GB / - • System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task, Thread)
                      >99,9%  ExecuteEntryUnsafe • 1,63 GB / - • System.Threading.Tasks.Task.ExecuteEntryUnsafe(Thread)
                        >99,9%  ExecuteFromThreadPool • 1,63 GB / - • System.Threading.Tasks.Task.ExecuteFromThreadPool(Thread)
                          >99,9%  Dispatch • 1,63 GB / - • System.Threading.ThreadPoolWorkQueue.Dispatch()
                            >99,9%  WorkerThreadStart • 1,63 GB / - • System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
                              >99,9%  StartCallback • 1,63 GB / - • System.Threading.Thread.StartCallback()
                                ► >99,9%  [AllThreadsRoot] • 1,63 GB / - • [AllThreadsRoot]
  ► <0,01%  Grow • 104,4 KB / - • System.Text.ValueStringBuilder.Grow(Int32)

To reproduce

I created a public sample console application where this issue can be reproduced

The situation is better in v5.2.0-preview3.23201.1 but in my opinion its still not good enough, is there anything else what can we done here to reduce the memory allocations?

https://github.com/Havunen/MicrosoftSqlDriverGcBug

Expected behavior

As little memory is allocated as possible, allocating gigabytes of memory for 1 megabyte of string sounds wrong.

Further technical details

Microsoft.Data.SqlClient version 5.1.1
.NET 6.0.21
SQL Server version: SQL Server 2019, 150
Operating system: Windows 11

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions