mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
Fix an issue with queue deadlocks causing test failures (#77)
This commit is contained in:
@@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -183,52 +184,62 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
try
|
||||||
if (bindingContext == null)
|
|
||||||
{
|
{
|
||||||
queueItem.ItemProcessed.Set();
|
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
|
||||||
continue;
|
if (bindingContext == null)
|
||||||
}
|
|
||||||
|
|
||||||
// prefer the queue item binding item, otherwise use the context default timeout
|
|
||||||
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
|
|
||||||
|
|
||||||
// handle the case a previous binding operation is still running
|
|
||||||
if (!bindingContext.BindingLocked.WaitOne(bindTimeout))
|
|
||||||
{
|
|
||||||
queueItem.ResultsTask = Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
var timeoutTask = queueItem.TimeoutOperation(bindingContext);
|
|
||||||
timeoutTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
|
|
||||||
return timeoutTask.Result;
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute the binding operation
|
|
||||||
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
|
||||||
queueItem.ResultsTask = queueItem.BindOperation(
|
|
||||||
bindingContext,
|
|
||||||
cancelToken.Token);
|
|
||||||
|
|
||||||
// set notification events once the binding operation task completes
|
|
||||||
queueItem.ResultsTask.ContinueWith((obj) =>
|
|
||||||
{
|
|
||||||
queueItem.ItemProcessed.Set();
|
queueItem.ItemProcessed.Set();
|
||||||
bindingContext.BindingLocked.Set();
|
continue;
|
||||||
});
|
|
||||||
|
|
||||||
// check if the binding tasks completed within the binding timeout
|
|
||||||
if (!queueItem.ResultsTask.Wait(bindTimeout))
|
|
||||||
{
|
|
||||||
// if the task didn't complete then call the timeout callback
|
|
||||||
if (queueItem.TimeoutOperation != null)
|
|
||||||
{
|
|
||||||
cancelToken.Cancel();
|
|
||||||
queueItem.ResultsTask = queueItem.TimeoutOperation(bindingContext);
|
|
||||||
queueItem.ResultsTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prefer the queue item binding item, otherwise use the context default timeout
|
||||||
|
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
|
||||||
|
|
||||||
|
// handle the case a previous binding operation is still running
|
||||||
|
if (!bindingContext.BindingLocked.WaitOne(bindTimeout))
|
||||||
|
{
|
||||||
|
queueItem.ResultsTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
var timeoutTask = queueItem.TimeoutOperation(bindingContext);
|
||||||
|
timeoutTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
|
||||||
|
return timeoutTask.Result;
|
||||||
|
});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the binding operation
|
||||||
|
CancellationTokenSource cancelToken = new CancellationTokenSource();
|
||||||
|
queueItem.ResultsTask = queueItem.BindOperation(
|
||||||
|
bindingContext,
|
||||||
|
cancelToken.Token);
|
||||||
|
|
||||||
|
// set notification events once the binding operation task completes
|
||||||
|
queueItem.ResultsTask.ContinueWith((obj) =>
|
||||||
|
{
|
||||||
|
queueItem.ItemProcessed.Set();
|
||||||
|
bindingContext.BindingLocked.Set();
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if the binding tasks completed within the binding timeout
|
||||||
|
if (!queueItem.ResultsTask.Wait(bindTimeout))
|
||||||
|
{
|
||||||
|
// if the task didn't complete then call the timeout callback
|
||||||
|
if (queueItem.TimeoutOperation != null)
|
||||||
|
{
|
||||||
|
cancelToken.Cancel();
|
||||||
|
queueItem.ResultsTask = queueItem.TimeoutOperation(bindingContext);
|
||||||
|
queueItem.ResultsTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// catch and log any exceptions raised in the binding calls
|
||||||
|
// set item processed to avoid deadlocks
|
||||||
|
Logger.Write(LogLevel.Error, "Binding queue threw exception " + ex.ToString());
|
||||||
|
queueItem.ItemProcessed.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a queue processing cancellation was requested then exit the loop
|
// if a queue processing cancellation was requested then exit the loop
|
||||||
|
|||||||
@@ -120,5 +120,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
// verify that send result was called with a completion array
|
// verify that send result was called with a completion array
|
||||||
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
requestContext.Verify(m => m.SendResult(It.IsAny<CompletionItem[]>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the service initialization code path and verify nothing throws
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public async void UpdateLanguageServiceOnConnection()
|
||||||
|
{
|
||||||
|
InitializeTestObjects();
|
||||||
|
|
||||||
|
AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object;
|
||||||
|
|
||||||
|
ConnectionInfo connInfo = TestObjects.GetTestConnectionInfo();
|
||||||
|
|
||||||
|
await LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,40 +162,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
LanguageService.ConnectionServiceInstance = null;
|
LanguageService.ConnectionServiceInstance = null;
|
||||||
Assert.True(LanguageService.ConnectionServiceInstance == null);
|
Assert.True(LanguageService.ConnectionServiceInstance == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test the service initialization code path and verify nothing throws
|
|
||||||
/// </summary>
|
|
||||||
[Fact]
|
|
||||||
public async void UpdateLanguageServiceOnConnection()
|
|
||||||
{
|
|
||||||
string ownerUri = "file://my/sample/file.sql";
|
|
||||||
var connectionService = TestObjects.GetTestConnectionService();
|
|
||||||
var connectionResult =
|
|
||||||
await connectionService
|
|
||||||
.Connect(new ConnectParams()
|
|
||||||
{
|
|
||||||
OwnerUri = ownerUri,
|
|
||||||
Connection = TestObjects.GetTestConnectionDetails()
|
|
||||||
});
|
|
||||||
|
|
||||||
// set up file for returning the query
|
|
||||||
var fileMock = new Mock<ScriptFile>();
|
|
||||||
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
|
|
||||||
fileMock.SetupGet(file => file.ClientFilePath).Returns(ownerUri);
|
|
||||||
|
|
||||||
// set up workspace mock
|
|
||||||
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
|
|
||||||
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
|
|
||||||
.Returns(fileMock.Object);
|
|
||||||
|
|
||||||
AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object;
|
|
||||||
|
|
||||||
ConnectionInfo connInfo = null;
|
|
||||||
connectionService.TryFindConnection(ownerUri, out connInfo);
|
|
||||||
|
|
||||||
await LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the service initialization code path and verify nothing throws
|
/// Test the service initialization code path and verify nothing throws
|
||||||
|
|||||||
@@ -248,11 +248,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution
|
|||||||
};
|
};
|
||||||
|
|
||||||
connInfo = Common.CreateTestConnectionInfo(null, false);
|
connInfo = Common.CreateTestConnectionInfo(null, false);
|
||||||
|
|
||||||
var srvConn = GetServerConnection(connInfo);
|
|
||||||
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
|
||||||
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
|
||||||
var binder = BinderProvider.CreateBinder(metadataProvider);
|
|
||||||
|
|
||||||
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo());
|
LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user