diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
index 9af57517..79c96901 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/BindingQueue.cs
@@ -5,12 +5,15 @@
using System;
using System.Collections.Generic;
+using System.Data.SqlClient;
+using System.Diagnostics;
+using System.Linq;
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.SqlTools.Utility;
-using System.Linq;
using Microsoft.SqlTools.ServiceLayer.Utility;
-using System.Diagnostics;
+using Microsoft.SqlTools.Utility;
+
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
{
@@ -33,6 +36,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private Task queueProcessorTask;
+ public delegate void UnhandledExceptionDelegate(string connectionKey, Exception ex);
+
+ public event UnhandledExceptionDelegate OnUnhandledException;
+
///
/// Map from context keys to binding context instances
/// Internal for testing purposes only
@@ -351,11 +358,28 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
Logger.Write(TraceEventType.Error, "Unexpected exception on the binding queue: " + ex.ToString());
if (queueItem.ErrorHandler != null)
{
- result = queueItem.ErrorHandler(ex);
+ try
+ {
+ result = queueItem.ErrorHandler(ex);
+ }
+ catch (Exception ex2)
+ {
+ Logger.Write(TraceEventType.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);
+ }
}
});
-
+
Task.Run(() =>
{
try
@@ -371,7 +395,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// if the task didn't complete then call the timeout callback
if (queueItem.TimeoutOperation != null)
- {
+ {
queueItem.Result = queueItem.TimeoutOperation(bindingContext);
}
@@ -469,5 +493,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
}
}
+
+ private bool IsExceptionOfType(Exception ex, Type t)
+ {
+ return ex.GetType() == t || (ex.InnerException != null && ex.InnerException.GetType() == t);
+ }
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs
index aac80371..29cdf503 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/ConnectedBindingQueue.cs
@@ -88,9 +88,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// Generate a unique key based on the ConnectionInfo object
///
///
- private string GetConnectionContextKey(ConnectionInfo connInfo)
- {
- ConnectionDetails details = connInfo.ConnectionDetails;
+ internal static string GetConnectionContextKey(ConnectionDetails details)
+ {
string key = string.Format("{0}_{1}_{2}_{3}",
details.ServerName ?? "NULL",
details.DatabaseName ?? "NULL",
@@ -108,7 +107,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
key += "_" + details.GroupId;
}
- return key;
+ return Uri.EscapeUriString(key);
}
///
@@ -158,7 +157,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
public void RemoveBindigContext(ConnectionInfo connInfo)
{
- string connectionKey = GetConnectionContextKey(connInfo);
+ string connectionKey = GetConnectionContextKey(connInfo.ConnectionDetails);
if (BindingContextExists(connectionKey))
{
RemoveBindingContext(connectionKey);
@@ -178,7 +177,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
// lookup the current binding context
- string connectionKey = GetConnectionContextKey(connInfo);
+ string connectionKey = GetConnectionContextKey(connInfo.ConnectionDetails);
if (BindingContextExists(connectionKey))
{
if (overwrite)
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CloseSessionRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CloseSessionRequest.cs
index d583555f..da7a64eb 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CloseSessionRequest.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Contracts/CloseSessionRequest.cs
@@ -39,6 +39,29 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts
public string SessionId { get; set; }
}
+ ///
+ /// Information returned when a session is disconnected.
+ /// Contains success information and a
+ ///
+ public class SessionDisconnectedParameters
+ {
+ ///
+ /// Boolean indicating if the connection was successful
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// Unique ID to use when sending any requests for objects in the
+ /// tree under the node
+ ///
+ public string SessionId { get; set; }
+
+ ///
+ /// Error message returned from the engine for a object explorer session failure reason, if any.
+ ///
+ public string ErrorMessage { get; set; }
+ }
+
///
/// Establishes an Object Explorer tree session for a specific connection.
/// This will create a connection to a specific server or database, register
@@ -50,4 +73,14 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts
RequestType Type =
RequestType.Create("objectexplorer/closesession");
}
+
+ ///
+ /// Session disconnected notification
+ ///
+ public class SessionDisconnectedNotification
+ {
+ public static readonly
+ EventType Type =
+ EventType.Create("objectexplorer/sessiondisconnected");
+ }
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs
index 0bdc18fd..97f5af57 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs
@@ -323,10 +323,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes
SmoQueryContext context = this.GetContextAs();
bool includeSystemObjects = context != null && context.Database != null ? DatabaseUtils.IsSystemDatabaseConnection(context.Database.Name) : true;
-
if (children.IsPopulating || context == null)
- return;
+ {
+ return;
+ }
children.Clear();
BeginChildrenInit();
diff --git a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs
index c8730ea0..36776550 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/ObjectExplorer/ObjectExplorerService.cs
@@ -8,10 +8,12 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Composition;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.Hosting;
using Microsoft.SqlTools.Hosting.Protocol;
@@ -25,8 +27,6 @@ using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.Utility;
-using Microsoft.SqlServer.Management.Common;
-using System.Diagnostics;
namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
{
@@ -73,7 +73,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
{
this.bindingQueue = value;
}
- }
+ }
///
/// Internal for testing only
@@ -132,6 +132,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
{
Logger.Write(TraceEventType.Verbose, "ObjectExplorer service initialized");
this.serviceHost = serviceHost;
+
+ this.ConnectedBindingQueue.OnUnhandledException += OnUnhandledException;
+
// Register handlers for requests
serviceHost.SetRequestHandler(CreateSessionRequest.Type, HandleCreateSessionRequest);
serviceHost.SetRequestHandler(ExpandRequest.Type, HandleExpandRequest);
@@ -511,8 +514,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
await SendSessionFailedNotification(uri, ex.Message);
return null;
}
- }
-
+ }
private async Task Connect(ConnectParams connectParams, string uri)
{
@@ -552,6 +554,18 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
await serviceHost.SendEvent(CreateSessionCompleteNotification.Type, result);
}
+ internal async Task SendSessionDisconnectedNotification(string uri, bool success, string errorMessage)
+ {
+ Logger.Write(TraceEventType.Information, $"OE session disconnected: {errorMessage}");
+ SessionDisconnectedParameters result = new SessionDisconnectedParameters()
+ {
+ Success = success,
+ ErrorMessage = errorMessage,
+ SessionId = uri
+ };
+ await serviceHost.SendEvent(SessionDisconnectedNotification.Type, result);
+ }
+
private void RunExpandTask(ObjectExplorerSession session, ExpandParams expandParams, bool forceRefresh = false)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
@@ -627,24 +641,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
/// Internal for testing purposes only
internal static string GenerateUri(ConnectionDetails details)
{
- Validate.IsNotNull("details", details);
- string uri = string.Format(CultureInfo.InvariantCulture, "{0}{1}", uriPrefix, Uri.EscapeUriString(details.ServerName));
- uri = AppendIfExists(uri, "databaseName", details.DatabaseName);
- uri = AppendIfExists(uri, "user", details.UserName);
- uri = AppendIfExists(uri, "groupId", details.GroupId);
- uri = AppendIfExists(uri, "displayName", details.DatabaseDisplayName);
- return uri;
- }
+ return ConnectedBindingQueue.GetConnectionContextKey(details);
+ }
- private static string AppendIfExists(string uri, string propertyName, string propertyValue)
- {
- if (!string.IsNullOrEmpty(propertyValue))
- {
- uri += string.Format(CultureInfo.InvariantCulture, ";{0}={1}", propertyName, Uri.EscapeUriString(propertyValue));
- }
- return uri;
- }
-
public IEnumerable GetApplicableChildFactories(TreeNode item)
{
if (ApplicableNodeChildFactories != null)
@@ -742,10 +741,37 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer
{
if (bindingQueue != null)
{
+ bindingQueue.OnUnhandledException -= OnUnhandledException;
bindingQueue.Dispose();
+ }
+ }
+
+ private async void OnUnhandledException(string queueKey, Exception ex)
+ {
+ string sessionUri = LookupUriFromQueueKey(queueKey);
+ if (!string.IsNullOrWhiteSpace(sessionUri))
+ {
+ await SendSessionDisconnectedNotification(uri: sessionUri, success: false, errorMessage: ex.ToString());
}
}
+ private string LookupUriFromQueueKey(string queueKey)
+ {
+ foreach (var session in this.sessionMap.Values)
+ {
+ var connInfo = session.ConnectionInfo;
+ if (connInfo != null)
+ {
+ string currentKey = ConnectedBindingQueue.GetConnectionContextKey(connInfo.ConnectionDetails);
+ if (queueKey == currentKey)
+ {
+ return session.Uri;
+ }
+ }
+ }
+ return string.Empty;
+ }
+
internal class ObjectExplorerSession
{
private ConnectionService connectionService;