From cea10c13e689a9a4b82428a8f286605e2f17cea9 Mon Sep 17 00:00:00 2001
From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com>
Date: Wed, 23 Aug 2023 13:46:13 -0700
Subject: [PATCH] Fixes BindingQueue, GetSignatureHelp and
RefreshIntellisenseCache to be truly async (#2175)
---
.../LanguageServices/BindingQueue.cs | 272 +++++-----
.../LanguageServices/LanguageService.cs | 495 +++++++++---------
.../QueryExecution/QueryExecutionService.cs | 9 +-
.../Workspace/Workspace.cs | 9 +-
.../LanguageServer/LanguageServiceTests.cs | 12 +-
.../LanguageServer/PeekDefinitionTests.cs | 14 +-
.../LanguageServer/AutocompleteTests.cs | 2 +-
.../LanguageServer/LanguageServiceTests.cs | 34 +-
8 files changed, 433 insertions(+), 414 deletions(-)
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
index a8e9ca3c..0df4ef2d 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
@@ -12,9 +12,8 @@ using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
-
+using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
@@ -130,7 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return context.IsConnected;
}
return false;
- }
+ }
}
///
@@ -144,7 +143,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
key = "disconnected_binding_context";
}
-
+
lock (this.bindingContextLock)
{
if (!this.BindingContextMap.ContainsKey(key))
@@ -155,7 +154,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
return this.BindingContextMap[key];
- }
+ }
}
protected IEnumerable GetBindingContexts(string keyPrefix)
@@ -269,10 +268,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
this.itemQueuedEvent,
token.WaitHandle
};
-
+
while (true)
{
- // wait for with an item to be queued or the a cancellation request
+ // wait for with an item to be queued or the cancellation request
WaitHandle.WaitAny(waitHandles);
if (token.IsCancellationRequested)
{
@@ -283,7 +282,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
// dispatch all pending queue items
while (this.HasPendingQueueItems)
- {
+ {
QueueItem queueItem = GetNextQueueItem();
if (queueItem == null)
{
@@ -291,7 +290,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
- if (bindingContext == null)
+ if (bindingContext == null)
{
queueItem.ItemProcessed.Set();
continue;
@@ -300,140 +299,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
var bindingContextTask = this.BindingContextTasks[bindingContext];
// Run in the binding context task in case this task has to wait for a previous binding operation
- this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith((task) =>
- {
- bool lockTaken = false;
- try
- {
- // 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.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
- {
- try
- {
- Logger.Warning("Binding queue operation timed out waiting for previous operation to finish");
- queueItem.Result = queueItem.TimeoutOperation != null
- ? queueItem.TimeoutOperation(bindingContext)
- : null;
- }
- catch (Exception ex)
- {
- Logger.Error("Exception running binding queue lock timeout handler: " + ex.ToString());
- }
- finally
- {
- queueItem.ItemProcessed.Set();
- }
-
- return;
- }
-
- bindingContext.BindingLock.Reset();
-
- lockTaken = true;
-
- // execute the binding operation
- object result = null;
- CancellationTokenSource cancelToken = new CancellationTokenSource();
-
- // run the operation in a separate thread
- var bindTask = Task.Run(() =>
- {
- try
- {
- result = queueItem.BindOperation(
- bindingContext,
- cancelToken.Token);
- }
- catch (Exception ex)
- {
- Logger.Error("Unexpected exception on the binding queue: " + ex.ToString());
- if (queueItem.ErrorHandler != null)
- {
- try
- {
- result = queueItem.ErrorHandler(ex);
- }
- catch (Exception ex2)
- {
- Logger.Error("Unexpected exception in binding queue error handler: " + ex2.ToString());
- }
- }
-
- if (IsExceptionOfType(ex, typeof(SqlException)) || IsExceptionOfType(ex, typeof(SocketException)))
- {
- if (this.OnUnhandledException != null)
- {
- this.OnUnhandledException(queueItem.Key, ex);
- }
-
- RemoveBindingContext(queueItem.Key);
- }
- }
- });
-
- Task.Run(() =>
- {
- try
- {
- // check if the binding tasks completed within the binding timeout
- if (bindTask.Wait(bindTimeout))
- {
- queueItem.Result = result;
- }
- else
- {
- cancelToken.Cancel();
-
- // if the task didn't complete then call the timeout callback
- if (queueItem.TimeoutOperation != null)
- {
- queueItem.Result = queueItem.TimeoutOperation(bindingContext);
- }
-
- bindTask.ContinueWithOnFaulted(t => Logger.Error("Binding queue threw exception " + t.Exception.ToString()));
-
- // Give the task a chance to complete before moving on to the next operation
- bindTask.Wait();
- }
- }
- catch (Exception ex)
- {
- Logger.Error("Binding queue task completion threw exception " + ex.ToString());
- }
- finally
- {
- // set item processed to avoid deadlocks
- if (lockTaken)
- {
- bindingContext.BindingLock.Set();
- }
- queueItem.ItemProcessed.Set();
- }
- });
- }
- catch (Exception ex)
- {
- // catch and log any exceptions raised in the binding calls
- // set item processed to avoid deadlocks
- Logger.Error("Binding queue threw exception " + ex.ToString());
- // set item processed to avoid deadlocks
- if (lockTaken)
- {
- bindingContext.BindingLock.Set();
- }
- queueItem.ItemProcessed.Set();
- }
- }, TaskContinuationOptions.None);
+ this.BindingContextTasks[bindingContext] = bindingContextTask.ContinueWith(
+ task => DispatchQueueItem(bindingContext, queueItem)
+ , TaskContinuationOptions.RunContinuationsAsynchronously);
// if a queue processing cancellation was requested then exit the loop
if (token.IsCancellationRequested)
{
break;
}
- }
+ }
}
finally
{
@@ -450,6 +325,127 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
+ private void DispatchQueueItem(IBindingContext bindingContext, QueueItem queueItem)
+ {
+ bool lockTaken = false;
+ try
+ {
+ // prefer the queue item binding item, otherwise use the context default timeout - timeout is in milliseconds
+ int bindTimeoutInMs = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
+
+ // handle the case a previous binding operation is still running
+ if (!bindingContext.BindingLock.WaitOne(queueItem.WaitForLockTimeout ?? 0))
+ {
+ try
+ {
+ Logger.Warning("Binding queue operation timed out waiting for previous operation to finish");
+ queueItem.Result = queueItem.TimeoutOperation != null
+ ? queueItem.TimeoutOperation(bindingContext)
+ : null;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Exception running binding queue lock timeout handler: " + ex.ToString());
+ }
+ finally
+ {
+ queueItem.ItemProcessed.Set();
+ }
+ }
+
+ bindingContext.BindingLock.Reset();
+
+ lockTaken = true;
+
+ // execute the binding operation
+ object result = null;
+ CancellationTokenSource cancelToken = new CancellationTokenSource();
+
+ // run the operation in a separate thread
+ var bindTask = Task.Run(() =>
+ {
+ try
+ {
+ result = queueItem.BindOperation(
+ bindingContext,
+ cancelToken.Token);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Unexpected exception on the binding queue: " + ex.ToString());
+ if (queueItem.ErrorHandler != null)
+ {
+ try
+ {
+ result = queueItem.ErrorHandler(ex);
+ }
+ catch (Exception ex2)
+ {
+ Logger.Error("Unexpected exception in binding queue error handler: " + ex2.ToString());
+ }
+ }
+ if (IsExceptionOfType(ex, typeof(SqlException)) || IsExceptionOfType(ex, typeof(SocketException)))
+ {
+ if (this.OnUnhandledException != null)
+ {
+ this.OnUnhandledException(queueItem.Key, ex);
+ }
+ RemoveBindingContext(queueItem.Key);
+ }
+ }
+ });
+
+ Task.Run(() =>
+ {
+ try
+ {
+ // check if the binding tasks completed within the binding timeout
+ if (bindTask.Wait(bindTimeoutInMs))
+ {
+ queueItem.Result = result;
+ }
+ else
+ {
+ cancelToken.Cancel();
+ // if the task didn't complete then call the timeout callback
+ if (queueItem.TimeoutOperation != null)
+ {
+ queueItem.Result = queueItem.TimeoutOperation(bindingContext);
+ }
+ bindTask.ContinueWithOnFaulted(t => Logger.Error("Binding queue threw exception " + t.Exception.ToString()));
+ // Give the task a chance to complete before moving on to the next operation
+ bindTask.Wait();
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Binding queue task completion threw exception " + ex.ToString());
+ }
+ finally
+ {
+ // set item processed to avoid deadlocks
+ if (lockTaken)
+ {
+ bindingContext.BindingLock.Set();
+ }
+ queueItem.ItemProcessed.Set();
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ // catch and log any exceptions raised in the binding calls
+ // set item processed to avoid deadlocks
+ Logger.Error("Binding queue threw exception " + ex.ToString());
+ // set item processed to avoid deadlocks
+ if (lockTaken)
+ {
+ bindingContext.BindingLock.Set();
+ }
+ queueItem.ItemProcessed.Set();
+ }
+ }
+
///
/// Clear queued items
///
@@ -487,7 +483,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
}
-
+
private bool IsExceptionOfType(Exception ex, Type t)
{
return ex.GetType() == t || (ex.InnerException != null && ex.InnerException.GetType() == t);
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
index b89d4a4d..41d1403e 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs
@@ -107,18 +107,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
compatibilityLevel: DatabaseCompatibilityLevel.Current,
transactSqlVersion: TransactSqlVersion.Current);
- private ConcurrentDictionary nonMssqlUriMap = new ConcurrentDictionary();
+ private ConcurrentDictionary nonMssqlUriMap = new();
- private Lazy> scriptParseInfoMap
- = new Lazy>(() => new Dictionary());
+ private Lazy> scriptParseInfoMap
+ = new Lazy>(() => new());
- private readonly ConcurrentDictionary completionExtensions = new ConcurrentDictionary();
- private readonly ConcurrentDictionary extAssemblyLastUpdateTime = new ConcurrentDictionary();
+ private readonly ConcurrentDictionary completionExtensions = new();
+ private readonly ConcurrentDictionary extAssemblyLastUpdateTime = new();
///
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
///
- internal Dictionary ScriptParseInfoMap
+ internal ConcurrentDictionary ScriptParseInfoMap
{
get
{
@@ -289,7 +289,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
WorkspaceServiceInstance.RegisterTextDocCloseCallback(HandleDidCloseTextDocumentNotification);
// Register a callback for when a connection is created
- ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection);
+ ConnectionServiceInstance.RegisterOnConnectionTask(StartUpdateLanguageServiceOnConnection);
// Register a callback for when a connection is closed
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
@@ -521,23 +521,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
};
}
- // turn off this code until needed (10/28/2016)
-#if false
- private async Task HandleReferencesRequest(
- ReferencesParams referencesParams,
- RequestContext requestContext)
- {
- await requestContext.SendResult(null);
- }
-
- private async Task HandleDocumentHighlightRequest(
- TextDocumentPosition textDocumentPosition,
- RequestContext requestContext)
- {
- await requestContext.SendResult(null);
- }
-#endif
-
internal async Task HandleSignatureHelpRequest(
TextDocumentPosition textDocumentPosition,
RequestContext requestContext)
@@ -551,18 +534,26 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
ScriptFile scriptFile = CurrentWorkspace.GetFile(
textDocumentPosition.TextDocument.Uri);
- SignatureHelp help = null;
if (scriptFile != null)
{
- help = GetSignatureHelp(textDocumentPosition, scriptFile);
- }
- if (help != null)
- {
- await requestContext.SendResult(help);
- }
- else
- {
- await requestContext.SendResult(new SignatureHelp());
+ // Start task asynchronously without blocking main thread - this is by design.
+ // Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
+ // All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
+#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ GetSignatureHelp(textDocumentPosition, scriptFile)
+ .ContinueWith(async task =>
+ {
+ var result = await task;
+ if (result != null)
+ {
+ await requestContext.SendResult(result);
+ }
+ else
+ {
+ await requestContext.SendResult(new SignatureHelp());
+ }
+ });
+#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
}
@@ -683,9 +674,33 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
/// Handle the rebuild IntelliSense cache notification
///
- public async Task HandleRebuildIntelliSenseNotification(
+ /// Rebuild params
+ /// Event context
+ /// Async task
+ public Task HandleRebuildIntelliSenseNotification(
RebuildIntelliSenseParams rebuildParams,
EventContext eventContext)
+ {
+ // Start task asynchronously without blocking main thread - this is by design.
+ // Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
+ // All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
+ Task.Factory.StartNew(async () =>
+ {
+ await DoHandleRebuildIntellisenseNotification(rebuildParams, eventContext);
+ }, CancellationToken.None,
+ TaskCreationOptions.None,
+ TaskScheduler.Default);
+
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Internal method to handle rebuild intellisense notification
+ ///
+ /// Rebuild params
+ /// Event context
+ /// Async task
+ public async Task DoHandleRebuildIntellisenseNotification(RebuildIntelliSenseParams rebuildParams, EventContext eventContext)
{
try
{
@@ -694,7 +709,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// This URI doesn't come in escaped - so if it's a file path with reserved characters (such as %)
// then we'll fail to find it since GetFile expects the URI to be a fully-escaped URI as that's
// what the document events are sent in as.
- var escapedOwnerUri = Uri.EscapeUriString(rebuildParams.OwnerUri);
+ var escapedOwnerUri = Uri.EscapeDataString(rebuildParams.OwnerUri);
// Skip closing this file if the file doesn't exist
var scriptFile = this.CurrentWorkspace.GetFile(escapedOwnerUri);
if (scriptFile == null)
@@ -710,47 +725,44 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// check that there is an active connection for the current editor
if (connInfo != null)
{
- await Task.Run(() =>
+ // Get the current ScriptInfo if one exists so we can lock it while we're rebuilding the cache
+ ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false);
+ if (scriptInfo != null && scriptInfo.IsConnected &&
+ Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
- // Get the current ScriptInfo if one exists so we can lock it while we're rebuilding the cache
- ScriptParseInfo scriptInfo = GetScriptParseInfo(connInfo.OwnerUri, createIfNotExists: false);
- if (scriptInfo != null && scriptInfo.IsConnected &&
- Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
+ try
{
- try
- {
- this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
- RemoveScriptParseInfo(rebuildParams.OwnerUri);
- UpdateLanguageServiceOnConnection(connInfo).Wait();
- }
- catch (Exception ex)
- {
- Logger.Error("Unknown error " + ex.ToString());
- }
- finally
- {
- // Set Metadata Build event to Signal state.
- Monitor.Exit(scriptInfo.BuildingMetadataLock);
- }
+ this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
+ RemoveScriptParseInfo(rebuildParams.OwnerUri);
+ await UpdateLanguageServiceOnConnection(connInfo);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Unknown error " + ex.ToString());
+ }
+ finally
+ {
+ // Set Metadata Build event to Signal state.
+ Monitor.Exit(scriptInfo.BuildingMetadataLock);
}
// if not in the preview window and diagnostics are enabled then run diagnostics
if (!IsPreviewWindow(scriptFile)
&& CurrentWorkspaceSettings.IsDiagnosticsEnabled)
{
- RunScriptDiagnostics(
- new ScriptFile[] { scriptFile },
- eventContext);
+ await RunScriptDiagnostics(
+ new ScriptFile[] { scriptFile },
+ eventContext);
}
// Send a notification to signal that autocomplete is ready
- ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
- });
- }
- else
- {
- // Send a notification to signal that autocomplete is ready
- await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
+ await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
+ }
+ else
+ {
+ // Send a notification to signal that autocomplete is ready
+ await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
+ }
}
}
catch (Exception ex)
@@ -887,121 +899,125 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
///
/// The ParseResult instance returned from SQL Parser
- public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
+ public Task ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
{
Logger.Verbose($"ParseAndBind - {scriptFile}");
// get or create the current parse info object
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientUri, createIfNotExists: true);
-
- if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
+ return Task.Run(() =>
{
- try
+ if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
{
- if (connInfo == null || !parseInfo.IsConnected)
+ try
{
- // parse on separate thread so stack size can be increased
- var parseThread = new Thread(() =>
+ if (connInfo == null || !parseInfo.IsConnected)
{
- try
- {
- // parse current SQL file contents to retrieve a list of errors
- ParseResult parseResult = Parser.IncrementalParse(
- scriptFile.Contents,
- parseInfo.ParseResult,
- this.DefaultParseOptions);
-
- parseInfo.ParseResult = parseResult;
- }
- catch (Exception e)
- {
- // Log the exception but don't rethrow it to prevent parsing errors from crashing SQL Tools Service
- Logger.Error(string.Format("An unexpected error occured while parsing: {0}", e.ToString()));
- }
- }, ConnectedBindingQueue.QueueThreadStackSize);
- parseThread.Start();
- parseThread.Join();
- }
- else
- {
- QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
- key: parseInfo.ConnectionKey,
- bindingTimeout: LanguageService.BindingTimeout,
- bindOperation: (bindingContext, cancelToken) =>
+ // parse on separate thread so stack size can be increased
+ var parseThread = new Thread(() =>
{
try
{
+ // parse current SQL file contents to retrieve a list of errors
ParseResult parseResult = Parser.IncrementalParse(
- scriptFile.Contents,
- parseInfo.ParseResult,
- bindingContext.ParseOptions);
+ scriptFile.Contents,
+ parseInfo.ParseResult,
+ this.DefaultParseOptions);
parseInfo.ParseResult = parseResult;
-
- List parseResults = new List();
- parseResults.Add(parseResult);
- if (bindingContext.IsConnected && bindingContext.Binder != null)
+ }
+ catch (Exception e)
+ {
+ // Log the exception but don't rethrow it to prevent parsing errors from crashing SQL Tools Service
+ Logger.Error(string.Format("An unexpected error occured while parsing: {0}", e.ToString()));
+ }
+ }, ConnectedBindingQueue.QueueThreadStackSize);
+ parseThread.Start();
+ parseThread.Join();
+ }
+ else
+ {
+ QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
+ key: parseInfo.ConnectionKey,
+ bindingTimeout: LanguageService.BindingTimeout,
+ bindOperation: (bindingContext, cancelToken) =>
+ {
+ try
{
- bindingContext.Binder.Bind(
- parseResults,
- connInfo.ConnectionDetails.DatabaseName,
- BindMode.Batch);
+ ParseResult parseResult = Parser.IncrementalParse(
+ scriptFile.Contents,
+ parseInfo.ParseResult,
+ bindingContext.ParseOptions);
+
+ parseInfo.ParseResult = parseResult;
+
+ List parseResults = new List();
+ parseResults.Add(parseResult);
+ if (bindingContext.IsConnected && bindingContext.Binder != null)
+ {
+ bindingContext.Binder.Bind(
+ parseResults,
+ connInfo.ConnectionDetails.DatabaseName,
+ BindMode.Batch);
+ }
+ }
+ catch (ConnectionException)
+ {
+ Logger.Error("Hit connection exception while binding - disposing binder object...");
+ }
+ catch (SqlParserInternalBinderError)
+ {
+ Logger.Error("Hit connection exception while binding - disposing binder object...");
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Unknown exception during parsing " + ex.ToString());
}
- }
- catch (ConnectionException)
- {
- Logger.Error("Hit connection exception while binding - disposing binder object...");
- }
- catch (SqlParserInternalBinderError)
- {
- Logger.Error("Hit connection exception while binding - disposing binder object...");
- }
- catch (Exception ex)
- {
- Logger.Error("Unknown exception during parsing " + ex.ToString());
- }
- return null;
- });
+ return null;
+ });
- queueItem.ItemProcessed.WaitOne();
+ queueItem.ItemProcessed.WaitOne();
+ }
+ }
+ catch (Exception ex)
+ {
+ // reset the parse result to do a full parse next time
+ parseInfo.ParseResult = null;
+ Logger.Error("Unknown exception during parsing " + ex.ToString());
+ }
+ finally
+ {
+ Monitor.Exit(parseInfo.BuildingMetadataLock);
}
}
- catch (Exception ex)
+ else
{
- // reset the parse result to do a full parse next time
- parseInfo.ParseResult = null;
- Logger.Error("Unknown exception during parsing " + ex.ToString());
+ Logger.Warning("Binding metadata lock timeout in ParseAndBind");
}
- finally
- {
- Monitor.Exit(parseInfo.BuildingMetadataLock);
- }
- }
- else
- {
- Logger.Warning("Binding metadata lock timeout in ParseAndBind");
- }
- return parseInfo.ParseResult;
+ return parseInfo.ParseResult;
+ });
+ }
+
+ ///
+ /// Runs UpdateLanguageServiceOnConnection as a background task
+ ///
+ /// Connection Info
+ ///
+ public Task StartUpdateLanguageServiceOnConnection(ConnectionInfo info)
+ {
+ // Start task asynchronously without blocking main thread - this is by design.
+ // Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
+ // All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
+ Task.Factory.StartNew(() => UpdateLanguageServiceOnConnection(info));
+ return Task.CompletedTask;
}
///
/// Starts a Task to update the autocomplete metadata provider when the user connects to a database
///
- /// Connection info
- public Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
- {
- return Task.Run(() =>
- {
- DoUpdateLanguageServiceOnConnection(info);
- });
- }
-
- ///
- /// Update the autocomplete metadata provider when the user connects to a database synchronously
- ///
- /// Connection info
- public void DoUpdateLanguageServiceOnConnection(ConnectionInfo info)
+ ///
+ public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
{
if (ConnectionService.IsDedicatedAdminConnection(info.ConnectionDetails))
{
@@ -1028,10 +1044,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Monitor.Exit(scriptInfo.BuildingMetadataLock);
}
}
- PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue);
-
- // Send a notification to signal that autocomplete is ready
- ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
+ await PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue).ContinueWith(async _ =>
+ {
+ // Send a notification to signal that autocomplete is ready
+ await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
+ });
}
@@ -1043,7 +1060,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
///
///
- internal void PrepopulateCommonMetadata(
+ internal async Task PrepopulateCommonMetadata(
ConnectionInfo info,
ScriptParseInfo scriptInfo,
ConnectedBindingQueue bindingQueue)
@@ -1060,71 +1077,73 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return;
}
- ParseAndBind(scriptFile, info);
-
- if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
+ await ParseAndBind(scriptFile, info).ContinueWith(t =>
{
- try
+ if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
- QueueItem queueItem = bindingQueue.QueueBindingOperation(
- key: scriptInfo.ConnectionKey,
- bindingTimeout: PrepopulateBindTimeout,
- waitForLockTimeout: PrepopulateBindTimeout,
- bindOperation: (bindingContext, cancelToken) =>
- {
- // parse a simple statement that returns common metadata
- ParseResult parseResult = Parser.Parse(
- "select ",
- bindingContext.ParseOptions);
- if (bindingContext.IsConnected && bindingContext.Binder != null)
+ try
+ {
+ QueueItem queueItem = bindingQueue.QueueBindingOperation(
+ key: scriptInfo.ConnectionKey,
+ bindingTimeout: PrepopulateBindTimeout,
+ waitForLockTimeout: PrepopulateBindTimeout,
+ bindOperation: (bindingContext, cancelToken) =>
{
- List parseResults = new List();
- parseResults.Add(parseResult);
- bindingContext.Binder.Bind(
- parseResults,
- info.ConnectionDetails.DatabaseName,
- BindMode.Batch);
-
- // get the completion list from SQL Parser
- var suggestions = Resolver.FindCompletions(
- parseResult, 1, 8,
- bindingContext.MetadataDisplayInfoProvider);
-
- // this forces lazy evaluation of the suggestion metadata
- AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
-
- parseResult = Parser.Parse(
- "exec ",
+ // parse a simple statement that returns common metadata
+ ParseResult parseResult = Parser.Parse(
+ "select ",
bindingContext.ParseOptions);
+ if (bindingContext.IsConnected && bindingContext.Binder != null)
+ {
+ List parseResults = new List();
+ parseResults.Add(parseResult);
+ bindingContext.Binder.Bind(
+ parseResults,
+ info.ConnectionDetails.DatabaseName,
+ BindMode.Batch);
- parseResults = new List();
- parseResults.Add(parseResult);
- bindingContext.Binder.Bind(
- parseResults,
- info.ConnectionDetails.DatabaseName,
- BindMode.Batch);
+ // get the completion list from SQL Parser
+ var suggestions = Resolver.FindCompletions(
+ parseResult, 1, 8,
+ bindingContext.MetadataDisplayInfoProvider);
- // get the completion list from SQL Parser
- suggestions = Resolver.FindCompletions(
- parseResult, 1, 6,
- bindingContext.MetadataDisplayInfoProvider);
+ // this forces lazy evaluation of the suggestion metadata
+ AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 8, 8);
- // this forces lazy evaluation of the suggestion metadata
- AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
- }
- return null;
- });
+ parseResult = Parser.Parse(
+ "exec ",
+ bindingContext.ParseOptions);
- queueItem.ItemProcessed.WaitOne();
+ parseResults = new List();
+ parseResults.Add(parseResult);
+ bindingContext.Binder.Bind(
+ parseResults,
+ info.ConnectionDetails.DatabaseName,
+ BindMode.Batch);
+
+ // get the completion list from SQL Parser
+ suggestions = Resolver.FindCompletions(
+ parseResult, 1, 6,
+ bindingContext.MetadataDisplayInfoProvider);
+
+ // this forces lazy evaluation of the suggestion metadata
+ AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
+ }
+ return null;
+ });
+
+ queueItem.ItemProcessed.WaitOne();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Exception in PrepopulateCommonMetadata " + ex.ToString());
+ }
+ finally
+ {
+ Monitor.Exit(scriptInfo.BuildingMetadataLock);
+ }
}
- catch
- {
- }
- finally
- {
- Monitor.Exit(scriptInfo.BuildingMetadataLock);
- }
- }
+ });
}
}
@@ -1348,7 +1367,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
if (RequiresReparse(scriptParseInfo, scriptFile))
{
- scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo);
+ scriptParseInfo.ParseResult = ParseAndBind(scriptFile, connInfo).GetAwaiter().GetResult();
}
// Get token from selected text
@@ -1499,7 +1518,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
///
/// Get function signature help for the current position
///
- internal SignatureHelp? GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
+ internal async Task GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
{
Logger.Verbose($"GetSignatureHelp - {scriptFile}");
int startLine = textDocumentPosition.Position.Line;
@@ -1522,8 +1541,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// reparse and bind the SQL statement if needed
if (RequiresReparse(scriptParseInfo, scriptFile))
{
- ParseAndBind(scriptFile, connInfo);
- } else
+ await ParseAndBind(scriptFile, connInfo);
+ }
+ else
{
Logger.Verbose($"GetSignatureHelp - No reparse needed for {scriptFile}");
}
@@ -1570,6 +1590,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Logger.Verbose($"GetSignatureHelp - Got result {queueItem.Result}");
return queueItem.GetResultAsT();
}
+ catch (Exception ex)
+ {
+ Logger.Error("Exception in GetSignatureHelp " + ex.ToString());
+ }
finally
{
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
@@ -1615,7 +1639,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// reparse and bind the SQL statement if needed
if (RequiresReparse(scriptParseInfo, scriptFile))
{
- ParseAndBind(scriptFile, connInfo);
+ await ParseAndBind(scriptFile, connInfo);
}
// if the parse failed then return the default list
@@ -1693,14 +1717,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// Gets a list of semantic diagnostic marks for the provided script file
///
///
- internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
+ internal async Task GetSemanticMarkers(ScriptFile scriptFile)
{
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientUri,
out connInfo);
- var parseResult = ParseAndBind(scriptFile, connInfo);
+ var parseResult = await ParseAndBind(scriptFile, connInfo);
// build a list of SQL script file markers from the errors
List markers = new List();
@@ -1835,10 +1859,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
Logger.Verbose("Analyzing script file: " + scriptFile.FilePath);
- ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
- Logger.Verbose("Analysis complete.");
- await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
+ // Start task asynchronously without blocking main thread - this is by design.
+ // Explanation: STS message queues are single-threaded queues, which should be unblocked as soon as possible.
+ // All Long-running tasks should be performed in a non-blocking background task, and results should be sent when ready.
+#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
+ GetSemanticMarkers(scriptFile).ContinueWith(async t =>
+ {
+ var semanticMarkers = t.GetAwaiter().GetResult();
+ Logger.Verbose("Analysis complete.");
+ await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
+ }, CancellationToken.None);
+#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
@@ -1861,7 +1893,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
else
{
Logger.Verbose($"Adding ScriptParseInfo for uri {uri}");
- this.ScriptParseInfoMap.Add(uri, scriptInfo);
+ this.ScriptParseInfoMap.TryAdd(uri, scriptInfo);
}
}
@@ -1887,7 +1919,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Logger.Verbose($"ScriptParseInfo for uri {uri} did not exist, creating new one");
// create a new script parse info object and initialize with the current settings
ScriptParseInfo scriptInfo = new ScriptParseInfo();
- this.ScriptParseInfoMap.Add(uri, scriptInfo);
+ this.ScriptParseInfoMap.TryAdd(uri, scriptInfo);
return scriptInfo;
}
else
@@ -1902,15 +1934,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
lock (this.parseMapLock)
{
- if (this.ScriptParseInfoMap.ContainsKey(uri))
- {
- Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
- return this.ScriptParseInfoMap.Remove(uri);
- }
- else
- {
- return false;
- }
+ Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
+ return this.ScriptParseInfoMap.TryRemove(uri, out _);
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
index 86814202..9efa6a60 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs
@@ -1064,22 +1064,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
// what the document events are sent in as.
var escapedOwnerUri = Uri.EscapeUriString(request.OwnerUri);
// If it is a document selection, we'll retrieve the text from the document
- ExecuteDocumentSelectionParams docRequest = request as ExecuteDocumentSelectionParams;
- if (docRequest != null)
+ if (request is ExecuteDocumentSelectionParams docRequest)
{
return GetSqlTextFromSelectionData(escapedOwnerUri, docRequest.QuerySelection);
}
// If it is a document statement, we'll retrieve the text from the document
- ExecuteDocumentStatementParams stmtRequest = request as ExecuteDocumentStatementParams;
- if (stmtRequest != null)
+ if (request is ExecuteDocumentStatementParams stmtRequest)
{
return GetSqlStatementAtPosition(escapedOwnerUri, stmtRequest.Line, stmtRequest.Column);
}
// If it is an ExecuteStringParams, return the text as is
- ExecuteStringParams stringRequest = request as ExecuteStringParams;
- if (stringRequest != null)
+ if (request is ExecuteStringParams stringRequest)
{
return stringRequest.Query;
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs
index 551056fc..d541cab3 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Workspace/Workspace.cs
@@ -15,6 +15,7 @@ using Microsoft.SqlTools.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
using System.Runtime.InteropServices;
using Microsoft.SqlTools.ServiceLayer.Utility;
+using System.Collections.Concurrent;
namespace Microsoft.SqlTools.ServiceLayer.Workspace
{
@@ -35,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
"vscode-notebook-cell"
};
- private Dictionary workspaceFiles = new Dictionary();
+ private ConcurrentDictionary workspaceFiles = new ConcurrentDictionary();
#endregion
@@ -121,7 +122,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
{
scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri,streamReader);
- this.workspaceFiles.Add(keyName, scriptFile);
+ this.workspaceFiles.TryAdd(keyName, scriptFile);
}
Logger.Verbose("Opened file on disk: " + resolvedFile.FilePath);
@@ -235,7 +236,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
{
scriptFile = new ScriptFile(resolvedFile.FilePath, resolvedFile.ClientUri, initialBuffer);
- this.workspaceFiles.Add(keyName, scriptFile);
+ this.workspaceFiles.TryAdd(keyName, scriptFile);
Logger.Verbose("Opened file as in-memory buffer: " + resolvedFile.FilePath);
}
@@ -260,7 +261,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Workspace
{
Validate.IsNotNull("scriptFile", scriptFile);
- this.workspaceFiles.Remove(scriptFile.Id);
+ this.workspaceFiles.TryRemove(scriptFile.Id, out _);
}
internal string GetBaseFilePath(string filePath)
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs
index 3aec39fe..957ee80f 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/LanguageServiceTests.cs
@@ -177,7 +177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
requestContext.Verify(x => x.SendError(It.IsAny(), 0, It.IsAny()), Times.Once);
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
- autoCompleteService.ParseAndBind(result.ScriptFile, result.ConnectionInfo);
+ await autoCompleteService.ParseAndBind(result.ScriptFile, result.ConnectionInfo);
scriptInfo.ConnectionKey = autoCompleteService.BindingQueue.AddConnectionContext(result.ConnectionInfo);
//Invoke auto completion with extension enabled
@@ -219,7 +219,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
/// provide signature help.
///
[Test]
- public void GetSignatureHelpReturnsNotNullIfParseInfoInitialized()
+ public async Task GetSignatureHelpReturnsNotNullIfParseInfoInitialized()
{
// When we make a connection to a live database
Hosting.ServiceHost.SendEventIgnoreExceptions = true;
@@ -243,14 +243,14 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
// If the SQL has already been parsed
var service = CreateLanguageService(result.ScriptFile);
- service.DoUpdateLanguageServiceOnConnection(result.ConnectionInfo);
+ await service.UpdateLanguageServiceOnConnection(result.ConnectionInfo);
// We should get back a non-null ScriptParseInfo
ScriptParseInfo? parseInfo = service.GetScriptParseInfo(result.ScriptFile.ClientUri);
Assert.That(parseInfo, Is.Not.Null, "ScriptParseInfo");
// And we should get back a non-null SignatureHelp
- SignatureHelp? signatureHelp = service.GetSignatureHelp(textDocument, result.ScriptFile);
+ SignatureHelp? signatureHelp = await service.GetSignatureHelp(textDocument, result.ScriptFile);
Assert.That(signatureHelp, Is.Not.Null, "SignatureHelp");
}
@@ -319,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
testDb.RunQuery("CREATE TABLE dbo.foo(col1 int)");
// And refresh the cache
- await langService.HandleRebuildIntelliSenseNotification(
+ await langService.DoHandleRebuildIntellisenseNotification(
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
new TestEventContext());
@@ -446,7 +446,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
testDb.RunQuery(createTableQueries);
// And refresh the cache
- await langService.HandleRebuildIntelliSenseNotification(
+ await langService.DoHandleRebuildIntellisenseNotification(
new RebuildIntelliSenseParams() { OwnerUri = connectionInfoResult.ScriptFile.ClientUri },
new TestEventContext());
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs
index 8aad63b4..71737a1b 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/LanguageServer/PeekDefinitionTests.cs
@@ -259,7 +259,7 @@ GO";
scriptFile.Contents = "select * from dbo.func ()";
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
- languageService.ScriptParseInfoMap.Add(scriptFile.ClientUri, scriptInfo);
+ languageService.ScriptParseInfoMap.TryAdd(scriptFile.ClientUri, scriptInfo);
// Pass in null connection info to force doing a local parse since that hits the BindingQueue timeout
// before we want it to (this is testing the timeout trying to fetch the definitions after the parse)
@@ -729,9 +729,9 @@ GO";
Thread.Sleep(2000);
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
- service.ParseAndBind(scriptFile, connInfo);
+ await service.ParseAndBind(scriptFile, connInfo);
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
- service.ScriptParseInfoMap.Add(OwnerUri, scriptInfo);
+ service.ScriptParseInfoMap.TryAdd(OwnerUri, scriptInfo);
// When I call the language service
var objectResult = service.GetDefinition(objectDocument, scriptFile, connInfo);
@@ -750,7 +750,7 @@ GO";
Cleanup(objectResult.Locations);
Cleanup(sysResult.Locations);
Cleanup(masterResult.Locations);
- service.ScriptParseInfoMap.Remove(OwnerUri);
+ service.ScriptParseInfoMap.TryRemove(OwnerUri, out _);
connInfo.RemoveAllConnections();
}
@@ -786,9 +786,9 @@ GO";
Thread.Sleep(2000);
ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true };
- service.ParseAndBind(scriptFile, connInfo);
+ await service.ParseAndBind(scriptFile, connInfo);
scriptInfo.ConnectionKey = bindingQueue.AddConnectionContext(connInfo);
- service.ScriptParseInfoMap.Add(TestUri, scriptInfo);
+ service.ScriptParseInfoMap.TryAdd(TestUri, scriptInfo);
// When I call the language service
var fnResult = service.GetDefinition(fnDocument, scriptFile, connInfo);
@@ -807,7 +807,7 @@ GO";
Cleanup(fnResult.Locations);
Cleanup(sysResult.Locations);
Cleanup(masterResult.Locations);
- service.ScriptParseInfoMap.Remove(TestUri);
+ service.ScriptParseInfoMap.TryRemove(TestUri, out _);
connInfo.RemoveAllConnections();
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs
index d4242c45..89642915 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/AutocompleteTests.cs
@@ -218,7 +218,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
};
- ParseResult parseResult = langService.ParseAndBind(scriptFile, null);
+ ParseResult parseResult = langService.ParseAndBind(scriptFile, null).GetAwaiter().GetResult();
ScriptParseInfo scriptParseInfo = langService.GetScriptParseInfo(scriptFile.ClientUri, true);
return new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs
index ce93f17c..03b553bd 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/LanguageServiceTests.cs
@@ -26,17 +26,17 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
public void LatestSqlParserIsUsedByDefault()
{
// This should only parse correctly on SQL server 2016 or newer
- const string sql2016Text =
+ const string sql2016Text =
@"CREATE SECURITY POLICY [FederatedSecurityPolicy]" + "\r\n" +
- @"ADD FILTER PREDICATE [rls].[fn_securitypredicate]([CustomerId])" + "\r\n" +
+ @"ADD FILTER PREDICATE [rls].[fn_securitypredicate]([CustomerId])" + "\r\n" +
@"ON [dbo].[Customer];";
-
+
LanguageService service = TestObjects.GetTestLanguageService();
// parse
var scriptFile = new ScriptFile();
scriptFile.SetFileContents(sql2016Text);
- ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
+ ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
// verify that no errors are detected
Assert.AreEqual(0, fileMarkers.Length);
@@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
// parse the sql statement
var scriptFile = new ScriptFile();
scriptFile.SetFileContents(sqlWithErrors);
- ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
+ ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
// verify there are no errors
Assert.AreEqual(0, fileMarkers.Length);
@@ -78,7 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
// parse sql statement
var scriptFile = new ScriptFile();
scriptFile.SetFileContents(sqlWithErrors);
- ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
+ ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
// verify there is one error
Assert.AreEqual(1, fileMarkers.Length);
@@ -97,7 +97,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
public void ParseMultilineSqlWithErrors()
{
// multiline sql with errors
- const string sqlWithErrors =
+ const string sqlWithErrors =
"SELECT *** FROM sys.objects;\n" +
"GO\n" +
"SELECT *** FROM sys.objects;\n";
@@ -108,7 +108,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
// parse sql
var scriptFile = new ScriptFile();
scriptFile.SetFileContents(sqlWithErrors);
- ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
+ ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile).GetAwaiter().GetResult();
// verify there are two errors
Assert.AreEqual(2, fileMarkers.Length);
@@ -140,29 +140,29 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
scriptFile.SetFileContents(docContent);
// When requesting SignatureHelp
- SignatureHelp signatureHelp = service.GetSignatureHelp(TestObjects.GetTestDocPosition(), scriptFile);
-
+ SignatureHelp signatureHelp = service.GetSignatureHelp(TestObjects.GetTestDocPosition(), scriptFile).GetAwaiter().GetResult();
+
// Then null is returned as no parse info can be used to find the signature
Assert.Null(signatureHelp);
}
[Test]
public void EmptyCompletionListTest()
- {
+ {
Assert.AreEqual(0, AutoCompleteHelper.EmptyCompletionList.Length);
}
internal sealed class TestScriptDocumentInfo : ScriptDocumentInfo
{
- public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
+ public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
string tokenText = null)
- :base(textDocumentPosition, scriptFile, scriptParseInfo)
+ : base(textDocumentPosition, scriptFile, scriptParseInfo)
{
this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
}
private string tokenText;
-
+
public override string TokenText
{
get
@@ -174,7 +174,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
[Test]
public void GetDefaultCompletionListWithNoMatchesTest()
- {
+ {
var scriptFile = new ScriptFile();
scriptFile.SetFileContents("koko wants a bananas");
@@ -183,10 +183,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
var scriptDocumentInfo = new TestScriptDocumentInfo(
new TextDocumentPosition()
{
- TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
+ TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
Position = new Position() { Line = 0, Character = 0 }
}, scriptFile, scriptInfo);
-
+
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
}