Fixes BindingQueue, GetSignatureHelp and RefreshIntellisenseCache to be truly async (#2175)

This commit is contained in:
Cheena Malhotra
2023-08-23 13:46:13 -07:00
committed by GitHub
parent fcd84f242a
commit cea10c13e6
8 changed files with 433 additions and 414 deletions

View File

@@ -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
{
@@ -272,7 +271,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
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)
{
@@ -300,13 +299,39 @@ 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) =>
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
{
lock (this.bindingQueueLock)
{
// verify the binding queue is still empty
if (this.bindingQueue.Count == 0)
{
// reset the item queued event since we've processed all the pending items
this.itemQueuedEvent.Reset();
}
}
}
}
}
private void DispatchQueueItem(IBindingContext bindingContext, QueueItem queueItem)
{
bool lockTaken = false;
try
{
// prefer the queue item binding item, otherwise use the context default timeout
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
// 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))
@@ -326,8 +351,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
queueItem.ItemProcessed.Set();
}
return;
}
bindingContext.BindingLock.Reset();
@@ -361,14 +384,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
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);
}
}
@@ -379,22 +400,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
try
{
// check if the binding tasks completed within the binding timeout
if (bindTask.Wait(bindTimeout))
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();
}
@@ -426,28 +444,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
queueItem.ItemProcessed.Set();
}
}, TaskContinuationOptions.None);
// if a queue processing cancellation was requested then exit the loop
if (token.IsCancellationRequested)
{
break;
}
}
}
finally
{
lock (this.bindingQueueLock)
{
// verify the binding queue is still empty
if (this.bindingQueue.Count == 0)
{
// reset the item queued event since we've processed all the pending items
this.itemQueuedEvent.Reset();
}
}
}
}
}
/// <summary>

View File

