-
Notifications
You must be signed in to change notification settings - Fork 874
Description
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:
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