Remove extra level of tasks in binding queue (#79)

* Remove extra layer of tasks in binding queue

* Change order of assigning result to avoid race condition

* Add timeout log for the metadata lock event

* Fix test cases
This commit is contained in:
Karl Burtram
2016-10-08 00:06:35 +00:00
committed by GitHub
parent 03d7fd94c8
commit f32b0290bb
5 changed files with 73 additions and 85 deletions

View File

@@ -4,7 +4,6 @@
// //
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlServer.Management.SqlParser.Parser;
@@ -22,6 +21,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary> /// </summary>
public static class AutoCompleteHelper public static class AutoCompleteHelper
{ {
private const int PrepopulateBindTimeout = 60000;
private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance; private static WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
private static readonly string[] DefaultCompletionText = new string[] private static readonly string[] DefaultCompletionText = new string[]
@@ -579,6 +580,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
QueueItem queueItem = bindingQueue.QueueBindingOperation( QueueItem queueItem = bindingQueue.QueueBindingOperation(
key: scriptInfo.ConnectionKey, key: scriptInfo.ConnectionKey,
bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
bindOperation: (bindingContext, cancelToken) => bindOperation: (bindingContext, cancelToken) =>
{ {
// parse a simple statement that returns common metadata // parse a simple statement that returns common metadata
@@ -619,7 +621,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// this forces lazy evaluation of the suggestion metadata // this forces lazy evaluation of the suggestion metadata
AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6); AutoCompleteHelper.ConvertDeclarationsToCompletionItems(suggestions, 1, 6, 6);
return Task.FromResult(null as object); return null;
}); });
queueItem.ItemProcessed.WaitOne(); queueItem.ItemProcessed.WaitOne();

View File

@@ -59,8 +59,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary> /// </summary>
public QueueItem QueueBindingOperation( public QueueItem QueueBindingOperation(
string key, string key,
Func<IBindingContext, CancellationToken, Task<object>> bindOperation, Func<IBindingContext, CancellationToken, object> bindOperation,
Func<IBindingContext, Task<object>> timeoutOperation = null, Func<IBindingContext, object> timeoutOperation = null,
int? bindingTimeout = null) int? bindingTimeout = null)
{ {
// don't add null operations to the binding queue // don't add null operations to the binding queue
@@ -184,53 +184,48 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
continue; continue;
} }
try IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key);
if (bindingContext == null)
{ {
IBindingContext bindingContext = GetOrCreateBindingContext(queueItem.Key); queueItem.ItemProcessed.Set();
if (bindingContext == null) continue;
{ }
queueItem.ItemProcessed.Set();
continue; try
} {
// prefer the queue item binding item, otherwise use the context default timeout // prefer the queue item binding item, otherwise use the context default timeout
int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout; int bindTimeout = queueItem.BindingTimeout ?? bindingContext.BindingTimeout;
// handle the case a previous binding operation is still running // handle the case a previous binding operation is still running
if (!bindingContext.BindingLocked.WaitOne(bindTimeout)) if (!bindingContext.BindingLocked.WaitOne(bindTimeout))
{ {
queueItem.ResultsTask = Task.Run(() => queueItem.Result = queueItem.TimeoutOperation(bindingContext);
{ queueItem.ItemProcessed.Set();
var timeoutTask = queueItem.TimeoutOperation(bindingContext);
timeoutTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
return timeoutTask.Result;
});
continue; continue;
} }
// execute the binding operation // execute the binding operation
CancellationTokenSource cancelToken = new CancellationTokenSource(); object result = null;
queueItem.ResultsTask = queueItem.BindOperation( CancellationTokenSource cancelToken = new CancellationTokenSource();
bindingContext, var bindTask = Task.Run(() =>
cancelToken.Token); {
result = queueItem.BindOperation(
// set notification events once the binding operation task completes bindingContext,
queueItem.ResultsTask.ContinueWith((obj) => cancelToken.Token);
{ });
queueItem.ItemProcessed.Set();
bindingContext.BindingLocked.Set();
});
// check if the binding tasks completed within the binding timeout // check if the binding tasks completed within the binding timeout
if (!queueItem.ResultsTask.Wait(bindTimeout)) if (bindTask.Wait(bindTimeout))
{ {
queueItem.Result = result;
}
else
{
// if the task didn't complete then call the timeout callback // if the task didn't complete then call the timeout callback
if (queueItem.TimeoutOperation != null) if (queueItem.TimeoutOperation != null)
{ {
cancelToken.Cancel(); cancelToken.Cancel();
queueItem.ResultsTask = queueItem.TimeoutOperation(bindingContext); queueItem.Result = queueItem.TimeoutOperation(bindingContext);
queueItem.ResultsTask.ContinueWith((obj) => queueItem.ItemProcessed.Set());
} }
} }
} }
@@ -238,7 +233,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
// catch and log any exceptions raised in the binding calls // catch and log any exceptions raised in the binding calls
// set item processed to avoid deadlocks // set item processed to avoid deadlocks
Logger.Write(LogLevel.Error, "Binding queue threw exception " + ex.ToString()); Logger.Write(LogLevel.Error, "Binding queue threw exception " + ex.ToString());
}
finally
{
bindingContext.BindingLocked.Set();
queueItem.ItemProcessed.Set(); queueItem.ItemProcessed.Set();
} }

