IndexOutOfRangeException in PgNumeric.Builder.ToDecimal() when reading many numeric rows (sync path, 8.0.9)

未關閉
#6,509 10 則留言 0 個 reaction 已指派 1 人 在 GitHub 檢視

維護者通常 6 天內回覆

@vonzshik 已經在處理了。

開始於 2026年3月23日。

評估

這個 Issue 還沒有評估資料。

描述

area-serialization

Describe the bug

After upgrading to 8.0.9, we encounter a sporadic IndexOutOfRangeException in PgNumeric.Builder.ToDecimal() when synchronously reading a large number of rows containing numeric columns via NpgsqlDataReader.GetValue().

We are aware that #6383 / PR #6385 fixed a similar ArraySegment boundary issue in NumericConverters.ReadAsync (the BigInteger async path) for 8.0.9. However, the synchronous decimal read path through PgNumeric.Builder.ToDecimal() appears to have the same class of bug that was not addressed in 8.0.9.

Environment

  • Npgsql version: 8.0.9 (confirmed via Assembly.GetName().Version = 8.0.9.0)
  • .NET version: .NET Framework 4.8
  • PostgreSQL version: Greenplum 6.x (PostgreSQL 9.4 compatible)
  • Operating system: Windows Server 2019

Exception details

System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at System.ThrowHelper.ThrowIndexOutOfRangeException()
   at Npgsql.Internal.Converters.PgNumeric.Builder.ToDecimal(Int16 scale, Int16 weight, UInt16 sign, Span`1 digits)
   at Npgsql.Internal.Converters.PgNumeric.Builder.ToDecimal()
   at Npgsql.Internal.Converters.DecimalNumericConverter`1.ConvertTo(Builder& numeric)
   at Npgsql.Internal.Converters.DecimalNumericConverter`1.ReadCore(PgReader reader)
   at Npgsql.Internal.PgBufferedConverter`1.Read(PgReader reader)
   at Npgsql.Internal.PgBufferedConverter`1.ReadAsObject(Boolean async, PgReader reader, CancellationToken cancellationToken)
   at Npgsql.Internal.PgConverter.ReadAsObject(PgReader reader)
   at Npgsql.NpgsqlDataReader.GetValue(Int32 ordinal)

Key observation: the call goes through PgBufferedConverter<T>.Read (sync), not ReadAsync. The exception source is System.Memory (Span<T> indexer), not Npgsql's own array access.

Steps to reproduce

The issue is sporadic and depends on internal buffer reuse patterns. It occurs more frequently with:

  • Large result sets (thousands of rows)
  • numeric columns with varying precision
  • Aggregation functions (SUM, AVG) that produce numeric results with different digit counts

Minimal reproduction pattern:

using var conn = new NpgsqlConnection(connStr);
conn.Open();

// Read many rows with numeric values - triggers buffer reuse in PgReader
using var cmd = new NpgsqlCommand(
    "SELECT 1234567890.123456::numeric AS val FROM generate_series(1, 8000)", conn);

using var reader = cmd.ExecuteReader();
while (reader.Read())
{
    // Sporadic IndexOutOfRangeException here
    var value = reader.GetValue(0);  // sync path → PgNumeric.Builder.ToDecimal()
}

Analysis

Comparing v8.0.8...v8.0.9, the fix in NumericConverters.cs (line 219) corrected the ArraySegment boundary in the async BigInteger read path:

// NumericConverters.cs ReadAsync method (line 219)
// Before: for (var i = digits.Offset; i < array.Length; i++)
// After:  for (var i = digits.Offset; i < digits.Offset + digits.Count; i++)

However, PgNumeric.Builder.ToDecimal(Int16 scale, Int16 weight, UInt16 sign, Span<short> digits) in PgNumeric.cs — which is the sync decimal path — was not modified in 8.0.9. The Span<short> digits parameter appears to receive incorrect bounds when the PgReader's internal buffer is reused across many rows, causing the Span indexer to throw IndexOutOfRangeException from System.Memory.

This is the same root cause as #4313 and #6383 — ArraySegment/Span boundary not properly scoped after buffer reuse — but manifesting in a different code path.

Related issues

  • #4313 - IndexOutOfRangeException in the NumericHandler (original report, closed)
  • #6383 - Reading numerics as BigInteger throws IndexOutOfRangeException (fixed in 8.0.9, but only async BigInteger path)
  • PR #6385 - Fix reading numerics as BigInteger (the 8.0.9 fix)
  • PR #6117 - Broader fix merged to main (10.0.0)

Expected behavior

NpgsqlDataReader.GetValue() should return a decimal value without throwing IndexOutOfRangeException, regardless of result set size.

Actual behavior

Sporadic IndexOutOfRangeException in PgNumeric.Builder.ToDecimal() when reading large result sets with numeric columns via the synchronous GetValue() path.

Possible fix

The fix from PR #6117 (merged to main / 10.0.0) likely addresses this. Could the relevant PgNumeric.cs changes be backported to the 8.0.x branch?

主要語言
C#
星號
3.7k
分支
890
平均合併
4 天 11 小時
30 天內合併 PR
19

環境準備

在 Codespaces 中開啟

在瀏覽器裡用你自己的 GitHub 帳號啟動這個專案的開發容器。

  • 沒有 Dockerfile 或 Docker Compose 檔案
  • 沒有 Pull Request 範本
  • 沒有貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

npgsql/npgsql 的其他 Issue

查看 npgsql/npgsql 的全部 Issue

相似的 Issue

更多 C# Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。