Switch from events to locks during parsing (#87)

* Switch from event to locks in binding.

* Remove unneeded null check.
This commit is contained in:
Karl Burtram
2016-10-12 15:09:24 -07:00
committed by GitHub
parent 9f39ac6014
commit c5f44ccee1
9 changed files with 87 additions and 85 deletions

View File

@@ -4,6 +4,7 @@
//
using System.Collections.Generic;
using System.Threading;
using Microsoft.SqlServer.Management.SqlParser.Binder;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser;
@@ -572,12 +573,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
var scriptFile = AutoCompleteHelper.WorkspaceServiceInstance.Workspace.GetFile(info.OwnerUri);
LanguageService.Instance.ParseAndBind(scriptFile, info);
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
try
{
scriptInfo.BuildingMetadataEvent.Reset();
QueueItem queueItem = bindingQueue.QueueBindingOperation(
key: scriptInfo.ConnectionKey,
bindingTimeout: AutoCompleteHelper.PrepopulateBindTimeout,
@@ -631,7 +630,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
scriptInfo.BuildingMetadataEvent.Set();
Monitor.Exit(scriptInfo.BuildingMetadataLock);
}
}
}

View File

@@ -191,19 +191,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
continue;
}
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.BindingLocked.WaitOne(bindTimeout))
if (!Monitor.TryEnter(bindingContext.BindingLock, bindTimeout))
{
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
queueItem.ItemProcessed.Set();
continue;
}
lockTaken = true;
// execute the binding operation
object result = null;
CancellationTokenSource cancelToken = new CancellationTokenSource();
@@ -237,7 +240,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
bindingContext.BindingLocked.Set();
if (lockTaken)
{
Monitor.Exit(bindingContext.BindingLock);
}
queueItem.ItemProcessed.Set();
}

View File

@@ -4,7 +4,6 @@
//
using System;
using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
@@ -21,6 +20,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
private ParseOptions parseOptions;
private object bindingLock;
private ServerConnection serverConnection;
/// <summary>
@@ -28,7 +29,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
public ConnectedBindingContext()
{
this.BindingLocked = new ManualResetEvent(initialState: true);
this.bindingLock = new object();
this.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
this.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
}
@@ -72,9 +73,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public IBinder Binder { get; set; }
/// <summary>
/// Gets or sets an event to signal if a binding operation is in progress
/// Gets the binding lock object
/// </summary>
public ManualResetEvent BindingLocked { get; set; }
public object BindingLock
{
get
{
return this.bindingLock;
}
}
/// <summary>
/// Gets or sets the binding operation timeout in milliseconds

View File

@@ -63,22 +63,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
string connectionKey = GetConnectionContextKey(connInfo);
IBindingContext bindingContext = this.GetOrCreateBindingContext(connectionKey);
try
lock (bindingContext.BindingLock)
{
// increase the connection timeout to at least 30 seconds and and build connection string
// enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections
int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout;
bool? originalPersistSecurityInfo = connInfo.ConnectionDetails.PersistSecurityInfo;
connInfo.ConnectionDetails.ConnectTimeout = Math.Max(DefaultMinimumConnectionTimeout, originalTimeout ?? 0);
connInfo.ConnectionDetails.PersistSecurityInfo = true;
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
connInfo.ConnectionDetails.ConnectTimeout = originalTimeout;
connInfo.ConnectionDetails.PersistSecurityInfo = originalPersistSecurityInfo;
// open a dedicated binding server connection
SqlConnection sqlConn = new SqlConnection(connectionString);
if (sqlConn != null)
try
{
// increase the connection timeout to at least 30 seconds and and build connection string
// enable PersistSecurityInfo to handle issues in SMO where the connection context is lost in reconnections
int? originalTimeout = connInfo.ConnectionDetails.ConnectTimeout;
bool? originalPersistSecurityInfo = connInfo.ConnectionDetails.PersistSecurityInfo;
connInfo.ConnectionDetails.ConnectTimeout = Math.Max(DefaultMinimumConnectionTimeout, originalTimeout ?? 0);
connInfo.ConnectionDetails.PersistSecurityInfo = true;
string connectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails);
connInfo.ConnectionDetails.ConnectTimeout = originalTimeout;
connInfo.ConnectionDetails.PersistSecurityInfo = originalPersistSecurityInfo;
// open a dedicated binding server connection
SqlConnection sqlConn = new SqlConnection(connectionString);
sqlConn.Open();
// populate the binding context to work with the SMO metadata provider
@@ -91,16 +91,12 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
bindingContext.Binder = BinderProvider.CreateBinder(bindingContext.SmoMetadataProvider);
bindingContext.ServerConnection = serverConn;
bindingContext.BindingTimeout = ConnectedBindingQueue.DefaultBindingTimeout;
bindingContext.IsConnected = true;
bindingContext.IsConnected = true;
}
}
catch (Exception)
{
bindingContext.IsConnected = false;
}
finally
{
bindingContext.BindingLocked.Set();
catch (Exception)
{
bindingContext.IsConnected = false;
}
}
return connectionKey;

