Skip to content

feat(csharp): honor adbc.spark.connect_timeout_ms on SEA path (PECO-3059) [SEA]#466

Merged
eric-wang-1990 merged 5 commits into
mainfrom
feat/csharp/PECO-3059-sea-connect-timeout
May 27, 2026
Merged

feat(csharp): honor adbc.spark.connect_timeout_ms on SEA path (PECO-3059) [SEA]#466
eric-wang-1990 merged 5 commits into
mainfrom
feat/csharp/PECO-3059-sea-connect-timeout

Conversation

@eric-wang-1990

Copy link
Copy Markdown
Collaborator

What's Changed

Wires adbc.spark.connect_timeout_ms through StatementExecutionConnection so the SEA (Statement Execution REST) path bounds session bring-up with the same semantics as the Thrift / HiveServer2 path. Before this PR, the parameter was silently ignored on the SEA path: CreateSessionAsync ran with no cancellation token, so a hung warehouse would wait for the full HttpClient.Timeout (minutes) before failing.

The fix:

  • Parses adbc.spark.connect_timeout_ms in StatementExecutionConnection's constructor with Thrift-compatible validation (0 = infinite, negative = invalid, default 30000 ms — matches HiveServer2Connection.ConnectTimeoutMillisecondsDefault).
  • Mirrors DatabricksConnection.ValidateOptions (lines 967-972): bumps the connect-timeout up to temporarily_unavailable_retry_timeout * 1000 when retry is enabled, so the connect-timeout token doesn't cancel mid-retry-loop.
  • Wraps CreateSessionAsync (and the semaphore wait) inside OpenAsync with a linked CancellationTokenSource so either the caller's token or the connect-timeout can cancel. Cancellations triggered by the connect-timeout (and not the caller's token) are translated to TimeoutException for exception-type parity with HiveServer2Connection.OpenAsync.

Why

PECO-3059 — M3 remaining parameter gap. Connection-timeout handling on the SEA path needed parity with Thrift before the Jun 10 cutoff.

Red → Green proof

E2E test: AdbcDrivers.Databricks.Tests.DatabricksConnectionTest.SeaConnectionTimeout_HonorsConnectTimeoutMs — forces adbc.databricks.protocol=rest, sets adbc.spark.connect_timeout_ms=1, and disables temporarily_unavailable_retry to bypass the parity-bump. The test runs against the live pecotesting warehouse.

Before fix (red, with the production change reverted):

[xUnit.net 00:00:00.91]     AdbcDrivers.Databricks.Tests.DatabricksConnectionTest.SeaConnectionTimeout_HonorsConnectTimeoutMs [FAIL]
  Failed AdbcDrivers.Databricks.Tests.DatabricksConnectionTest.SeaConnectionTimeout_HonorsConnectTimeoutMs [643 ms]
  Error Message:
   Assert.NotNull() Failure: Value is null
  Standard Output Messages:
 [PECO-3059] Forcing SEA protocol with ConnectTimeoutMilliseconds=1, retry disabled
 [PECO-3059] Elapsed: 640 ms. Exception: <none>

Failed!  - Failed:     1, Passed:     0, Skipped:     0, Total:     1

The connection succeeded in 640 ms — the 1 ms connect-timeout was silently ignored.

After fix (green):

A total of 1 test files matched the specified pattern.

Passed!  - Failed:     0, Passed:     1, Skipped:     0, Total:     1, Duration: 26 ms - AdbcDrivers.Databricks.Tests.dll (net8.0)

Connection fails fast (~26 ms total test duration including teardown) with a TimeoutException in the chain, matching the Thrift ConnectionTimeoutTest.

Files touched

  • csharp/src/StatementExecution/StatementExecutionConnection.cs — parse the timeout, apply Thrift-parity bump, gate CreateSessionAsync with linked CTS, translate cancellation → TimeoutException.
  • csharp/test/E2E/DatabricksConnectionTest.cs — new [SkippableFact] E2E test asserting the SEA path fails fast under connect_timeout_ms=1.

Safety

  • Full DatabricksConnectionTest class: 93 passed, 0 failed (42 s).
  • Full StatementExecution* E2E tests: 225 passed, 0 failed (1m 9s).
  • dotnet build succeeds for netstandard2.0, net472, net8.0.

Manual verification

  • E2E test (SeaConnectionTimeout_HonorsConnectTimeoutMs) passes against pecotesting
  • Full DatabricksConnectionTest class passes (no regression)
  • Full StatementExecution* test class passes (no regression)
  • Build green on all TFMs
  • pre-commit run (skipped — sandbox blocked network access for hook env install; will run in CI)

PECO-3059

…059) [SEA]

The SEA (Statement Execution REST) path silently ignored
adbc.spark.connect_timeout_ms when opening a session — the
HttpClient request could hang for the full HttpClient.Timeout
(minutes) on a slow/unreachable warehouse, while the Thrift
path failed fast in the same scenario.

