setting timeout for oe tasks (#363)

This commit is contained in:
Leila Lali
2017-05-26 21:54:45 -07:00
committed by GitHub
parent 29c9b5fa51
commit 0d00534f6a
5 changed files with 157 additions and 19 deletions

View File

@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Composition; using System.Composition;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.SqlTools.Extensibility; using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.Hosting; using Microsoft.SqlTools.Hosting;
@@ -19,6 +20,8 @@ using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel; using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
@@ -39,6 +42,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
private readonly Lazy<Dictionary<string, HashSet<ChildFactory>>> applicableNodeChildFactories; private readonly Lazy<Dictionary<string, HashSet<ChildFactory>>> applicableNodeChildFactories;
private IMultiServiceProvider serviceProvider; private IMultiServiceProvider serviceProvider;
/// <summary>
/// This timeout limits the amount of time that object explorer tasks can take to complete
/// </summary>
private ObjectExplorerSettings settings;
/// <summary> /// <summary>
/// Singleton constructor /// Singleton constructor
/// </summary> /// </summary>
@@ -101,10 +109,36 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
serviceHost.SetRequestHandler(ExpandRequest.Type, HandleExpandRequest); serviceHost.SetRequestHandler(ExpandRequest.Type, HandleExpandRequest);
serviceHost.SetRequestHandler(RefreshRequest.Type, HandleRefreshRequest); serviceHost.SetRequestHandler(RefreshRequest.Type, HandleRefreshRequest);
serviceHost.SetRequestHandler(CloseSessionRequest.Type, HandleCloseSessionRequest); serviceHost.SetRequestHandler(CloseSessionRequest.Type, HandleCloseSessionRequest);
WorkspaceService<SqlToolsSettings> workspaceService = WorkspaceService;
if (workspaceService != null)
{
workspaceService.RegisterConfigChangeCallback(HandleDidChangeConfigurationNotification);
}
} }
/// <summary>
/// Gets the workspace service. Note: should handle case where this is null in cases where unit tests do not set this up
/// </summary>
private WorkspaceService<SqlToolsSettings> WorkspaceService
{
get { return serviceProvider.GetService<WorkspaceService<SqlToolsSettings>>(); }
}
/// <summary>
/// Ensure formatter settings are always up to date
/// </summary>
public Task HandleDidChangeConfigurationNotification(
SqlToolsSettings newSettings,
SqlToolsSettings oldSettings,
EventContext eventContext)
{
// update the current settings to reflect any changes (assuming formatter settings exist)
settings = newSettings.SqlTools.ObjectExplorer ?? settings;
return Task.FromResult(true);
}
internal async Task HandleCreateSessionRequest(ConnectionDetails connectionDetails, RequestContext<CreateSessionResponse> context) internal async Task HandleCreateSessionRequest(ConnectionDetails connectionDetails, RequestContext<CreateSessionResponse> context)
{ {
Logger.Write(LogLevel.Verbose, "HandleCreateSessionRequest"); Logger.Write(LogLevel.Verbose, "HandleCreateSessionRequest");
@@ -140,8 +174,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
string uri = expandParams.SessionId; string uri = expandParams.SessionId;
ObjectExplorerSession session = null; ObjectExplorerSession session = null;
NodeInfo[] nodes = null;
ExpandResponse response;
if (!sessionMap.TryGetValue(uri, out session)) if (!sessionMap.TryGetValue(uri, out session))
{ {
Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} ");
@@ -170,7 +202,6 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
string uri = refreshParams.SessionId; string uri = refreshParams.SessionId;
ObjectExplorerSession session = null; ObjectExplorerSession session = null;
ExpandResponse response;
if (!sessionMap.TryGetValue(uri, out session)) if (!sessionMap.TryGetValue(uri, out session))
{ {
Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} "); Logger.Write(LogLevel.Verbose, $"Cannot expand object explorer node. Couldn't find session for uri. {uri} ");
@@ -237,11 +268,28 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
private void RunCreateSessionTask(ConnectionDetails connectionDetails, string uri) private void RunCreateSessionTask(ConnectionDetails connectionDetails, string uri)
{ {
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
if (connectionDetails != null && !string.IsNullOrEmpty(uri)) if (connectionDetails != null && !string.IsNullOrEmpty(uri))
{ {
Task task = CreateSessionAsync(connectionDetails, uri); Task task = CreateSessionAsync(connectionDetails, uri, cancellationTokenSource.Token);
CreateSessionTask = task; CreateSessionTask = task;
Task.Run(async () => await task); Task.Run(async () =>
{
ObjectExplorerTaskResult result = await RunTaskWithTimeout(task, settings.CreateSessionTimeout);
if (result != null && !result.IsComplete)
{
cancellationTokenSource.Cancel();
SessionCreatedParameters response = new SessionCreatedParameters
{
Success = false,
SessionId = uri,
ErrorMessage = result.Exception != null ? result.Exception.Message : $"Failed to create session for session id {uri}"
};
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response);
}
return result;
});
} }
} }
@@ -254,7 +302,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
private set; private set;
} }
private async Task<SessionCreatedParameters> CreateSessionAsync(ConnectionDetails connectionDetails, string uri) private async Task<SessionCreatedParameters> CreateSessionAsync(ConnectionDetails connectionDetails, string uri, CancellationToken cancellationToken)
{ {
ObjectExplorerSession session; ObjectExplorerSession session;
if (!sessionMap.TryGetValue(uri, out session)) if (!sessionMap.TryGetValue(uri, out session))
@@ -264,7 +312,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
} }
SessionCreatedParameters response; SessionCreatedParameters response;
if (session != null) if (session != null && !cancellationToken.IsCancellationRequested)
{ {
// Else we have a session available, response with existing session information // Else we have a session available, response with existing session information
response = new SessionCreatedParameters response = new SessionCreatedParameters
@@ -275,7 +323,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
ErrorMessage = session.ErrorMessage ErrorMessage = session.ErrorMessage
}; };
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response); await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, response);
return response; return response;
} }
return null; return null;
@@ -366,14 +414,40 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false)
{ {
Task task = ExpandNodeAsync(session, expandParams, forceRefresh); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Task task = ExpandNodeAsync(session, expandParams, cancellationTokenSource.Token, forceRefresh);
ExpandTask = task; ExpandTask = task;
Task.Run(async () => Task.Run(async () =>
{ {
await task; ObjectExplorerTaskResult result = await RunTaskWithTimeout(task, settings.ExpandTimeout);
if (result != null && !result.IsComplete)
{
cancellationTokenSource.Cancel();
ExpandResponse response = CreateExpandResponse(session, expandParams);
response.ErrorMessage = result.Exception != null ? result.Exception.Message: $"Failed to expand node: {expandParams.NodePath} in session {session.Uri}";
await serviceHost.SendEvent(ExpandCompleteNotification.Type, response);
}
return result;
}); });
} }
private async Task<ObjectExplorerTaskResult> RunTaskWithTimeout(Task task, int timeoutInSec)
{
ObjectExplorerTaskResult result = new ObjectExplorerTaskResult();
TimeSpan timeout = TimeSpan.FromSeconds(timeoutInSec);
await Task.WhenAny(task, Task.Delay(timeout));
result.IsComplete = task.IsCompleted;
if(task.Exception != null)
{
result.Exception = task.Exception;
}
else if (!task.IsCompleted)
{
result.Exception = new TimeoutException($"Object Explorer task didn't completed in {timeoutInSec} seconds.");
}
return result;
}
/// <summary> /// <summary>
/// For tests only /// For tests only
/// </summary> /// </summary>
@@ -383,15 +457,26 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
set; set;
} }
private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false) private async Task ExpandNodeAsync(ObjectExplorerSession session, ExpandParams expandParams, CancellationToken cancellationToken, bool forceRefresh = false)
{ {
NodeInfo[] nodes = null; NodeInfo[] nodes = null;
nodes = await ExpandNode(session, expandParams.NodePath, forceRefresh); nodes = await ExpandNode(session, expandParams.NodePath, forceRefresh);
ExpandResponse response = new ExpandResponse() { Nodes = nodes, SessionId = session.Uri, NodePath = expandParams.NodePath }; if (cancellationToken.IsCancellationRequested)
await serviceHost.SendEvent(ExpandCompleteNotification.Type, response); {
Logger.Write(LogLevel.Verbose, "OE expand canceled ");
}
else
{
ExpandResponse response = CreateExpandResponse(session, expandParams);
response.Nodes = nodes;
await serviceHost.SendEvent(ExpandCompleteNotification.Type, response);
}
} }
private ExpandResponse CreateExpandResponse(ObjectExplorerSession session, ExpandParams expandParams)
{
return new ExpandResponse() { SessionId = session.Uri, NodePath = expandParams.NodePath };
}
private async Task<T> HandleRequestAsync<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType) private async Task<T> HandleRequestAsync<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType)
{ {
@@ -491,6 +576,12 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
applicableFactories.Add(factory); applicableFactories.Add(factory);
} }
internal class ObjectExplorerTaskResult
{
public bool IsComplete { get; set; }
public Exception Exception { get; set; }
}
internal class ObjectExplorerSession internal class ObjectExplorerSession
{ {
private ConnectionService connectionService; private ConnectionService connectionService;

View File

@@ -3,6 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
// //
using System;
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
@@ -16,8 +17,19 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{ {
Table parentTable = parent as Table; Table parentTable = parent as Table;
Table historyTable = smoObject as Table; Table historyTable = smoObject as Table;
if (parentTable != null && historyTable != null)
return (parentTable.HistoryTableID == historyTable.ID); {
try
{
return (parentTable.HistoryTableID == historyTable.ID);
}
catch(Exception)
{
//TODO: have a better filtering here. HistoryTable is not available for SQL 2014.
//and the property throws exception here
}
}
return false;
} }
} }
} }

