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
This commit is contained in:
Benjamin Russell
2016-10-12 17:41:48 -07:00
committed by GitHub
parent 71fabd5c75
commit fa7e13d9c5
3 changed files with 20 additions and 25 deletions

View File

@@ -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<SqlError>().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));
}
}

View File

@@ -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)

View File

@@ -201,6 +201,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
/// <param name="cancellationToken">Cancellation token for cancelling the query</param>
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