Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions csharp/src/StatementExecution/StatementExecutionConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -734,16 +734,28 @@ async Task IGetObjectsDataProvider.PopulateColumnInfoAsync(string? catalogPatter

internal async Task<List<RecordBatch>> ExecuteMetadataSqlAsync(string sql, CancellationToken cancellationToken = default)
{
// Ensure metadata SQL always has a timeout to prevent infinite polling.
// The caller (e.g. GetObjects) may already supply a timeout token, but if
// the token is CancellationToken.None (e.g. when called from
// ExecuteMetadataCommandAsync → GetTablesAsync via ExecuteQuery()), the
// inner statement would have _queryTimeoutSeconds=0 and poll forever.
// Link the caller's token with a metadata timeout as a safety net.
using var metadataTimeoutCts = CreateMetadataTimeoutCts();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems we have same bug for Thrift, can we fix that too?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good question! I investigated this and Thrift is not affected by this bug. Here's why:

  1. Thrift metadata calls use native RPC (e.g., TGetTablesReq) — a single request-response call, not SQL execution with polling.

  2. Databricks always returns DirectResults inline in the Thrift metadata response. The DatabricksConnection overrides GetResultSetMetadataAsync and GetRowSetAsync (DatabricksConnection.cs L923-927) to extract results directly from response.DirectResults! — never reaching PollForResponseAsync.

  3. The bug is SEA-specific because SEA metadata commands (e.g., SHOW TABLES) use ExecuteMetadataSqlAsyncStatementExecutionStatementPollUntilCompleteAsync (a while(true) loop). When called with CancellationToken.None and _queryTimeoutSeconds=0, this loop runs forever. Thrift's path never enters this code.

The base HiveServer2Connection.PollForResponseAsync (L733) does have a polling loop, but it's only used by non-Databricks drivers (via HiveServer2ExtendedConnection) and for statement execution — not for Databricks metadata calls.

using var linkedCts = cancellationToken.CanBeCanceled
? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, metadataTimeoutCts.Token)
: null;
var effectiveToken = linkedCts?.Token ?? metadataTimeoutCts.Token;

var batches = new List<RecordBatch>();
using var stmt = (StatementExecutionStatement)CreateStatement();
stmt.SqlQuery = sql;
var result = await stmt.ExecuteQueryAsync(cancellationToken, isMetadataExecution: true).ConfigureAwait(false);
var result = await stmt.ExecuteQueryAsync(effectiveToken, isMetadataExecution: true).ConfigureAwait(false);
using var stream = result.Stream;
if (stream == null) return batches;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
var batch = await stream.ReadNextRecordBatchAsync(cancellationToken).ConfigureAwait(false);
effectiveToken.ThrowIfCancellationRequested();
var batch = await stream.ReadNextRecordBatchAsync(effectiveToken).ConfigureAwait(false);
if (batch == null) break;
batches.Add(batch);
}
Expand Down
Loading