Skip to content

Infinite Loop in NpgsqlDataReader.Consume that floods the RAM #6160

@pw-sgr

Description

@pw-sgr

I've a program that creates concurrent db connections and reads each 3 selects (always the same with different parameters).

If in NpgsqlDataReader.NextResult -> NpgsqlDataReader.ConsumeResultSet the exception System.TimeoutException is thrown, an infinite loop happens due to while(true) in NpgsqlDataReader.Consume.

This loop fills a list of exceptions in the millions and floods the RAM until the machine can no longer respond.

The logic call flow that needs to happen to trigger the bug:

I've added the values of each in ('<value>')

NpgsqlDataReader.Consume(bool, Exception)
|- while(true)
|   |- iteration 0
|      |- try
|         |- NpgsqlDataReader.NextResult(bool, bool, CancellationToken)
|            |- try
|                |- NpgsqlDataReader.ConsumeResultSet(bool) -> Throws System.TimeoutException
|            |- catch
|               |- if  exception is PostgresException -> is not, so skip
|               |- for NpgsqlDataReader.StatementIndex ('0') < NpgsqlDataReader._statments.Count ('1')
|                  |- Do stuff
|                  |- Increase NpgsqlDataReader.StatementIndex by 1
|               |- NpgsqlDataReader.State ('Closed') is not Closed -> false, so skip
|               |- throw
|      |- catch
|         |- add System.TimeoutException to exceptions
|- iteration 1 until the RAM is full and the machine craches
|   |- try
|      |- NpgsqlDataReader.NextResult(bool, bool, CancellationToken)
|         |- if State ('Closed') is Consumed -> skip
|         |- try
|            |- if NpgsqlDataReader.State ('Closed') is BeforeResult or InResult -> skip
|            |- statements = NpgsqlDataReader._statements ('Count: 1', index start at 0, so one item at index 0)
|            |- if NpgsqlDataReader.StatementIndex ('1') is above or equal 0
|               |- if NpgsqlDataReader._statements.get_Item(NpgsqlDataReader.StatementIndex) -> throws ArgumentOutOfRangeException, as the index 1 does not exist
|   |- catch
|      |- add System.ArgumentOutOfRangeException to exceptions

Log

2025-07-09 11:52:35.282 +02:00 [VRB] [Npgsql.Connection] Breaking connection
Npgsql.NpgsqlException (0x80004005): Exception while reading from stream
 ---> System.TimeoutException: Timeout during reading attempt
   at Npgsql.Internal.NpgsqlReadBuffer.<Ensure>g__EnsureLong|55_0(NpgsqlReadBuffer buffer, Int32 count, Boolean async, Boolean readingNotifications)
   at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at Npgsql.Internal.NpgsqlReadBuffer.Skip(Boolean async, Int32 len)
   at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
   at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
   at Npgsql.NpgsqlDataReader.<NextResult>g__ConsumeResultSet|52_0(Boolean async)
   at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)

JetBrains dotMemory Screenshot:

Image

I've tried to simulate that scenario with forcing the client timeouts to be 1s or artificially increasing the network delay (my local db is in a docker container under wsl) but was not able to replicate that exception. For these cases the exception handling work as expected.

Even the production app does not trigger this bug consistently. I'm trying to find a way to consistently hit that bug, but the local dev db reacts to fast and I can't really replicate the production db, as it has millions of entries.

I will update this issue if I find out more information, especially with replicating this bug.

Possible solutions could be adding ReaderState.Closed to the first check, which currently only checks if the State is ReaderState.Consumed in NpgsqlDataReader.NextResult or checking if StatementIndex is below _statements.Count before accessing it in if (RowDescription is { } description && statements[statementIndex].IsPrepared && ColumnInfoCache is { } cache) here. My insight in the library is not very good, so these solutions could cause other unwanted behaviors.

Methods:
Consume(bool, Exception)
Next(bool, bool, CancellationToken)

Technologies:

  • Npgsql v9.0.2-9.03
  • Postgres PG14
    • with Timescale

Edit: fixed formatting

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions