Skip to content

fix(csharp/src/Client): stop blocking on async calls in the ADO.NET wrapper - #4716

Merged
CurtHagenlocher merged 1 commit into
apache:mainfrom
RagingKore:gh-4715
Aug 25, 2026
Merged

fix(csharp/src/Client): stop blocking on async calls in the ADO.NET wrapper#4716
CurtHagenlocher merged 1 commit into
apache:mainfrom
RagingKore:gh-4715

Conversation

@RagingKore

@RagingKore RagingKore commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Implements the fix described in #4715.

AdbcCommand overrode only ExecuteDbDataReader and AdbcDataReader only Read(), so both BCL async entry points fell back to the synchronous bodies. ReadNextRecordBatchAsync becomes genuinely async, AdbcDataReader gains a ReadAsync override and AdbcCommand an ExecuteDbDataReaderAsync override.

Three notes on decisions in the diff, rather than as comments in the source.

ReadAsync returns a cached task on the intra-batch path instead of being async. It runs once per row, not once per batch, so an async method would allocate a state machine on the 4095 advances out of every 4096 that complete synchronously. Measured at 0 B per row, identical to Read().

Blocking that legitimately remains, on the synchronous APIs, now uses AsTask(). A driver's stream is genuinely asynchronous, so reading .Result on the ValueTask it returns is unsupported. That applies to AdbcDataReader.Read and to the schema-loading loop in AdbcConnection, which had the same defect independently of this change.

ExecuteDbDataReaderAsync only checks its CancellationToken rather than passing it down, because AdbcStatement.ExecuteQueryAsync takes none. The initial query call stays uncancellable exactly as it is today; per-batch fetches become cancellable through ReadAsync.

All additive: no driver changes, no public contract change. Read(), GetSchema() and ExecuteDbDataReader are unchanged, and a driver overriding only ExecuteQuery still gets the base Task.Run implementation.

Tests are in Client/ClientTests.cs. ReadAsyncDoesNotDeadlockOnASynchronizationContext hangs against current main and passes here.

Closes #4715

…rapper

AdbcCommand overrode only ExecuteDbDataReader and AdbcDataReader only Read(), so
both BCL async entry points fell back to the synchronous bodies and reached .Result.
One of those sits inside a private method named ReadNextRecordBatchAsync that returns
ValueTask<RecordBatch?> and takes a CancellationToken it never used. On a host with a
SynchronizationContext the awaited path deadlocks.

ReadNextRecordBatchAsync now awaits the stream. AdbcDataReader overrides ReadAsync,
keeping the intra-batch path free of a state machine via a cached task. AdbcCommand
overrides ExecuteDbDataReaderAsync and awaits AdbcStatement.ExecuteQueryAsync, sharing
behavior validation with ExecuteReader.

Where blocking legitimately remains, on the synchronous APIs, it now uses AsTask().
A driver's stream is genuinely asynchronous, so reading .Result on the ValueTask it
returns is unsupported. That applied to AdbcDataReader.Read and to the schema-loading
loop in AdbcConnection, which had the same defect independently of this change.

All additive. No driver changes and no public contract change. Read(), GetSchema() and
ExecuteDbDataReader are unchanged, and a driver that overrides only ExecuteQuery still
gets the base Task.Run implementation.

AdbcStatement.ExecuteQueryAsync takes no CancellationToken, so the initial query call
stays uncancellable as it is today. Per-batch fetches are cancellable.

Closes apache#4715
@RagingKore RagingKore changed the title fix(csharp/src/Client): await the async statement path in AdbcCommand and AdbcDataReader fix(csharp/src/Client): stop blocking on async calls in the ADO.NET wrapper Aug 25, 2026

@CurtHagenlocher CurtHagenlocher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@CurtHagenlocher
CurtHagenlocher merged commit 1069393 into apache:main Aug 25, 2026
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

csharp: AdbcCommand and AdbcDataReader block on every async call path

2 participants