This change ensures that we send back only one 'isComplete=true' message for a given result set (#763)

Fixes:
The streaming protocol is now tightened to ensure that only the last message for a result set contain isCompleted=true. Now isCompleted=true is never sent in isAvailable message.
Tightened logic in sending messages to make sure that no duplicate messages get sent out due to concurrent processing.
Made a fix to a null reference exception when processing special action which was a pre-existing benign bug.
Testing: Added 1 more new test that runs existing tests concurrently 1000 times to make sure no random timing issues are observed and tightened verifications to existing tests to ensure no duplicate messages and only one isComplete=true message is sent across.

* tightening the protocal to ensure only one message (the last one) with completed=true is sent back for a single result set within a query batch
This commit is contained in:
Arvind Ranasaria
2019-01-11 10:21:52 -08:00
committed by GitHub
parent 0fd98df79b
commit 5f6d500977
4 changed files with 128 additions and 56 deletions

View File

@@ -125,6 +125,30 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
//
VerifyReadResultToEnd(resultSet, resultSummaryFromAvailableCallback, resultSummaryFromCompleteCallback, resultSummariesFromUpdatedCallback);
}
/// <summary>
/// Read to End test
/// </summary>
/// <param name="testDataSet"></param>
[Theory]
[MemberData(nameof(ReadToEndSuccessData), parameters: 3)]
public async Task ReadToEndSuccessSeveralTimes(TestResultSet[] testDataSet)
{
const int NumberOfInvocations = 5000;
List<Task> allTasks = new List<Task>();
Parallel.ForEach(Partitioner.Create(0, NumberOfInvocations), (range) =>
{
int start = range.Item1 == 0 ? 1 : range.Item1;
Task[] tasks = new Task[range.Item2 - start];
for (int i = start; i < range.Item2; i++)
{
allTasks.Add(ReadToEndSuccess(testDataSet));
}
});
await Task.WhenAll(allTasks);
}
public static IEnumerable<object[]> ReadToEndSuccessData(int numTests) => Common.TestResultSetsEnumeration.Select(r => new object[] { new TestResultSet[] { r } }).Take(numTests);
[Theory]
@@ -160,7 +184,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
// ... The callback for result set available, update and completion callbacks should have been fired.
//
Assert.True(null != resultSummaryFromCompleteCallback, "completeResultSummary is null" + $"\r\n\t\tupdateResultSets: {string.Join("\r\n\t\t\t", resultSummariesFromUpdatedCallback)}");
Assert.True(null != resultSummaryFromAvailableCallback, "availableResultSummary is null" + $"\r\n\t\tupdateResultSets: {string.Join("\r\n\t\t\t", resultSummariesFromUpdatedCallback)}");
Assert.True(null != resultSummaryFromAvailableCallback, "availableResultSummary is null" + $"\r\n\t\tavailableResultSet: {resultSummaryFromAvailableCallback}");
// ... resultSetAvailable is not marked Complete
//
Assert.True(false == resultSummaryFromAvailableCallback.Complete, "availableResultSummary.Complete is true" + $"\r\n\t\tavailableResultSet: {resultSummaryFromAvailableCallback}");
// insert availableResult at the top of the resultSummariesFromUpdatedCallback list as the available result set is the first update in that series.
//
@@ -175,6 +203,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
Assert.True(resultSummariesFromUpdatedCallback.Last().Complete,
$"Complete Check failed.\r\n\t\t resultSummariesFromUpdatedCallback:{string.Join("\r\n\t\t\t", resultSummariesFromUpdatedCallback)}");
// ... The no of rows in the final updateResultSet/AvailableResultSet should be equal to that in the Complete Result Set.
//
Assert.True(resultSummaryFromCompleteCallback.RowCount == resultSummariesFromUpdatedCallback.Last().RowCount,
@@ -184,7 +213,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
);
// ... RowCount should be in increasing order in updateResultSet callbacks
// ..... and there should be only one resultSummary with Complete flag set to true.
//
int completeFlagCount = 0;
Parallel.ForEach(Partitioner.Create(0, resultSummariesFromUpdatedCallback.Count), (range) =>
{
int start = range.Item1 == 0 ? 1 : range.Item1;
@@ -194,8 +225,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
$"Row Count of {i}th updateResultSet was smaller than that of the previous one"
+ $"\r\n\t\tupdateResultSets: {string.Join("\r\n\t\t\t", resultSummariesFromUpdatedCallback)}"
);
if (resultSummariesFromUpdatedCallback[i].Complete)
{
Interlocked.Increment(ref completeFlagCount);
}
}
});
Assert.True(completeFlagCount == 1, "Number of update events with complete flag event set should be 1" + $"\r\n\t\tupdateResultSets: {string.Join("\r\n\t\t\t", resultSummariesFromUpdatedCallback)}");
}
/// <summary>

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
@@ -652,17 +653,26 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
foreach (var (key, list) in resultSetDictionary)
{
ResultSetSummary completeSummary = null, lastResultSetSummary = null;
int completeFlagCount = 0;
for (int i = 0; i < list.Count; i++)
{
VerifyResultSummary(key, i, list, ref completeSummary, ref lastResultSetSummary);
VerifyResultSummary(key, i, list, ref completeSummary, ref lastResultSetSummary, ref completeFlagCount);
}
// Verify that the completeEvent and lastResultSetSummary has same number of rows
//
if (lastResultSetSummary != null && completeSummary != null)
{
Assert.True(lastResultSetSummary.RowCount == completeSummary.RowCount, "CompleteSummary and last Update Summary should have same number of rows");
Assert.True(lastResultSetSummary.RowCount == completeSummary.RowCount, $"CompleteSummary and last Update Summary should have same number of rows, updateSummaryRowCount: {lastResultSetSummary.RowCount}, CompleteSummaryRowCount: {completeSummary.RowCount}");
}
// Verify that we got exactly on complete Flag Count.
//
Assert.True(1 == completeFlagCount, $"Complete flag should be set in exactly once on Update Result Summary Event. Observed Count: {completeFlagCount}, \r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", list.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}" );
// Verify that the complete event was set on the last updateResultSet event.
//
Assert.True(list.Last().ResultSetSummary.Complete, $"Complete flag should be set on the last Update Result Summary event, , \r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", list.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}");
}
@@ -677,7 +687,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
/// <param name="resultSetEventParamsList">The list of resultSetParams that we are verifying</param>
/// <param name="completeSummary"> This should be null when we start validating the list of ResultSetEventParams</param>
/// <param name="lastResultSetSummary"> This should be null when we start validating the list of ResultSetEventParams</param>
private static void VerifyResultSummary(string batchIdResultSetId, int position, List<ResultSetEventParams> resultSetEventParamsList, ref ResultSetSummary completeSummary, ref ResultSetSummary lastResultSetSummary)
/// <param name="completeFlagCount">The number of events with complete flag set. Increments input value if current resultSummary has Complete flag set</param>
private static void VerifyResultSummary(string batchIdResultSetId, int position, List<ResultSetEventParams> resultSetEventParamsList, ref ResultSetSummary completeSummary, ref ResultSetSummary lastResultSetSummary, ref int completeFlagCount)
{
ResultSetEventParams resultSetEventParams = resultSetEventParamsList[position];
switch (resultSetEventParams.GetType().Name)
@@ -690,9 +701,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
+ $"\r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", resultSetEventParamsList.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}"
);
// Verify that Complete flag is not set on the ResultSetAvailableEventParams
Assert.True(false == resultSetEventParams.ResultSetSummary.Complete,
$"availableResultSummary.Complete is true for {batchIdResultSetId}"
+ $"\r\nresultSetEventParamsList is:{string.Join("\r\n\t\t", resultSetEventParamsList.ConvertAll((p) => p.GetType() + ":" + p.ResultSetSummary))}"
);
// Save the lastResultSetSummary for this event for other verifications.
//
lastResultSetSummary = resultSetEventParams.ResultSetSummary;
break;
case nameof(ResultSetUpdatedEventParams):
// Verify that the updateEvent is not the first in the sequence. Since we set lastResultSetSummary on each available or updatedEvent, we check that there has been no lastResultSetSummary previously set yet.
@@ -713,6 +731,12 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution
// Save the lastResultSetSummary for this event for other verifications.
//
lastResultSetSummary = resultSetEventParams.ResultSetSummary;
// increment completeFlagCount if complete flag is set.
if (resultSetEventParams.ResultSetSummary.Complete)
{
Interlocked.Increment(ref completeFlagCount);
}
break;
case nameof(ResultSetCompleteEventParams):
// Verify that there is only one completeEvent