diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs index d0f7f13d..29310269 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs @@ -15,7 +15,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// Main class for the Binding Queue /// public class BindingQueue where T : IBindingContext, new() - { + { private CancellationTokenSource processQueueCancelToken = new CancellationTokenSource(); private ManualResetEvent itemQueuedEvent = new ManualResetEvent(initialState: false); @@ -197,14 +197,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices // 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 (!Monitor.TryEnter(bindingContext.BindingLock, bindTimeout)) + // handle the case a previous binding operation is still running + if (!bindingContext.BindingLock.WaitOne(0)) { - queueItem.Result = queueItem.TimeoutOperation(bindingContext); - queueItem.ItemProcessed.Set(); + queueItem.Result = queueItem.TimeoutOperation != null + ? queueItem.TimeoutOperation(bindingContext) + : null; + continue; } + bindingContext.BindingLock.Reset(); + lockTaken = true; // execute the binding operation @@ -231,9 +235,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { queueItem.Result = queueItem.TimeoutOperation(bindingContext); } + + lockTaken = false; - // we'll need to wait for the task to finsh canceling otherwise future binding will fail - bindTask.Wait(); + bindTask.ContinueWith((a) => bindingContext.BindingLock.Set()); } } catch (Exception ex) @@ -246,7 +251,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { if (lockTaken) { - Monitor.Exit(bindingContext.BindingLock); + bindingContext.BindingLock.Set(); } queueItem.ItemProcessed.Set(); diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs index 17cc6612..0add6ec4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingContext.cs @@ -4,6 +4,7 @@ // using System; +using System.Threading; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.SmoMetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Binder; @@ -20,7 +21,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { private ParseOptions parseOptions; - private object bindingLock; + private ManualResetEvent bindingLock; private ServerConnection serverConnection; @@ -29,7 +30,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public ConnectedBindingContext() { - this.bindingLock = new object(); + this.bindingLock = new ManualResetEvent(initialState: true); this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout; this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); } @@ -75,7 +76,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Gets the binding lock object /// - public object BindingLock + public ManualResetEvent BindingLock { get { diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs index ae6d8414..965d94d2 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs @@ -21,7 +21,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// public class ConnectedBindingQueue : BindingQueue { - internal const int DefaultBindingTimeout = 60000; + internal const int DefaultBindingTimeout = 500; internal const int DefaultMinimumConnectionTimeout = 30; @@ -63,10 +63,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices string connectionKey = GetConnectionContextKey(connInfo); IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey); - lock (bindingContext.BindingLock) + if (bindingContext.BindingLock.WaitOne()) { try { + bindingContext.BindingLock.Reset(); + // increase the connection timeout to at least 30 seconds and and build connection string // enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout; @@ -96,7 +98,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices catch (Exception) { bindingContext.IsConnected = false; - } + } + finally + { + bindingContext.BindingLock.Set(); + } } return connectionKey; diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs index 0c4c7742..aa4637b3 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/IBindingContext.cs @@ -46,7 +46,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices /// /// Gets the binding lock object /// - object BindingLock { get; } + ManualResetEvent BindingLock { get; } /// /// Gets or sets the binding operation timeout in milliseconds diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 13a8c4a4..a5d27b52 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -37,11 +37,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices internal const int DiagnosticParseDelay = 750; - internal const int HoverTimeout = 3000; + internal const int HoverTimeout = 500; - internal const int BindingTimeout = 3000; - - internal const int FindCompletionStartTimeout = 50; + internal const int BindingTimeout = 500; internal const int OnConnectionWaitTimeout = 300000; @@ -252,7 +250,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices await Task.FromResult(true); } else - { + { // get the current list of completion items and return to client var scriptFile = LanguageService.WorkspaceServiceInstance.Workspace.GetFile( textDocumentPosition.TextDocument.Uri); @@ -628,7 +626,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri); if (scriptParseInfo != null && scriptParseInfo.ParseResult != null) { - if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock, LanguageService.FindCompletionStartTimeout)) + if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) { try { @@ -711,8 +709,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices } Token token = GetToken(scriptParseInfo, line, column); - if (scriptParseInfo.IsConnected - && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock, LanguageService.FindCompletionStartTimeout)) + if (scriptParseInfo.IsConnected && Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) { try { diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/BindingQueueTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/BindingQueueTests.cs index d49aa9e5..bf60278b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/BindingQueueTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/BindingQueueTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices { public TestBindingContext() { - this.BindingLock = new object(); + this.BindingLock = new ManualResetEvent(true); this.BindingTimeout = 3000; } @@ -38,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices public IBinder Binder { get; set; } - public object BindingLock { get; set; } + public ManualResetEvent BindingLock { get; set; } public int BindingTimeout { get; set; }