View File

@@ -44,9 +44,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
IBinder Binder { get; set; }
/// <summary>
/// Gets or sets an event to signal if a binding operation is in progress
/// Gets the binding lock object
/// </summary>
ManualResetEvent BindingLocked { get; set; }
object BindingLock { get; }
/// <summary>
/// Gets or sets the binding operation timeout in milliseconds

View File

@@ -452,12 +452,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// get or create the current parse info object
ScriptParseInfo parseInfo = GetScriptParseInfo(scriptFile.ClientFilePath, createIfNotExists: true);
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.BindingTimeout))
if (Monitor.TryEnter(parseInfo.BuildingMetadataLock, LanguageService.BindingTimeout))
{
try
{
parseInfo.BuildingMetadataEvent.Reset();
if (connInfo == null || !parseInfo.IsConnected)
{
// parse current SQL file contents to retrieve a list of errors
@@ -518,7 +516,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
parseInfo.BuildingMetadataEvent.Set();
Monitor.Exit(parseInfo.BuildingMetadataLock);
}
}
else
@@ -538,11 +536,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
await Task.Run(() =>
{
ScriptParseInfo scriptInfo = GetScriptParseInfo(info.OwnerUri, createIfNotExists: true);
if (scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout))
if (Monitor.TryEnter(scriptInfo.BuildingMetadataLock, LanguageService.OnConnectionWaitTimeout))
{
try
{
scriptInfo.BuildingMetadataEvent.Reset();
{
scriptInfo.ConnectionKey = this.BindingQueue.AddConnectionContext(info);
scriptInfo.IsConnected = true;
@@ -556,7 +553,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
// Set Metadata Build event to Signal state.
// (Tell Language Service that I am ready with Metadata Provider Object)
scriptInfo.BuildingMetadataEvent.Set();
Monitor.Exit(scriptInfo.BuildingMetadataLock);
}
}
@@ -631,9 +628,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null)
{
if (scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock, LanguageService.FindCompletionStartTimeout))
{
scriptParseInfo.BuildingMetadataEvent.Reset();
try
{
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
@@ -661,8 +657,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
finally
{
scriptParseInfo.BuildingMetadataEvent.Set();
}
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
}
@@ -691,8 +687,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
this.currentCompletionParseInfo = null;
// Take a reference to the list at a point in time in case we update and replace the list
// get the current script parse info object
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
if (connInfo == null || scriptParseInfo == null)
{
@@ -711,18 +706,18 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
if (scriptParseInfo.IsConnected
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
{
scriptParseInfo.BuildingMetadataEvent.Reset();
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
CompletionItem[] completions = null;
try
&& Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock, LanguageService.FindCompletionStartTimeout))
{
try
{
// queue the completion task with the binding queue
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
CompletionItem[] completions = null;
// get the completion list from SQL Parser
scriptParseInfo.CurrentSuggestions = Resolver.FindCompletions(
scriptParseInfo.ParseResult,
@@ -738,26 +733,27 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
scriptParseInfo.CurrentSuggestions,
startLine,
startColumn,
endColumn);
}
finally
{
scriptParseInfo.BuildingMetadataEvent.Set();
}
endColumn);
return completions;
},
timeoutOperation: (bindingContext) =>
return completions;
},
timeoutOperation: (bindingContext) =>
{
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
});
queueItem.ItemProcessed.WaitOne();
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
if (completionItems != null && completionItems.Length > 0)
{
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);
});
queueItem.ItemProcessed.WaitOne();
var completionItems = queueItem.GetResultAsT<CompletionItem[]>();
if (completionItems != null && completionItems.Length > 0)
{
return completionItems;
return completionItems;
}
}
finally
{
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn, useLowerCaseSuggestions);

View File

@@ -5,7 +5,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{

View File

@@ -4,7 +4,6 @@
//
using System.Collections.Generic;
using System.Threading;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser;
@@ -15,14 +14,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// </summary>
internal class ScriptParseInfo
{
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
private object buildingMetadataLock = new object();
/// <summary>
/// Event which tells if MetadataProvider is built fully or not
/// </summary>
public ManualResetEvent BuildingMetadataEvent
public object BuildingMetadataLock
{
get { return this.buildingMetadataEvent; }
get { return this.buildingMetadataLock; }
}
/// <summary>

View File

@@ -4,7 +4,6 @@
//
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.SmoMetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Binder;
@@ -25,7 +24,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
{
public TestBindingContext()
{
this.BindingLocked = new ManualResetEvent(initialState: true);
this.BindingLock = new object();
this.BindingTimeout = 3000;
}
@@ -39,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
public IBinder Binder { get; set; }
public ManualResetEvent BindingLocked { get; set; }
public object BindingLock { get; set; }
public int BindingTimeout { get; set; }