From fa7e13d9c5fb85408181d2a15f425adf3907761c Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Wed, 12 Oct 2016 17:41:48 -0700 Subject: [PATCH] Cancel Query Part 2 (#89) Moving some logic around such that when a query is cancelled, it isn't thrown away, allowing reading of the partial results, a la SSMS. Adding a configure await to fix a tenacious bug causing query cancellations to hang for 20s or more. Capturing sql errors for user cancellation, to return all user cancelation scenarios using the same messages. (ie, cancelling during ExecuteReaderAsync will yield a error from the server whereas cancelling during ReadAsync throws a TaskCancelledException No changes to protocol, just implementation changes. * Test of try/finally * Fixed issue where resultsets are unreadable after query cancellation * Fix for await/async issue --- .../QueryExecution/Batch.cs | 22 +++++++++++++------ .../QueryExecution/QueryExecutionService.cs | 15 +------------ .../QueryExecution/ResultSet.cs | 8 +++---- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs index 1bbce090..10b2d9b1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Batch.cs @@ -183,7 +183,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution resultSets.Add(resultSet); // Read until we hit the end of the result set - await resultSet.ReadResultToEnd(cancellationToken); + await resultSet.ReadResultToEnd(cancellationToken).ConfigureAwait(false); // Add a message for the number of rows the query returned resultMessages.Add(new ResultMessage(SR.QueryServiceAffectedRows(resultSet.RowCount))); @@ -268,15 +268,23 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution SqlException se = dbe as SqlException; if (se != null) { - foreach (var error in se.Errors) + var errors = se.Errors.Cast().ToList(); + // Detect user cancellation errors + if (errors.Any(error => error.Class == 11 && error.Number == 0)) { - SqlError sqlError = error as SqlError; - if (sqlError != null) + // User cancellation error, add the single message + HasError = false; + resultMessages.Add(new ResultMessage(SR.QueryServiceQueryCancelled)); + } + else + { + // Not a user cancellation error, add all + foreach (var error in errors) { - int lineNumber = sqlError.LineNumber + Selection.StartLine; + int lineNumber = error.LineNumber + Selection.StartLine; string message = string.Format("Msg {0}, Level {1}, State {2}, Line {3}{4}{5}", - sqlError.Number, sqlError.Class, sqlError.State, lineNumber, - Environment.NewLine, sqlError.Message); + error.Number, error.Class, error.State, lineNumber, + Environment.NewLine, error.Message); resultMessages.Add(new ResultMessage(message)); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs index e6abce65..39543c33 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -231,21 +231,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution return; } - // Cancel the query + // Cancel the query and send a success message result.Cancel(); - result.Dispose(); - - // Attempt to dispose the query - if (!ActiveQueries.TryRemove(cancelParams.OwnerUri, out result)) - { - // It really shouldn't be possible to get to this scenario, but we'll cover it anyhow - await requestContext.SendResult(new QueryCancelResult - { - Messages = SR.QueryServiceCancelDisposeFailed - }); - return; - } - await requestContext.SendResult(new QueryCancelResult()); } catch (InvalidOperationException e) diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs index 39aabd49..cc6a98ab 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs @@ -201,6 +201,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Cancellation token for cancelling the query public async Task ReadResultToEnd(CancellationToken cancellationToken) { + // Mark that result has been read + hasBeenRead = true; + fileStreamReader = fileStreamFactory.GetReader(outputFileName); + // Open a writer for the file using (IFileStreamWriter fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxXmlCharsToStore)) { @@ -221,10 +225,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution } // Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata SingleColumnXmlJsonResultSet(); - - // Mark that result has been read - hasBeenRead = true; - fileStreamReader = fileStreamFactory.GetReader(outputFileName); } #endregion