diff --git a/csharp/test/E2E/StatementTests.cs b/csharp/test/E2E/StatementTests.cs index 6e5e0bf8..67629489 100644 --- a/csharp/test/E2E/StatementTests.cs +++ b/csharp/test/E2E/StatementTests.cs @@ -1025,14 +1025,14 @@ private async Task TestMetadataQuery(AdbcConnection connection, string queryType { OutputHelper?.WriteLine($"Testing {queryType} with EnableMultipleCatalogSupport={shouldAllowMultipleCatalogs}, CatalogName={catalogName}"); - var statement = connection.CreateStatement(); - statement.SetOption(ApacheParameters.IsMetadataCommand, "true"); - statement.SetOption(ApacheParameters.CatalogName, catalogName); - // Use default as schema name, it is the default schema name - statement.SetOption(ApacheParameters.SchemaName, "default"); - statement.SqlQuery = queryType; - - QueryResult queryResult = await statement.ExecuteQueryAsync(); + // Retry the metadata query on a transient server-side 500. The SEA backend + // intermittently returns HTTP 500 INTERNAL_ERROR "The result files are not available + // in the result metadata" for metadata statements — a server-side result-staging race, + // not a driver or test defect (the identical query succeeds on the next attempt with a + // fresh statement). Without this, the shared-warehouse merge-queue run flakes on that + // 500. Only this specific transient error is retried; any other exception (and all the + // schema/row assertions below) propagate unchanged, so real regressions still fail. + QueryResult queryResult = await ExecuteMetadataQueryWithRetryAsync(connection, queryType, catalogName); Assert.NotNull(queryResult.Stream); // Store SPARK catalog schema for comparison @@ -1164,6 +1164,44 @@ private async Task TestMetadataQuery(AdbcConnection connection, string queryType } } + /// + /// Executes a metadata query, retrying only on the transient server-side 500 + /// "The result files are not available in the result metadata" (a SEA backend + /// result-staging race). Uses a fresh statement per attempt. Any other exception — + /// and success — returns/propagates immediately, so genuine failures are not masked. + /// + private async Task ExecuteMetadataQueryWithRetryAsync( + AdbcConnection connection, string queryType, string catalogName) + { + const int maxAttempts = 4; + const string transientMarker = "result files are not available in the result metadata"; + + for (int attempt = 1; ; attempt++) + { + var statement = connection.CreateStatement(); + statement.SetOption(ApacheParameters.IsMetadataCommand, "true"); + statement.SetOption(ApacheParameters.CatalogName, catalogName); + // Use default as schema name, it is the default schema name + statement.SetOption(ApacheParameters.SchemaName, "default"); + statement.SqlQuery = queryType; + + try + { + return await statement.ExecuteQueryAsync(); + } + catch (Exception ex) when (attempt < maxAttempts && + ex.ToString().IndexOf(transientMarker, StringComparison.OrdinalIgnoreCase) >= 0) + { + // Exponential backoff: 500ms, 1s, 2s — gives the server time to stage results. + int delayMs = 500 * (1 << (attempt - 1)); + OutputHelper?.WriteLine( + $"{queryType} (catalog={catalogName}) hit transient server 500 on attempt {attempt}/{maxAttempts}; " + + $"retrying in {delayMs}ms"); + await Task.Delay(delayMs); + } + } + } + private void AssertField(Schema schema, int index, string expectedName, IArrowType expectedType, bool expectedNullable) { var field = schema.FieldsList[index];