View File

@@ -39,7 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal const int HoverTimeout = 3000; internal const int HoverTimeout = 3000;
internal const int FindCompletionsTimeout = 3000; internal const int BindingTimeout = 3000;
internal const int FindCompletionStartTimeout = 50; internal const int FindCompletionStartTimeout = 50;
@@ -452,7 +452,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// get or create the current parse info object // get or create the current parse info object
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true); ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true);
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionsTimeout)) if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.BindingTimeout))
{ {
try try
{ {
@@ -472,6 +472,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{ {
QueueItem queueItem = this.BindingQueue.QueueBindingOperation( QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: parseInfo.ConnectionKey, key: parseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) => bindOperation: (bindingContext, cancelToken) =>
{ {
try try
@@ -503,7 +504,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString()); Logger.Write(LogLevel.Error, "Unknown exception during parsing " + ex.ToString());
} }
return Task.FromResult(null as object); return null;
}); });
queueItem.ItemProcessed.WaitOne(); queueItem.ItemProcessed.WaitOne();
@@ -520,6 +521,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
parseInfo.BuildingMetadataEvent.Set(); parseInfo.BuildingMetadataEvent.Set();
} }
} }
else
{
Logger.Write(LogLevel.Warning, "Binding metadata lock timeout in ParseAndBind");
}
return parseInfo.ParseResult; return parseInfo.ParseResult;
} }
@@ -644,13 +649,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
bindingContext.MetadataDisplayInfoProvider); bindingContext.MetadataDisplayInfoProvider);
// convert from the parser format to the VS Code wire format // convert from the parser format to the VS Code wire format
return Task.FromResult( return AutoCompleteHelper.ConvertQuickInfoToHover(
AutoCompleteHelper.ConvertQuickInfoToHover(
quickInfo, quickInfo,
startLine, startLine,
startColumn, startColumn,
endColumn endColumn);
) as object);
}); });
queueItem.ItemProcessed.WaitOne(); queueItem.ItemProcessed.WaitOne();
@@ -714,6 +717,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
QueueItem queueItem = this.BindingQueue.QueueBindingOperation( QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey, key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) => bindOperation: (bindingContext, cancelToken) =>
{ {
CompletionItem[] completions = null; CompletionItem[] completions = null;
@@ -741,12 +745,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
scriptParseInfo.BuildingMetadataEvent.Set(); scriptParseInfo.BuildingMetadataEvent.Set();
} }
return Task.FromResult(completions as object); return completions;
}, },
timeoutOperation: (bindingContext) => timeoutOperation: (bindingContext) =>
{ {
return Task.FromResult( return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions) as object);
}); });
queueItem.ItemProcessed.WaitOne(); queueItem.ItemProcessed.WaitOne();

View File

