private void DisconnectExistingConnectionIfNeeded(ConnectParams connectionParams, ConnectionInfo connectionInfo, bool disconnectAll) { // Resolve if it is an existing connection // Disconnect active connection if the URI is already connected for this connection type DbConnection existingConnection; if (connectionInfo.TryGetConnection(connectionParams.Type, out existingConnection)) { var disconnectParams = new DisconnectParams() { OwnerUri = connectionParams.OwnerUri, Type = disconnectAll ? null : connectionParams.Type }; Disconnect(disconnectParams); } }
/// <summary> /// Handle disconnect requests /// </summary> protected async Task HandleDisconnectRequest( DisconnectParams disconnectParams, RequestContext <bool> requestContext) { Logger.Write(LogLevel.Verbose, "HandleDisconnectRequest"); try { bool result = Instance.Disconnect(disconnectParams); await requestContext.SendResult(result); } catch (Exception ex) { await requestContext.SendError(ex.ToString()); } }
public async void ClosingQueryConnectionShouldLeaveDefaultConnectionOpen() { // Setup the connect and disconnect params var connectParamsDefault = new ConnectParams() { OwnerUri = "connectParamsSame", Connection = TestObjects.GetTestConnectionDetails(), Type = ConnectionType.Default }; var connectParamsQuery = new ConnectParams() { OwnerUri = connectParamsDefault.OwnerUri, Connection = TestObjects.GetTestConnectionDetails(), Type = ConnectionType.Query }; var disconnectParamsQuery = new DisconnectParams() { OwnerUri = connectParamsDefault.OwnerUri, Type = connectParamsQuery.Type }; // If I connect a Default and a Query connection var service = TestObjects.GetTestConnectionService(); await service.Connect(connectParamsDefault); await service.Connect(connectParamsQuery); ConnectionInfo connectionInfo = service.OwnerToConnectionMap[connectParamsDefault.OwnerUri]; // There should be 2 connections in the map Assert.Equal(2, connectionInfo.CountConnections); // If I Disconnect only the Query connection, there should be 1 connection in the map service.Disconnect(disconnectParamsQuery); Assert.Equal(1, connectionInfo.CountConnections); // If I reconnect, there should be 2 again await service.Connect(connectParamsQuery); Assert.Equal(2, connectionInfo.CountConnections); }
/// <summary> /// Close a connection with the specified connection details. /// </summary> public bool Disconnect(DisconnectParams disconnectParams) { // Validate parameters if (disconnectParams == null || string.IsNullOrEmpty(disconnectParams.OwnerUri)) { return(false); } // Cancel if we are in the middle of connecting if (CancelConnect(new CancelConnectParams() { OwnerUri = disconnectParams.OwnerUri })) { return(false); } // Lookup the connection owned by the URI ConnectionInfo info; if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info)) { return(false); } // Close the connection info.SqlConnection.Close(); // Remove URI mapping ownerToConnectionMap.Remove(disconnectParams.OwnerUri); // Invoke callback notifications foreach (var activity in this.onDisconnectActivities) { activity(info.ConnectionDetails, disconnectParams.OwnerUri); } // Success return(true); }
/// <summary> /// Close a connection with the specified connection details. /// </summary> public bool Disconnect(DisconnectParams disconnectParams) { // Validate parameters if (disconnectParams == null || string.IsNullOrEmpty(disconnectParams.OwnerUri)) { return(false); } // Cancel if we are in the middle of connecting if (CancelConnect(new CancelConnectParams() { OwnerUri = disconnectParams.OwnerUri })) { return(false); } // Lookup the connection owned by the URI ConnectionInfo info; if (!ownerToConnectionMap.TryGetValue(disconnectParams.OwnerUri, out info)) { return(false); } if (ServiceHost != null) { try { // Send a telemetry notification for intellisense performance metrics ServiceHost.SendEvent(TelemetryNotification.Type, new TelemetryParams() { Params = new TelemetryProperties { Properties = new Dictionary <string, string> { { "IsAzure", info.IsAzure ? "1" : "0" } }, EventName = TelemetryEventNames.IntellisenseQuantile, Measures = info.IntellisenseMetrics.Quantile } }); } catch (Exception ex) { Logger.Write(LogLevel.Verbose, "Could not send Connection telemetry event " + ex.ToString()); } } // Close the connection info.SqlConnection.Close(); // Remove URI mapping ownerToConnectionMap.Remove(disconnectParams.OwnerUri); // Invoke callback notifications foreach (var activity in this.onDisconnectActivities) { activity(info.ConnectionDetails, disconnectParams.OwnerUri); } // Success return(true); }
/// <summary> /// Open a connection with the specified connection details /// </summary> /// <param name="connectionParams"></param> public async Task <ConnectionCompleteParams> Connect(ConnectParams connectionParams) { // Validate parameters string paramValidationErrorMessage; if (connectionParams == null) { return(new ConnectionCompleteParams { Messages = SR.ConnectionServiceConnectErrorNullParams }); } if (!connectionParams.IsValid(out paramValidationErrorMessage)) { return(new ConnectionCompleteParams { OwnerUri = connectionParams.OwnerUri, Messages = paramValidationErrorMessage }); } // Resolve if it is an existing connection // Disconnect active connection if the URI is already connected ConnectionInfo connectionInfo; if (ownerToConnectionMap.TryGetValue(connectionParams.OwnerUri, out connectionInfo)) { var disconnectParams = new DisconnectParams() { OwnerUri = connectionParams.OwnerUri }; Disconnect(disconnectParams); } connectionInfo = new ConnectionInfo(ConnectionFactory, connectionParams.OwnerUri, connectionParams.Connection); // try to connect var response = new ConnectionCompleteParams { OwnerUri = connectionParams.OwnerUri }; CancellationTokenSource source = null; try { // build the connection string from the input parameters string connectionString = BuildConnectionString(connectionInfo.ConnectionDetails); // create a sql connection instance connectionInfo.SqlConnection = connectionInfo.Factory.CreateSqlConnection(connectionString); // Add a cancellation token source so that the connection OpenAsync() can be cancelled using (source = new CancellationTokenSource()) { // Locking here to perform two operations as one atomic operation lock (cancellationTokenSourceLock) { // If the URI is currently connecting from a different request, cancel it before we try to connect CancellationTokenSource currentSource; if (ownerToCancellationTokenSourceMap.TryGetValue(connectionParams.OwnerUri, out currentSource)) { currentSource.Cancel(); } ownerToCancellationTokenSourceMap[connectionParams.OwnerUri] = source; } // Create a task to handle cancellation requests var cancellationTask = Task.Run(() => { source.Token.WaitHandle.WaitOne(); try { source.Token.ThrowIfCancellationRequested(); } catch (ObjectDisposedException) { // Ignore } }); var openTask = Task.Run(async() => { await connectionInfo.SqlConnection.OpenAsync(source.Token); }); // Open the connection await Task.WhenAny(openTask, cancellationTask).Unwrap(); source.Cancel(); } } catch (SqlException ex) { response.ErrorNumber = ex.Number; response.ErrorMessage = ex.Message; response.Messages = ex.ToString(); return(response); } catch (OperationCanceledException) { // OpenAsync was cancelled response.Messages = SR.ConnectionServiceConnectionCanceled; return(response); } catch (Exception ex) { response.ErrorMessage = ex.Message; response.Messages = ex.ToString(); return(response); } finally { // Remove our cancellation token from the map since we're no longer connecting // Using a lock here to perform two operations as one atomic operation lock (cancellationTokenSourceLock) { // Only remove the token from the map if it is the same one created by this request CancellationTokenSource sourceValue; if (ownerToCancellationTokenSourceMap.TryGetValue(connectionParams.OwnerUri, out sourceValue) && sourceValue == source) { ownerToCancellationTokenSourceMap.TryRemove(connectionParams.OwnerUri, out sourceValue); } } } ownerToConnectionMap[connectionParams.OwnerUri] = connectionInfo; // Update with the actual database name in connectionInfo and result // Doing this here as we know the connection is open - expect to do this only on connecting connectionInfo.ConnectionDetails.DatabaseName = connectionInfo.SqlConnection.Database; response.ConnectionSummary = new ConnectionSummary { ServerName = connectionInfo.ConnectionDetails.ServerName, DatabaseName = connectionInfo.ConnectionDetails.DatabaseName, UserName = connectionInfo.ConnectionDetails.UserName, }; // invoke callback notifications InvokeOnConnectionActivities(connectionInfo); // try to get information about the connected SQL Server instance try { var reliableConnection = connectionInfo.SqlConnection as ReliableSqlConnection; DbConnection connection = reliableConnection != null?reliableConnection.GetUnderlyingConnection() : connectionInfo.SqlConnection; ReliableConnectionHelper.ServerInfo serverInfo = ReliableConnectionHelper.GetServerVersion(connection); response.ServerInfo = new ServerInfo { ServerMajorVersion = serverInfo.ServerMajorVersion, ServerMinorVersion = serverInfo.ServerMinorVersion, ServerReleaseVersion = serverInfo.ServerReleaseVersion, EngineEditionId = serverInfo.EngineEditionId, ServerVersion = serverInfo.ServerVersion, ServerLevel = serverInfo.ServerLevel, ServerEdition = serverInfo.ServerEdition, IsCloud = serverInfo.IsCloud, AzureVersion = serverInfo.AzureVersion, OsVersion = serverInfo.OsVersion }; connectionInfo.IsAzure = serverInfo.IsCloud; } catch (Exception ex) { response.Messages = ex.ToString(); } // return the connection result response.ConnectionId = connectionInfo.ConnectionId.ToString(); return(response); }
public async void DbConnectionDoesntLeakUponDisconnect() { // If we connect with a single URI and 2 connection types var connectParamsDefault = new ConnectParams() { OwnerUri = "connectParams", Connection = TestObjects.GetTestConnectionDetails(), Type = ConnectionType.Default }; var connectParamsQuery = new ConnectParams() { OwnerUri = "connectParams", Connection = TestObjects.GetTestConnectionDetails(), Type = ConnectionType.Query }; var disconnectParams = new DisconnectParams() { OwnerUri = connectParamsDefault.OwnerUri }; var service = TestObjects.GetTestConnectionService(); await service.Connect(connectParamsDefault); await service.Connect(connectParamsQuery); // We should have one ConnectionInfo and 2 DbConnections ConnectionInfo connectionInfo = service.OwnerToConnectionMap[connectParamsDefault.OwnerUri]; Assert.Equal(2, connectionInfo.CountConnections); Assert.Equal(1, service.OwnerToConnectionMap.Count); // If we record when the Default connecton calls Close() bool defaultDisconnectCalled = false; var mockDefaultConnection = new Mock <DbConnection> { CallBase = true }; mockDefaultConnection.Setup(x => x.Close()) .Callback(() => { defaultDisconnectCalled = true; }); connectionInfo.ConnectionTypeToConnectionMap[ConnectionType.Default] = mockDefaultConnection.Object; // And when the Query connecton calls Close() bool queryDisconnectCalled = false; var mockQueryConnection = new Mock <DbConnection> { CallBase = true }; mockQueryConnection.Setup(x => x.Close()) .Callback(() => { queryDisconnectCalled = true; }); connectionInfo.ConnectionTypeToConnectionMap[ConnectionType.Query] = mockQueryConnection.Object; // If we disconnect all open connections with the same URI as used above service.Disconnect(disconnectParams); // Close() should have gotten called for both DbConnections Assert.True(defaultDisconnectCalled); Assert.True(queryDisconnectCalled); // And the maps that hold connection data should be empty Assert.Equal(0, connectionInfo.CountConnections); Assert.Equal(0, service.OwnerToConnectionMap.Count); }