View File

@@ -312,7 +312,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Scripting
if (operation == ScriptOperation.Select) if (operation == ScriptOperation.Select)
{ {
return string.Format( return string.Format(
@"SELECT TOP 100 * " + Environment.NewLine + @"FROM {0}.{1}", @"SELECT TOP 1000 * " + Environment.NewLine + @"FROM {0}.{1}",
metadata.Schema, metadata.Name); metadata.Schema, metadata.Name);
} }
else if (operation == ScriptOperation.Create) else if (operation == ScriptOperation.Create)

View File

@@ -0,0 +1,29 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
namespace Microsoft.SqlTools.ServiceLayer.SqlContext
{
/// <summary>
/// Contract for receiving object explorer settings as part of workspace settings
/// </summary>
public class ObjectExplorerSettings
{
public ObjectExplorerSettings()
{
CreateSessionTimeout = 10;
ExpandTimeout = 10;
}
/// <summary>
/// Number of seconds to wait before fail create session request with timeout error
/// </summary>
public int CreateSessionTimeout { get; set; }
/// <summary>
/// Number of seconds to wait before fail expand request with timeout error
/// </summary>
public int ExpandTimeout { get; set; }
}
}

View File

@@ -134,5 +134,11 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
/// </summary> /// </summary>
[JsonProperty("format")] [JsonProperty("format")]
public FormatterSettings Format { get; set; } public FormatterSettings Format { get; set; }
/// <summary>
/// Gets or sets the formatter settings
/// </summary>
[JsonProperty("objectExplorer")]
public ObjectExplorerSettings ObjectExplorer { get; set; }
} }
} }