@@ -30,12 +30,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <summary> /// <summary>
/// Gets or sets the bind operation callback method /// Gets or sets the bind operation callback method
/// </summary> /// </summary>
public Func<IBindingContext, CancellationToken, Task<object>> BindOperation { get; set; } public Func<IBindingContext, CancellationToken, object> BindOperation { get; set; }
/// <summary> /// <summary>
/// Gets or sets the timeout operation to call if the bind operation doesn't finish within timeout period /// Gets or sets the timeout operation to call if the bind operation doesn't finish within timeout period
/// </summary> /// </summary>
public Func<IBindingContext, Task<object>> TimeoutOperation { get; set; } public Func<IBindingContext, object> TimeoutOperation { get; set; }
/// <summary> /// <summary>
/// Gets or sets an event to signal when this queue item has been processed /// Gets or sets an event to signal when this queue item has been processed
@@ -43,10 +43,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public ManualResetEvent ItemProcessed { get; set; } public ManualResetEvent ItemProcessed { get; set; }
/// <summary> /// <summary>
/// Gets or sets the task that was used to execute this queue item. /// Gets or sets the result of the queued task
/// This allows the queuer to retrieve the execution result.
/// </summary> /// </summary>
public Task<object> ResultsTask { get; set; } public object Result { get; set; }
/// <summary> /// <summary>
/// Gets or sets the binding operation timeout in milliseconds /// Gets or sets the binding operation timeout in milliseconds
@@ -54,13 +53,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public int? BindingTimeout { get; set; } public int? BindingTimeout { get; set; }
/// <summary> /// <summary>
/// Converts the result of the execution task to type T /// Converts the result of the execution to type T
/// </summary> /// </summary>
public T GetResultAsT<T>() where T : class public T GetResultAsT<T>() where T : class
{ {
var task = this.ResultsTask; //var task = this.ResultsTask;
return (task != null && task.IsCompleted && task.Result != null) return (this.Result != null)
? task.Result as T ? this.Result as T
: null; : null;
} }
} }

View File

@@ -89,45 +89,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
/// <summary> /// <summary>
/// Test bind operation callback /// Test bind operation callback
/// </summary> /// </summary>
private Task<object> TestBindOperation( private object TestBindOperation(
IBindingContext bindContext, IBindingContext bindContext,
CancellationToken cancelToken) CancellationToken cancelToken)
{ {
return Task.Run(() => cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay);
this.isCancelationRequested = cancelToken.IsCancellationRequested;
if (!this.isCancelationRequested)
{ {
cancelToken.WaitHandle.WaitOne(this.bindCallbackDelay); ++this.bindCallCount;
this.isCancelationRequested = cancelToken.IsCancellationRequested; }
if (!this.isCancelationRequested) return new CompletionItem[0];
{
++this.bindCallCount;
}
return new CompletionItem[0] as object;
});
} }
/// <summary> /// <summary>
/// Test callback for the bind timeout operation /// Test callback for the bind timeout operation
/// </summary> /// </summary>
private Task<object> TestTimeoutOperation( private object TestTimeoutOperation(
IBindingContext bindingContext) IBindingContext bindingContext)
{ {
++this.timeoutCallCount; ++this.timeoutCallCount;
return Task.FromResult(new CompletionItem[0] as object); return new CompletionItem[0];
} }
/// <summary>
/// Runs for a few seconds to allow the queue to pump any requests
/// </summary>
private void WaitForQueue(int delay = 5000)
{
int step = 50;
int steps = delay / step + 1;
for (int i = 0; i < steps; ++i)
{
Thread.Sleep(step);
}
}
/// <summary> /// <summary>
/// Queues a single task /// Queues a single task
/// </summary> /// </summary>
@@ -141,7 +125,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
bindOperation: TestBindOperation, bindOperation: TestBindOperation,
timeoutOperation: TestTimeoutOperation); timeoutOperation: TestTimeoutOperation);
WaitForQueue(); Thread.Sleep(1000);
this.bindingQueue.StopQueueProcessor(15000); this.bindingQueue.StopQueueProcessor(15000);
@@ -166,7 +150,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
timeoutOperation: TestTimeoutOperation); timeoutOperation: TestTimeoutOperation);
} }
WaitForQueue(); Thread.Sleep(2000);
this.bindingQueue.StopQueueProcessor(15000); this.bindingQueue.StopQueueProcessor(15000);
@@ -183,14 +167,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
{ {
InitializeTestSettings(); InitializeTestSettings();
this.bindCallbackDelay = 10000; this.bindCallbackDelay = 1000;
this.bindingQueue.QueueBindingOperation( this.bindingQueue.QueueBindingOperation(
key: "testkey", key: "testkey",
bindingTimeout: bindCallbackDelay / 2,
bindOperation: TestBindOperation, bindOperation: TestBindOperation,
timeoutOperation: TestTimeoutOperation); timeoutOperation: TestTimeoutOperation);
WaitForQueue(this.bindCallbackDelay + 2000); Thread.Sleep(this.bindCallbackDelay + 100);
this.bindingQueue.StopQueueProcessor(15000); this.bindingQueue.StopQueueProcessor(15000);