@@ -107,18 +107,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
compatibilityLevel: DatabaseCompatibilityLevel.Current,
transactSqlVersion: TransactSqlVersion.Current);
private ConcurrentDictionary<string, bool> nonMssqlUriMap = new ConcurrentDictionary<string, bool>();
private ConcurrentDictionary<string, bool> nonMssqlUriMap = new();
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
private Lazy<ConcurrentDictionary<string, ScriptParseInfo>> scriptParseInfoMap
= new Lazy<ConcurrentDictionary<string, ScriptParseInfo>>(() => new());
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new ConcurrentDictionary<string, ICompletionExtension>();
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new ConcurrentDictionary<string, DateTime>();
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new();
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new();
/// <summary>
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
/// </summary>
internal Dictionary<string, ScriptParseInfo> ScriptParseInfoMap
internal ConcurrentDictionary<string, ScriptParseInfo> 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<Location[]> requestContext)
{
await requestContext.SendResult(null);
}
private async Task HandleDocumentHighlightRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<DocumentHighlight[]> requestContext)
{
await requestContext.SendResult(null);
}
#endif
internal async Task HandleSignatureHelpRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<SignatureHelp> requestContext)
@@ -551,19 +534,27 @@ 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)
// 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 =>
{
await requestContext.SendResult(help);
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
/// <summary>
/// Handle the rebuild IntelliSense cache notification
/// </summary>
public async Task HandleRebuildIntelliSenseNotification(
/// <param name="rebuildParams">Rebuild params</param>
/// <param name="eventContext">Event context</param>
/// <returns>Async task</returns>
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;
}
/// <summary>
/// Internal method to handle rebuild intellisense notification
/// </summary>
/// <param name="rebuildParams">Rebuild params</param>
/// <param name="eventContext">Event context</param>
/// <returns>Async task</returns>
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)
@@ -709,8 +724,6 @@ 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);
@@ -721,7 +734,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
this.BindingQueue.AddConnectionContext(connInfo, featureName: Constants.LanguageServiceFeature, overwrite: true);
RemoveScriptParseInfo(rebuildParams.OwnerUri);
UpdateLanguageServiceOnConnection(connInfo).Wait();
await UpdateLanguageServiceOnConnection(connInfo);
}
catch (Exception ex)
{
@@ -732,20 +745,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// 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(
await RunScriptDiagnostics(
new ScriptFile[] { scriptFile },
eventContext);
}
// Send a notification to signal that autocomplete is ready
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
});
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = connInfo.OwnerUri });
}
else
{
@@ -753,6 +764,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = rebuildParams.OwnerUri });
}
}
}
catch (Exception ex)
{
Logger.Error("Unknown error " + ex.ToString());
@@ -887,12 +899,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="filePath"></param>
/// <param name="sqlText"></param>
/// <returns>The ParseResult instance returned from SQL Parser</returns>
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
public Task<ParseResult> ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
{
Logger.Verbose($"ParseAndBind - {scriptFile}");
// get or create the current parse info object
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientUri, createIfNotExists: true);
return Task.Run(() =>
{
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
{
try
@@ -983,25 +996,28 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
return parseInfo.ParseResult;
});
}
/// <summary>
/// Runs UpdateLanguageServiceOnConnection as a background task
/// </summary>
/// <param name="info">Connection Info</param>
/// <returns></returns>
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;
}
/// <summary>
/// Starts a Task to update the autocomplete metadata provider when the user connects to a database
/// </summary>
/// <param name="info">Connection info</param>
public Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
{
return Task.Run(() =>
{
DoUpdateLanguageServiceOnConnection(info);
});
}
/// <summary>
/// Update the autocomplete metadata provider when the user connects to a database synchronously
/// </summary>
/// <param name="info">Connection info</param>
public void DoUpdateLanguageServiceOnConnection(ConnectionInfo info)
/// <param name="info"></param>
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);
await PrepopulateCommonMetadata(info, scriptInfo, this.BindingQueue).ContinueWith(async _ =>
{
// Send a notification to signal that autocomplete is ready
ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
await ServiceHostInstance.SendEvent(IntelliSenseReadyNotification.Type, new IntelliSenseReadyParams() { OwnerUri = info.OwnerUri });
});
}
@@ -1043,7 +1060,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
/// <param name="info"></param>
/// <param name="scriptInfo"></param>
internal void PrepopulateCommonMetadata(
internal async Task PrepopulateCommonMetadata(
ConnectionInfo info,
ScriptParseInfo scriptInfo,
ConnectedBindingQueue bindingQueue)
@@ -1060,8 +1077,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return;
}
ParseAndBind(scriptFile, info);
await ParseAndBind(scriptFile, info).ContinueWith(t =>
{
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
try
@@ -1117,14 +1134,16 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
queueItem.ItemProcessed.WaitOne();
}
catch
catch (Exception ex)
{
Logger.Error("Exception in PrepopulateCommonMetadata " + ex.ToString());
}
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
/// <summary>
/// Get function signature help for the current position
/// </summary>
internal SignatureHelp? GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
internal async Task<SignatureHelp?> 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<SignatureHelp>();
}
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
/// </summary>
/// <param name="scriptFile"></param>
internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
internal async Task<ScriptFileMarker[]> 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<ScriptFileMarker> markers = new List<ScriptFileMarker>();
@@ -1835,10 +1859,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
Logger.Verbose("Analyzing script file: " + scriptFile.FilePath);
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
Logger.Verbose("Analysis complete.");
// 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
@@ -1901,16 +1933,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal bool RemoveScriptParseInfo(string uri)
{
lock (this.parseMapLock)
{
if (this.ScriptParseInfoMap.ContainsKey(uri))
{
Logger.Verbose($"Removing ScriptParseInfo for uri {uri}");
return this.ScriptParseInfoMap.Remove(uri);
}
else
{
return false;
}
return this.ScriptParseInfoMap.TryRemove(uri, out _);
}
}

View File

@@ -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;
}

View File

@@ -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<string, ScriptFile> workspaceFiles = new Dictionary<string, ScriptFile>();
private ConcurrentDictionary<string, ScriptFile> workspaceFiles = new ConcurrentDictionary<string, ScriptFile>();
#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)

View File

@@ -177,7 +177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.LanguageServer
requestContext.Verify(x => x.SendError(It.IsAny<string>(), 0, It.IsAny<string>()), 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.
/// </summary>
[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());

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
// 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);
@@ -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,7 +140,7 @@ 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);
@@ -156,7 +156,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer
{
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;
}