/// <summary> /// Blocks the calling thread until the queue of pending responses is fully exhausted (all responses received /// by the server). /// </summary> /// <returns>A reference to the current connection instance for action chaining.</returns> public override VoltConnection Drain() { // Synchronize access - this will take another lock if the call originates from "Close", but it actually // won't: the lock's owning thread will fly through since it owns the lock - everybody else will be stuck. lock (this.SyncRoot) { // Validate connection status. if (!((this.Status == ConnectionStatus.Connected) || (this.Status == ConnectionStatus.Closing))) { throw new InvalidOperationException(string.Format(Resources.InvalidConnectionStatus, this.Status)); } // Trace as needed. if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.DrainingStarted , Resources.TraceClusterConnectionDrainingStarted ); } // Cache current status to resore later. ConnectionStatus previousStatus = this.Status; //Change status and perform draining. this.Status = ConnectionStatus.Draining; // In concept, this should really be parallelized but who really cares? The cluster will refuse all // connections, so that by not submitting more work we ARE draining all the children anyways. // Thus the wait will still be = Max-Drain-Duration-for-slowest-host. foreach (VoltNodeConnection connection in this.ConnectionPool) { // Sub-connection might have died, in which case there won't be anything to do, but we need to make // sure we won't die on the exception it will raise! try { connection.Drain(); } catch { } } // Restore status. this.Status = previousStatus; // Trace as needed. if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.DrainingCompleted , Resources.TraceClusterConnectionDrainingCompleted ); } } return(this); }
/// <summary> /// Blocks the calling thread until the queue of pending responses is fully exhausted (all responses received /// by the server). /// </summary> /// <returns>A reference to the current connection instance for action chaining.</returns> public override VoltConnection Drain() { // Synchronize access - this will take another lock if the call originates from "Close", but it actually // won't: the lock's owning thread will fly through since it owns the lock - everybody else will be stuck. lock (this.SyncRoot) { // Validate connection status. if (!((this.Status == ConnectionStatus.Connected) || (this.Status == ConnectionStatus.Closing))) { throw new InvalidOperationException(string.Format(Resources.InvalidConnectionStatus, this.Status)); } // Trace as needed. if (this.Settings_TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.DrainingStarted , Resources.TraceConnectionDrainingStarted , this.ServerHostId , this.ConnectionId ); } // Cache current status to resore later. ConnectionStatus previousStatus = this.Status; //Change status and perform draining. this.Status = ConnectionStatus.Draining; while (this.ExecutionCache.Size > 0) { Thread.Sleep(100); } // Restore status. this.Status = previousStatus; // Trace as needed. if (this.Settings_TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.DrainingCompleted , Resources.TraceConnectionDrainingCompleted , this.ServerHostId , this.ConnectionId ); } } return(this); }
/// <summary> /// Provides a hookup on internal completetion of procedure calls (used by derived classes for performance /// monitoring and tracing. /// </summary> /// <param name="executionId">The execution id of the request that was just completed.</param> /// <param name="procedure">The name of the procedure that was run.</param> /// <param name="executionDuration">The duration of the execution that was just completed.</param> /// <param name="status">The status of the execution request's response.</param> /// <param name="bytesReceived">Size of the response, in bytes, for network I/O tracking.</param> internal void OnExecutionComplete( long executionId , string procedure , int executionDuration , ResponseStatus status , long bytesReceived ) { // TODO: Revise network tracking so that dropped (timeout or cancel) responses are still tracked adequately // Track statistics as needed. TrackStatisticsCloseRequest(executionId, procedure, executionDuration, status, bytesReceived); // Trace as needed. if (this.Settings_TraceEnabled) { VoltTrace.TraceEvent( status != ResponseStatus.Success ? TraceEventType.Error : TraceEventType.Information , status == ResponseStatus.Timedout ? VoltTraceEventType.ExecutionTimedout : status == ResponseStatus.Failed ? VoltTraceEventType.ExecutionFailed : status == ResponseStatus.Aborted ? VoltTraceEventType.ExecutionAborted : VoltTraceEventType.ExecutionCompleted , status == ResponseStatus.Timedout ? Resources.TraceExecutionTimedout : status == ResponseStatus.Failed ? Resources.TraceExecutionFailed : status == ResponseStatus.Aborted ? Resources.TraceExecutionAborted : Resources.TraceExecutionCompleted , this.ServerHostId , this.ConnectionId , executionId , procedure , executionDuration ); } // Confirm the item as removed in the execution cache. this.ExecutionCache.EndRemoveItem(); }
/// <summary> /// Closes the connection, optionally waiting for all pending responses to have been received. /// </summary> /// <param name="drain">True/false: whether to wait for all responses to be processed before terminating /// the connection.</param> /// <returns>A reference to the current connection instance for action chaining.</returns> public override VoltConnection Close(bool drain) { // Synchronize access. lock (this.SyncRoot) { // Validate connection status. if (this.Status != ConnectionStatus.Connected) { throw new InvalidOperationException(string.Format(Resources.InvalidConnectionStatus, this.Status)); } // Trace as needed. if (this.Settings_TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionClosing , Resources.TraceConnectionClosing , this.ServerHostId , this.ConnectionId ); } // Set status this.Status = ConnectionStatus.Closing; // Drain first if requested. if (drain) { this.Drain(); } // Terminate the connection (trace event for final closure will be posted there). this.Terminate(); } return(this); }
/// <summary> /// Closes the connection, optionally waiting for all pending responses to have been received. /// </summary> /// <param name="drain">True/false: whether to wait for all responses to be processed before terminating the /// connection.</param> /// <returns>A reference to the current connection instance for action chaining.</returns> public override VoltConnection Close(bool drain) { // Synchronize access. lock (this.SyncRoot) { try { // Validate connection status. if (this.Status != ConnectionStatus.Connected) { throw new InvalidOperationException( string.Format( Resources.InvalidConnectionStatus , this.Status ) ); } // Trace as needed. if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionClosing , Resources.TraceClusterConnectionClosing ); } // Set status. this.Status = ConnectionStatus.Closing; // Drain first if requested (we do not call the child's CLose(drain) method because we want to make // sure tracing at the top level is consistent). if (drain) { this.Drain(); } // Close connections foreach (VoltNodeConnection connection in this.ConnectionPool) { // Sub-connection might have died, in which case there won't be anything to do, but we need to // make sure we won't die on the exception it will raise! try { connection.Close(false); } catch { } } // Stop the callback executor's threads. This will also drain callback execution, ensuring every // triggered callback actually gets executed before the executor is shut down. this.CallbackExecutor.Stop(); // Trace as needed. if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionClosed , Resources.TraceClusterConnectionClosed ); } } finally { // Mark connection as closed this.Status = ConnectionStatus.Closed; } } return(this); }
/// <summary> /// Open the cluster connections: the provided settings are analyzed to deploy a full list of IP Endpoints /// (IP:port) of nodes to which we should connect. Child connections are then opened in parallel to all those /// endpoints. Depending on your settings and the cluster configuration, a failure can mean the "Open" method /// will fail (and throw details about the errors encountered), or move forth (all connections opened, of /// course; but also in cases where the configuration indicates that a partial connection is valid, the /// connection will be allowed to stay available). /// </summary> /// <returns>A reference to the current connection instance for action chaining.</returns> public override VoltConnection Open() { lock (this.SyncRoot) { // Validate connection status. if (this.Status != ConnectionStatus.Closed) { throw new InvalidOperationException(string.Format(Resources.InvalidConnectionStatus, this.Status)); } // Set status to "connecting". this.Status = ConnectionStatus.Connecting; try { // Deploy the connection settings into individual IPEndPoint connections. ConnectionSettings[] clusterSettings = this.Settings.ClusterConnectionSettings; // Create delegate for invokation. OpenChildConnectionDelegate handler = this.OpenChildConnection; // Empty the connection list if this is happening after a close event. this.ConnectionPool.Clear(); this.ConnectionCount = 0; // Reset connection monitoring flags. this.ConnectionExceptionList = new List <Exception>(); this.ConnectionExceptionCount = 0; // Prepare async operation - we open connections in parallel (as much as the system will allow, // that is by blocks of 64 maximum - the size limit for a WaitHandle[] that WaitAll will accept). for (int batchStart = 0; batchStart < clusterSettings.Length; batchStart += 64) { int batchLength = Math.Min(clusterSettings.Length - batchStart, 64); // Create a block of wait handles. WaitHandle[] waitHandles = new WaitHandle[batchLength]; // Kick off connection.Open requests - finalization will be processed in parallel as well. // We will check the results after al threads have returned or a timeout. // Now technically, since we're doing this in batches of 64, if we have 65 connections, it // means we will wait for the first 64 connections to be open (or fail to open) before trying // out for the 65th. Not ideal, but probably not a case to seriously worry about. for (int sourceIndex = batchStart; sourceIndex < batchLength + batchStart; sourceIndex++) { IAsyncResult ar = handler.BeginInvoke(clusterSettings[sourceIndex], null, handler); waitHandles[sourceIndex - batchStart] = ar.AsyncWaitHandle; } // Now wait for all connections to complete before proceeding to check the results. if (!WaitHandle.WaitAll( waitHandles , this.Settings.ConnectionTimeout == Timeout.Infinite ? Timeout.Infinite : this.Settings.ConnectionTimeout * batchLength) ) { // At least one of the connections timeout. If we're in "ConnectToAllOrNone" mode, trigger // termination. if (this.Settings.ConnectToAllOrNone) { lock ((this.ConnectionPool as IList).SyncRoot) throw new VoltClusterConnectionException( Resources.ClusterConnectionTimeout , string.Join("\r\n" , clusterSettings.Select(r => string.Format( Resources.ClusterConnectionSummaryRow , r.DefaultIPEndPoint , this.ConnectionPool .Exists(c => c.IPEndPoint.Equals(r.DefaultIPEndPoint)) ) ).ToArray() ) ); } } // Check the results. if (this.ConnectionExceptionCount > 0) { // Aggregate all the exceptions to feed to the user. lock ((this.ConnectionExceptionList as IList).SyncRoot) throw new VoltClusterConnectionException( Resources.ClusterConnectionFailure , string.Join("\r\n\r\n" , this.ConnectionExceptionList.Select(x => x.ToString()).ToArray() ) ); } } // If we get here, all connections were successfully open (or in the case we're not in "All or // nothing", some did - or did they? Make sure we have at least one! if (this.ConnectionPool.Count < 1) { throw new VoltClusterConnectionException(Resources.ClusterConnectionFailureNoSingleHost , string.Join("\r\n\r\n", this.ConnectionExceptionList.Select(x => x.ToString()).ToArray())); } // Record connection count for multiplexing. this.ConnectionCount = this.ConnectionPool.Count; // Initialize map of live connections - at the beginning, obviously, they are all alive. // This array contains the list of Indexes of the live connections. For instance, if connection // #2 where to die on a cluster of 3 connections , the array would be modified to: [1,3]. // Multiplexing will then load balance between those two, while the dead connection is left in the // pool for possible recovery and statistical history support. this.LiveConnectionIndexList = new int[this.ConnectionCount]; for (int i = 0; i < this.ConnectionCount; i++) { this.LiveConnectionIndexList[i] = i; } // Looking good... Set status to "connected". this.Status = ConnectionStatus.Connected; // Trace as needed. if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionOpened , Resources.TraceClusterConnectionOpened , this.LeaderIPEndPoint , this.ClusterStartTimeStamp , this.BuildString , string.Join("\r\n" , clusterSettings.Select(r => string.Format(Resources.ClusterConnectionSummaryRow , r.DefaultIPEndPoint , this.ConnectionPool .Exists(c => c.IPEndPoint.Equals(r.DefaultIPEndPoint)) ) ).ToArray() ) ); } } catch (Exception x) { try { // In case of failure, terminate everything immediately (will correct status) try { // No waiting, no nothing: it's over - Terminate sub-connections (Terminate swallows any // exception, so this is safe without try/catch). foreach (VoltNodeConnection connection in this.ConnectionPool) { connection.Terminate(); } this.ConnectionPool.Clear(); // Trace as needed if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionClosed , Resources.TraceClusterConnectionClosed ); } } finally { this.ConnectionPool.Clear(); this.Status = ConnectionStatus.Closed; } } catch { } // Re-throw exception, wrapping if needed. if (x is VoltException) { throw new VoltConnectionException(Resources.ClusterConnectionUnknownFailure, x); } else { throw x; } } } return(this); }
/// <summary> /// Open the connection. /// </summary> /// <returns>A reference to the current connection instance for action chaining.</returns> public override VoltConnection Open() { // Synchronize access. lock (this.SyncRoot) { // Validate connection status. if (this.Status != ConnectionStatus.Closed) { throw new InvalidOperationException(string.Format(Resources.InvalidConnectionStatus, this.Status)); } // Set status to "connecting". this.Status = ConnectionStatus.Connecting; // Connect to the default endpoint (there should only be one anyways if this is a managed pool // connection, otherwise, you're on your own - this is a connection, not a Pool!). IPEndPoint endPoint = this.Settings.DefaultIPEndPoint; try { // Create new socket stream and wrap a core protocol stream manager around it. this.BaseStream = new VoltProtocolStream(endPoint, this.Settings.ConnectionTimeout); // Now send login message. using (Serializer serializer = new Serializer()) { var msg = serializer .Write(this.Settings.ServiceType.ToString().ToLowerInvariant()) .Write(this.Settings.UserID) .WriteRaw(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(this.Settings.Password))) .GetBytes(); this.BaseStream.WriteMessage(msg.Array, msg.Offset, msg.Count); } // Receive and process login response message. var deserializer = new Deserializer(this.BaseStream.ReadMessage()); LoginResponseStatus status = (LoginResponseStatus)deserializer.ReadSByte(); if (status != LoginResponseStatus.Connected) { // Re-package server response in an appropriate exception. switch (status) { case LoginResponseStatus.ConnectionHandshakeTimeout: throw new VoltConnectionException( Resources.LRS_ConnectionHandshakeTimeout , endPoint , status ); case LoginResponseStatus.CorruptedHandshake: throw new VoltConnectionException( Resources.LRS_CorruptedHandshake , endPoint , status ); case LoginResponseStatus.InvalidCredentials: throw new VoltPermissionException( Resources.LRS_InvalidCredentials , endPoint , status ); case LoginResponseStatus.ServerTooBusy: throw new VoltConnectionException( Resources.LRS_ServerTooBusy , endPoint , status ); default: throw new VoltConnectionException(Resources.LRS_Unknown, endPoint, status); } } // Parse the rest of the response the get core cluster information. try { this.ServerHostId = deserializer.ReadInt32(); this.ConnectionId = deserializer.ReadInt64(); this.ClusterStartTimeStamp = deserializer.ReadDateTimeFromMilliseconds(); this.LeaderIPEndPoint = new IPEndPoint( new IPAddress(deserializer.ReadRaw(4)) , endPoint.Port ); this.BuildString = deserializer.ReadString(); } catch (Exception x) { throw new VoltConnectionException(Resources.LR_FailedParsingResponse, x, endPoint); } // Now that we are successfully connected, set the socket timeout to infinite (we are constantly // listening for new messages). this.BaseStream.ResetTimeout(Timeout.Infinite); // Create background threads. this.BackgroundNetworkThread = new Thread(this.BackgroundNetworkThreadRun) { IsBackground = true, Priority = ThreadPriority.AboveNormal }; this.BackgroundTimeoutThread = new Thread(this.BackgroundTimeoutThreadRun) { IsBackground = true }; // Starting background processing threads. this.BackgroundNetworkThread.Start(); this.BackgroundTimeoutThread.Start(); // Start Callback executor this.CallbackExecutor.Start(); // Ensure terminal exception is reset this.TerminalException = null; // Connection is now ready. this.Status = ConnectionStatus.Connected; // Initialize statistics as needed. if (this.Settings.StatisticsEnabled) { this.Stats = new Dictionary <string, Statistics>(StringComparer.OrdinalIgnoreCase); // For lifetime statistics, keep track of previous connection cycles, if any. if (this.LifetimeStats != null) { if (this.PastLifetimeStats != null) { this.PastLifetimeStats.SummarizeWith(this.LifetimeStats); } else { this.PastLifetimeStats = this.LifetimeStats; } } this.LifetimeStats = new Statistics(); } // Trace as needed if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionOpened , Resources.TraceConnectionOpened , this.ServerHostId , this.ConnectionId , endPoint , this.LeaderIPEndPoint , this.ClusterStartTimeStamp , this.BuildString ); } } catch (Exception x) { try { // In case of failure, terminate everything immediately (will correct status). this.Terminate(); } catch { } // Re-throw exception, wrapping if needed. if (x is VoltException) { throw new VoltConnectionException(Resources.ConnectionFailure, x, endPoint); } else { throw x; } } } return(this); }
/// <summary> /// Terminates the connection and closes all resources. /// </summary> internal void Terminate() { // Mark connection as closed this.Status = ConnectionStatus.Closed; // Stop background threads - swallow any undue exception... try { // Kill the threads, the stream. try { if (this.OwnsCallbackExecutor) { this.CallbackExecutor.Stop(); } this.BackgroundNetworkThread.Abort(); this.BackgroundTimeoutThread.Abort(); this.BaseStream.Close(); } catch { } // Trigger all callbacks on executions we will not be able to fulfill. try { long[] killList = this.ExecutionCache.GetCurrentItems(); while (killList.Length > 0) { foreach (long expiredExecutionId in killList) { // Get the response from the stack Response response = this.ExecutionCache.BeginRemoveItem(expiredExecutionId); // If the response wasn't already dealt with, finalize processing. if (response != null) { // Trigger the callback. The call is non-blocking: the callback is queued for // execution in the ThreadPool. response.OnExecutionAbort(); // Call completion handler for classes that provide additional tracking. this.OnExecutionComplete( expiredExecutionId , response.Procedure , response.ExecutionDuration , response.Status , 0 // Not accurate: the response might come later... ); } } Thread.Sleep(10); killList = this.ExecutionCache.GetCurrentItems(); } } catch { } // Freeze statistics so the elapsed time doesn't report nonsensical figures. if (this.Settings.StatisticsEnabled) { lock ((this.Stats as IDictionary).SyncRoot) foreach (KeyValuePair <string, Statistics> pair in this.Stats) { pair.Value.Freeze(); } this.LifetimeStats.Freeze(); } // Trace as needed. if (this.Settings.TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ConnectionClosed , Resources.TraceConnectionClosed , this.ServerHostId , this.ConnectionId ); } } catch { } }
/// <summary> /// Asynchronously execute a procedure against a VoltDB database, returning immediately to the calling thread. /// The provided callback will be fired upon completion. /// </summary> /// <typeparam name="T">Data type of the expected response result for the call (Table[], Table, /// SingleRowTable[], SingleRowTable, T[][], T[] or T, with T one of the supported value types).</typeparam> /// <param name="callback">The callback method to call upon completion.</param> /// <param name="state">A user-defined state object to be passed to your callback through the Response's /// .AsyncState property</param> /// <param name="timeout">Timeout value (overrides connection settings DefaultCommandTimeout). Use /// Timeout.Infinite or -1 for infinite timeout.</param> /// <param name="procedure">The name of the procedure to call.</param> /// <param name="procedureUtf8">The UTF-8 bytes of the procedure name.</param> /// <param name="parameters">List of parameters to pass to the procedure.</param> /// <returns>The execution handle for the request.</returns> public override AsyncResponse <T> BeginExecute <T>( ExecuteAsyncCallback <T> callback , object state , int timeout , string procedure , byte[] procedureUtf8 , params object[] parameters ) { // Correct default timeout usage. if (timeout == 0) { timeout = this.Settings_DefaultCommandTimeout; } else if (timeout < -1) { throw new ArgumentOutOfRangeException(string.Format(Resources.InvalidTimeoutValue, timeout)); } // Validate connection status. if (this.Status != ConnectionStatus.Connected) { if (this.TerminalException != null) { throw this.TerminalException; } else { throw new InvalidOperationException(string.Format(Resources.InvalidConnectionStatus, this.Status)); } } // Assign execution id. long executionId = Interlocked.Increment(ref this.ExecutionId); // Prepare the response object. AsyncResponse <T> response = new AsyncResponse <T>(this, executionId, timeout, callback, state, procedure, parameters); // Attempt asynchronouse execution - any failure will trigger a synchronous failure. try { // This might fail (invalid parameters / exceeding max string length, for instance) - the equivalent of an // ArgumentException, so we leave it outside of the try/catch block related to protecting ourselves against // connectivity issues. var message = GetProcedureCallMessage(executionId, procedureUtf8, parameters); // Block if we reached queue capacity. while (this.ExecutionCache.Size >= this.Settings_MaxOutstandingTxns) { Thread.Sleep(1); } // The request appears to be properly formatted and ready to ship - push it in the queue. this.ExecutionCache.AddItem(response); // Write out execution request to protocol stream - lock out access for thread safety around the underlying // stream. lock (this.SyncRoot) { try { this.BaseStream.WriteMessage(message.Array, message.Offset, message.Count); } catch (Exception x) { // We will only get here in case of a network/connection failure. // Swallow any exception: the background processing thread will pick up a network failure as well // and we will let it initiate termination and kick out the abort on the request just posted. // We still set the Terminal exception so we report the *first* exception encountered (failing to // write on the stream here, in case the background thread picks things up first, failing to read). this.TerminalException = new VoltExecutionException(Resources.ConnectionClosedDuringAWrite, x); // But, as stated, we do not re-throw: connection termination will ensure that all pending requests // are rejected with an "abort" exception. The next execution call will fail directly on a // "connection closed" error, or be blocked until recovery is complete. } } // Track statistics as needed. TrackStatisticsOpenRequest(procedure, message.Count); // Trace as needed. if (this.Settings_TraceEnabled) { VoltTrace.TraceEvent( TraceEventType.Information , VoltTraceEventType.ExecutionStarted , Resources.TraceExecutionStarted , this.ServerHostId , this.ConnectionId , executionId , procedure ); } } catch (Exception x) { response.OnExecutionRequestFailed(new VoltExecutionException(Resources.RequestExecutionFailure, x)); } // Return execution handle to caller. return(response); }