/// <summary> /// EndOfMessage arrives /// </summary> /// <param name="msgCount">indicating the msg count</param> /// <param name="timeout">indicating the timeout</param> public void EndOfMessage(long msgCount, int batchId, int timeout) { lock (this.lockState) { if (this.state != BrokerClientState.ClientConnected) { BrokerTracing.EtwTrace.LogBrokerClientRejectEOM(this.sharedData.BrokerInfo.SessionId, this.clientId, this.state.ToString()); switch (this.state) { case BrokerClientState.GetResponse: ThrowHelper.ThrowSessionFault(SOAFaultCode.Broker_EOMReject_GetResponse, SR.EOMReject_GetResponse); break; case BrokerClientState.EndRequests: ThrowHelper.ThrowSessionFault(SOAFaultCode.Broker_EOMReject_EndRequests, SR.EOMReject_EndRequests); break; default: ThrowHelper.ThrowSessionFault(SOAFaultCode.Broker_EOMRejected, SR.EOMRejected, this.state.ToString()); break; } } // Stop the timer this.timeoutManager.Stop(); try { this.Flush(msgCount, batchId, timeout, true); } catch (Exception e) { BrokerTracing.TraceError("[BrokerClient] Failed to flush messages: {0}", e); // If EndRequests throws exception from broker queue, the // timer is closed and never reactivated without user action. // Thus, the broker client would leak forever and the broker // process would run away if user does not purge the client // or close the session later. this.timeoutManager.RegisterTimeout(this.sharedData.Config.Monitor.ClientIdleTimeout, this.TimeoutToDisconnected, this.state); throw; } // State will change if flush succeeded // If flush failed, the state won't change BrokerTracing.TraceInfo("[BrokerClient] Client {0}: State: ClientConnected ==> EndOfMessage", this.clientId); this.state = BrokerClientState.EndRequests; this.endOfMessageCalled = true; } }
private void OnAllRequestsProcessed(object state) { lock (this.lockState) { BrokerTracing.EtwTrace.LogBrokerClientAllRequestDone(this.sharedData.BrokerInfo.SessionId, this.clientId); this.state = BrokerClientState.AllRequestDone; } // Instead of invoking AllRequestDone event handler directly, we fist make a copy of it and // then check for null. This ensures that we'll not fire NullReferenceException in case all // AllRequestDone subscribers are removed. EventHandler allRequestDoneCopy = this.AllRequestDone; if (allRequestDoneCopy != null) { allRequestDoneCopy(this, EventArgs.Empty); } bool flag = false; lock (this.connectedInstance) { if (this.connectedInstance.Count != 0) { this.timeoutManager.RegisterTimeout(this.sharedData.Config.Monitor.ClientIdleTimeout, this.TimeoutToDisconnected, this.state); flag = true; } } if (!flag && !this.singletonInstanceConnected) { // Bug 5193: If there was no channel connected to this client while all requests are done, close the client immediately BrokerTracing.EtwTrace.LogBrokerClientDisconnected(this.sharedData.BrokerInfo.SessionId, this.clientId, "all request done and there's no frontend connected"); this.SyncDisconnect(); } else if (!flag && this.singletonInstanceConnected) { // Bug 17241: If there's no connected instance and this.singletonInstanceConnceted // is true (this situation could happen when on azure, since it is the REST // instance connecting, singletonInstanceConnected is always set to true so that // the broker client would not dispose itself as the real client might be still // exist), we need to register timeout so that it could be timed out. Otherwise, // the client might be leaked forever so that the entire broker would runaway. this.timeoutManager.RegisterTimeout(this.sharedData.Config.Monitor.ClientIdleTimeout, this.TimeoutToDisconnected, this.state); } }
/// <summary> /// Sync disconnect the broker client /// </summary> private void SyncDisconnect() { bool flag = false; lock (this.lockState) { BrokerTracing.TraceInfo("[BrokerClient] Client {0}: SyncDisconnect the state is {1} and this.queue.ProcessingRequestsCount/ProcessedRequestsCount/FlushedRequestsCount are {2}/{3}/{4}.", this.clientId, this.state.ToString(), this.queue.ProcessingRequestsCount, this.queue.ProcessedRequestsCount, this.queue.FlushedRequestsCount); if (this.state == BrokerClientState.GetResponse || this.state == BrokerClientState.AllRequestDone || (this.state == BrokerClientState.ClientConnected && ((this.queue.ProcessingRequestsCount == 0 && this.queue.ProcessedRequestsCount >= this.queue.FlushedRequestsCount) || (this.v2ProxyClient && this.observer.AllRequestProcessed())))) // check if the broker client is idle for v2 proxy client, in which case responses are directly sent back without persist in the queue. { flag = true; this.state = BrokerClientState.Disconnected; if (this.timeoutManager != null) { this.timeoutManager.Stop(); } } } if (flag) { BrokerTracing.TraceInfo("[BrokerClient] Client {0}: Disconnect client.", this.clientId); if (this.responsesClient != null) { this.responsesClient.ClientDisconnect(false); } if (this.ClientDisconnected != null) { this.ClientDisconnected(this, EventArgs.Empty); } } else if (!this.sharedData.BrokerInfo.Durable) { //update the flush counters for persisted and committed for interactive session when client disconnected without removal. this.queue.FlushCount(); } }
/// <summary> /// Helper function that maps BrokerClientState to BrokerClientStatus /// </summary> /// <param name="state"></param> internal static BrokerClientStatus MapToBrokerClientStatus(BrokerClientState state) { switch (state) { case BrokerClientState.ClientConnected: return(BrokerClientStatus.Ready); case BrokerClientState.EndRequests: return(BrokerClientStatus.Processing); case BrokerClientState.AllRequestDone: case BrokerClientState.GetResponse: return(BrokerClientStatus.Finished); default: Debug.Fail("[BrokerClient] BrokerClient should not be in other states."); return(default(BrokerClientStatus)); } }
/// <summary> /// Gets broker client status /// </summary> /// <param name="clientId">indicating the client id</param> /// <returns>returns the broker client status</returns> public BrokerClientStatus GetBrokerClientStatus(string clientId) { try { ParamCheckUtility.ThrowIfNull(clientId, "clientId"); ParamCheckUtility.ThrowIfTooLong(clientId.Length, "clientId", Constant.MaxClientIdLength, SR.ClientIdTooLong); ParamCheckUtility.ThrowIfNotMatchRegex(ParamCheckUtility.ClientIdValid, clientId, "clientId", SR.InvalidClientId); BrokerTracing.TraceEvent(System.Diagnostics.TraceEventType.Information, 0, "[BrokerController] Get broker client status for Client {0}", clientId); this.ThrowIfDisposed(); this.CheckAuth(); BrokerClientState state = this.GetClient(clientId).State; return(BrokerClient.MapToBrokerClientStatus(state)); } catch (Exception e) { BrokerTracing.TraceEvent(System.Diagnostics.TraceEventType.Error, 0, "[BrokerController] Get broker client status for Client {1} Failed: {0}", e, clientId); throw TranslateException(e); } }
/// <summary> /// Dispose the broker client /// </summary> /// <param name="disposing">indicating whether it is disposing</param> private void Dispose(bool disposing) { this.disposing = disposing; try { // Set state this.state = BrokerClientState.Disconnected; // Copy all the connected instances out of lock List <object> connectedList = null; lock (this.connectedInstance) { connectedList = new List <object>(this.connectedInstance.Keys); } foreach (object instance in connectedList) { try { BrokerTracing.TraceInfo("[BrokerClient] Try to close the connected instance: {0} ({1})", instance.ToString(), instance.GetHashCode()); if (instance is IDisposable) { // BrokerController ((IDisposable)instance).Dispose(); } else if (instance is IChannel) { Utility.AsyncCloseICommunicationObject((ICommunicationObject)instance); } else { Debug.Fail("[BrokerClient] Connected instance must be IDisposable or IChannel."); } } catch (Exception e) { BrokerTracing.TraceWarning("[BrokerClient] Failed to close the connected instance: {0}", e); } } if (disposing) { if (this.queue != null) { this.queue.OnEvent -= this.Queue_OnEvent; this.queue.OnPutResponsesSuccessEvent -= this.Queue_OnPutResponsesSuccessEvent; this.queue.OnFatalExceptionEvent -= this.Queue_OnFatalExceptionEvent; //for durable session, the queue is closed unless there were flushed requests, dispose if EOM was called. if (this.sharedData.BrokerInfo.Durable) { if (this.queue.FlushedRequestsCount == 0) { BrokerTracing.TraceInfo("[BrokerClient] durable session broker client {0} close the queue.", this.clientId); //if not ever flushed, reduce the count of all requests in the queue this.observer.ReduceUncommittedCounter(this.queue.AllRequestsCount); this.queue.Close(); } else if (this.endOfMessageCalled) { // Only dispose the broker queue if it is a durable session. Non-durable broker queue for // interactive session should be kept/reused and closed when closing the broker domain. // // Note (related bug #14224): logic in BrokerClient.SyncDisconnect() ensures that BrokerClient // instance will not be disposed if EndRequests is called (this.state = BrokerClientState.EndRequests). // So below code snippet could only be reached on broker entry exiting. this.observer.ReduceUncommittedCounter(this.queue.AllRequestsCount - this.queue.FlushedRequestsCount); this.queue.Dispose(); } } else //for interactive session, close the queue if EOM is not called. { if (!this.endOfMessageCalled) { BrokerTracing.TraceInfo("[BrokerClient] interactive session broker client {0} close the queue.", this.clientId); if (!this.v2ProxyClient) { //reduce the count of all unflushed requests in the queue this.observer.ReduceUncommittedCounter(this.queue.AllRequestsCount - this.queue.FlushedRequestsCount); } this.queue.Close(); } } this.queue = null; this.batchMessageIds.Clear(); } } if (this.responsesClient != null) { this.responsesClient.Dispose(); this.responsesClient = null; } if (this.timeoutManager != null) { this.timeoutManager.Dispose(); this.timeoutManager = null; } // do not dispose this.lockForDiscardRequests for this may block the threads in the write queue, which may lead to the hang when broker entry exits. // do not dispose this.batchIdChangedEvent for any wait for the event would hang } catch (Exception e) { BrokerTracing.TraceWarning("[BrokerClient] Exception thrown while disposing: {0}", e); } }
/// <summary> /// Initializes a new instance of the BrokerClient class /// </summary> /// <param name="clientId">indicating the client Id</param> /// <param name="userName">indicating the user name</param> /// <param name="queueFactory">indicating the queue factory</param> /// <param name="observer">indicating the observer</param> /// <param name="stateManager">indicating the state manager</param> /// <param name="monitor">indicating the monitor</param> /// <param name="sharedData">indicating the shared data</param> public BrokerClient(string clientId, string userName, BrokerQueueFactory queueFactory, BrokerObserver observer, BrokerStateManager stateManager, ServiceJobMonitorBase monitor, SharedData sharedData) { bool isNewCreated; // Set the "signletonInstanceConnected" property if // SessionStartInfo.AutoDisposeBrokerClient is set. This property is only possibly to // be set to true after if HTTP transport scheme is specified. And it is by design so. if (sharedData.StartInfo.AutoDisposeBrokerClient.HasValue) { this.singletonInstanceConnected = !sharedData.StartInfo.AutoDisposeBrokerClient.Value; } this.clientId = clientId; this.v2ProxyClient = clientId.StartsWith(FrontEndBase.DefaultClientPrefix); this.sharedData = sharedData; this.observer = observer; this.monitor = monitor; this.stateManager = stateManager; this.stateManager.OnFailed += new BrokerStateManager.SessionFailedEventHandler(this.StateManager_OnFailed); try { this.queue = queueFactory.GetPersistQueueByClient(clientId, userName, out isNewCreated); } catch (BrokerQueueException e) { // Catch the exception about the username not match and translte it to session fault if (e.ErrorCode == (int)BrokerQueueErrorCode.E_BQ_USER_NOT_MATCH) { ThrowHelper.ThrowSessionFault(SOAFaultCode.AccessDenied_BrokerQueue, SR.AccessDenied_BrokerQueue, clientId, userName); } else { throw; } } this.queue.OnEvent += new BrokerQueueEventDelegate(this.Queue_OnEvent); this.queue.OnPutResponsesSuccessEvent += new EventHandler <PutResponsesSuccessEventArgs>(this.Queue_OnPutResponsesSuccessEvent); this.queue.OnFatalExceptionEvent += new EventHandler <ExceptionEventArgs>(this.Queue_OnFatalExceptionEvent); this.timeoutManager = new TimeoutManager("BrokerClient " + clientId); BrokerTracing.EtwTrace.LogBrokerClientCreated(this.sharedData.BrokerInfo.SessionId, clientId); if (this.queue.IsAllRequestsProcessed || monitor.ServiceJobState == ServiceJobState.Finished) { // If the queue has processed all the request or the service job is finished, the broker client can only get responses this.state = BrokerClientState.GetResponse; this.endOfMessageCalled = true; BrokerTracing.EtwTrace.LogBrokerClientStateTransition(this.sharedData.BrokerInfo.SessionId, this.clientId, "GetResponse"); this.timeoutManager.RegisterTimeout(this.sharedData.Config.Monitor.ClientIdleTimeout, this.TimeoutToDisconnected, this.state); } else { if (!this.queue.EOMReceived) { // If EndOfMessage is not received, the client is in the ClientConnected state and is ready to accept messages this.state = BrokerClientState.ClientConnected; BrokerTracing.EtwTrace.LogBrokerClientStateTransition(this.sharedData.BrokerInfo.SessionId, this.clientId, "ClientConnected"); this.timeoutManager.RegisterTimeout(this.sharedData.Config.Monitor.ClientIdleTimeout, this.TimeoutToDisconnected, this.state); } else { // If EndOfMessage has been received, the client is in the EndOfMessage state and does not accept any more requests. this.state = BrokerClientState.EndRequests; this.endOfMessageCalled = true; BrokerTracing.EtwTrace.LogBrokerClientStateTransition(this.sharedData.BrokerInfo.SessionId, this.clientId, "EndOfMessage"); } } }