mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
setting timeout for oe tasks (#363)
This commit is contained in:
@@ -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,8 +109,34 @@ 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)
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Verbose, "OE expand canceled ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExpandResponse response = CreateExpandResponse(session, expandParams);
|
||||||
|
response.Nodes = nodes;
|
||||||
await serviceHost.SendEvent(ExpandCompleteNotification.Type, response);
|
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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
return (parentTable.HistoryTableID == historyTable.ID);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user