diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs index 4417cda6..89b5e0c7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { @@ -183,52 +184,62 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices continue; } - IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key); - if (bindingContext == null) + try { - queueItem.ItemProcessed.Set(); - continue; - } - - // 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(() => + IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key); + if (bindingContext == null) { - 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()); + continue; } + + // 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 diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs index a9e346cd..a44f8994 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/AutocompleteTests.cs @@ -120,5 +120,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices // verify that send result was called with a completion array requestContext.Verify(m => m.SendResult(It.IsAny()), Times.Once()); } + + /// + /// Test the service initialization code path and verify nothing throws + /// + [Fact] + public async void UpdateLanguageServiceOnConnection() + { + InitializeTestObjects(); + + AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object; + + ConnectionInfo connInfo = TestObjects.GetTestConnectionInfo(); + + await LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo); + } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs index 7d5422ed..9ce596e5 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs @@ -162,40 +162,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices LanguageService.ConnectionServiceInstance = null; Assert.True(LanguageService.ConnectionServiceInstance == null); } - - /// - /// Test the service initialization code path and verify nothing throws - /// - [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(); - fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery); - fileMock.SetupGet(file => file.ClientFilePath).Returns(ownerUri); - - // set up workspace mock - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); - - AutoCompleteHelper.WorkspaceServiceInstance = workspaceService.Object; - - ConnectionInfo connInfo = null; - connectionService.TryFindConnection(ownerUri, out connInfo); - - await LanguageService.Instance.UpdateLanguageServiceOnConnection(connInfo); - } /// /// Test the service initialization code path and verify nothing throws diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs index d9391970..c3f6df75 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs @@ -248,11 +248,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution }; 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());