Parse the timeout in StatementExecutionConnection's constructor
with the same validation as SparkHttpConnection (0 = infinite,
negative is invalid, default 30000 ms), mirror DatabricksConnection's
TemporarilyUnavailableRetry bump so the connect-timeout token doesn't
cancel mid-retry, and use the value to bound CreateSessionAsync inside
OpenAsync via a linked CancellationTokenSource. Cancellations triggered
by the connect-timeout (not the caller's token) are translated to
TimeoutException for parity with the Thrift HiveServer2Connection.OpenAsync
path.

E2E test (csharp/test/E2E/DatabricksConnectionTest.cs):
SeaConnectionTimeout_HonorsConnectTimeoutMs forces protocol=rest,
sets ConnectTimeoutMilliseconds=1, disables temporarily_unavailable_retry,
and asserts the connection fails fast (<10s wall clock) with a
TimeoutException — matching the existing Thrift ConnectionTimeoutTest.
Comment thread csharp/src/StatementExecution/StatementExecutionConnection.cs Outdated
Comment thread csharp/src/StatementExecution/StatementExecutionConnection.cs Outdated
…on + collapse try blocks (PECO-3059)

- Introduce StatementExecutionConnection.ValidateOptions() mirroring the
  DatabricksConnection (Thrift) pattern. All property-driven fields
  (result config, feature flags, tracing, identity federation, connect
  timeout) are now parsed and validated in one method instead of being
  scattered through the constructor. ParseConnectTimeoutMilliseconds is
  deleted; its logic lives inside ValidateOptions.
- Collapse the 3-level try in OpenAsync to 2 levels using a lockAcquired
  flag. The previous outer (timeout-mapping) + inner (lock-release) try
  pair is now a single try/catch/finally; the innermost try for
  CreateSession exception mapping is preserved.

Behavior preserved: all 93 DatabricksConnectionTest E2E tests pass,
including SeaConnectionTimeout_HonorsConnectTimeoutMs. Build green on
netstandard2.0, net472, net8.0.
Comment thread csharp/src/StatementExecution/StatementExecutionConnection.cs Outdated
Comment thread csharp/src/StatementExecution/StatementExecutionConnection.cs Outdated
Comment thread csharp/test/E2E/DatabricksConnectionTest.cs Outdated
…es + tighten timeout assertion (PECO-3059)

Follow-up review comments on PR #466:

- Rename ValidateOptions() to ValidateProperties() per reviewer
  preference; keep the centralized parsing pattern.
- Drop initial values from field declarations now that
  ValidateProperties() assigns them all from the constructor; use
  `= null!` only where required by nullable-reference-types to suppress
  CS8618.
- Tighten SeaConnectionTimeout_HonorsConnectTimeoutMs assertion from
  <10s to <1s — green runs complete in ~25-50 ms, the loose 10s budget
  was masking what "fails fast" really means here.

Co-authored-by: Isaac
…-sea-connect-timeout

# Conflicts:
#	csharp/src/StatementExecution/StatementExecutionConnection.cs
@eric-wang-1990 eric-wang-1990 added this pull request to the merge queue May 27, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 27, 2026
…meoutException (PECO-3059)

OAuthClientCredentialsProvider wraps a cancelled token-acquisition HTTP
call as DatabricksException("Failed to acquire OAuth access token: A
task was canceled."). The prior catch order — `catch (DatabricksException)
{ throw; }` followed by the timeout `when` filter — re-threw the wrapper
before the timeout translation could run, so callers saw an
AdbcException instead of TimeoutException and broke parity with the
Thrift path.

Reorder the catches so the timeout `when` filter runs first against any
exception type, including DatabricksException. Surfaces the
SeaConnectionTimeout_HonorsConnectTimeoutMs E2E expectation
(TimeoutException somewhere in the exception chain).

Co-authored-by: Isaac
@eric-wang-1990 eric-wang-1990 added this pull request to the merge queue May 27, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 27, 2026
@eric-wang-1990 eric-wang-1990 added this pull request to the merge queue May 27, 2026
Merged via the queue into main with commit 0f9f44c May 27, 2026
19 checks passed
@eric-wang-1990 eric-wang-1990 deleted the feat/csharp/PECO-3059-sea-connect-timeout branch May 27, 2026 07:07
eric-wang-1990 added a commit that referenced this pull request Jun 11, 2026
## Summary

- Bump `VersionPrefix` from `1.1.4` → `1.1.5` in
`csharp/Directory.Build.props`
- Mark `[1.1.4]` as released (2026-05-08) and add a dated `[1.1.5] -
2026-06-11` section in `CHANGELOG.md`

The 1.1.5 patch release captures everything merged since the 1.1.4 cut,
including session/cancel/close telemetry (#437), the concurrent Dispose
deadlock fix (#385), SEA-path config fixes (#457, #466, #468, #470),
result-transfer perf improvements (#474), the feature flag cache enabled
by default (#500), and connection-parameter telemetry corrections
(#517).

Note: unlike #453, the new section is dated immediately rather than left
as "Unreleased" — the release tag is cut from this PR's merge commit, so
the "Unreleased" label would go stale the moment it lands.

## Test plan

- [ ] CI passes on this PR
- [ ] After merge, `csharp-sync-latest.yml` creates tag `csharp/v1.1.5`
and updates `release/csharp/latest`

This pull request and its description were written by Isaac.
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.

2 participants