public async Task <AmqpObject> CreateAndOpenAmqpLinkAsync() { TimeoutHelper timeoutHelper = new TimeoutHelper(this.serviceBusConnection.OperationTimeout); AmqpConnection connection = await this.serviceBusConnection.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); // Authenticate over CBS AmqpCbsLink cbsLink = connection.Extensions.Find <AmqpCbsLink>(); Uri address = new Uri(this.serviceBusConnection.Endpoint, this.entityPath); string audience = address.AbsoluteUri; string resource = address.AbsoluteUri; await cbsLink.SendTokenAsync(this.cbsTokenProvider, address, audience, resource, this.requiredClaims, timeoutHelper.RemainingTime()).ConfigureAwait(false); AmqpSession session = null; try { // Create our Session AmqpSessionSettings sessionSettings = new AmqpSessionSettings { Properties = new Fields() }; session = connection.CreateSession(sessionSettings); await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); // Create our Link AmqpObject link = this.OnCreateAmqpLink(connection, this.amqpLinkSettings, session); await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); return(link); } catch (Exception) { session?.Abort(); throw; } }
protected override void SessionClosed(AmqpObject sender, Error error) { this.AmqpSession = null; this._amqpSender = null; Logger.TraceLog("AMQP Session Closed. Error: " + error.ToString()); this.bPendingSend = false; }
private static async Task OpenLinkAsync(AmqpObject link, TimeSpan timeout) { Logging.Enter(link, link.State, timeout, nameof(OpenLinkAsync)); try { var timeoutHelper = new TimeoutHelper(timeout); try { await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); } catch (Exception exception) { Logging.Error(link, exception, nameof(OpenLinkAsync)); if (exception.IsFatal()) { throw; } link.SafeClose(exception); throw; } } finally { Logging.Exit(link, link.State, timeout, nameof(OpenLinkAsync)); } }
protected override async Task OpenLinkAsync(AmqpObject link, IotHubConnectionString doNotUse, string doNotUse2, TimeSpan timeout, CancellationToken token) { if (Logging.IsEnabled) { Logging.Enter(this, timeout, token, $"{nameof(IotHubSingleTokenConnection)}.{nameof(OpenLinkAsync)}"); } token.ThrowIfCancellationRequested(); try { await link.OpenAsync(timeout).ConfigureAwait(false); } catch (Exception exception) { if (exception.IsFatal()) { throw; } link.SafeClose(exception); throw; } finally { if (Logging.IsEnabled) { Logging.Exit(this, timeout, token, $"{nameof(IotHubSingleTokenConnection)}.{nameof(OpenLinkAsync)}"); } } }
/// <summary> /// Performs the actions needed to open an AMQP object, such /// as a session or link for use. /// </summary> /// /// <param name="target">The target AMQP object to open.</param> /// <param name="timeout">The timeout to apply when opening the link.</param> protected virtual async Task OpenAmqpObjectAsync( AmqpObject target, TimeSpan timeout) { try { await target.OpenAsync(timeout).ConfigureAwait(false); } catch { switch (target) { case AmqpLink linkTarget: linkTarget.Session?.SafeClose(); break; case RequestResponseAmqpLink linkTarget: linkTarget.Session?.SafeClose(); break; default: break; } target.SafeClose(); throw; } }
protected ActiveClientLinkObject(AmqpObject amqpLinkObject, Uri endpointUri, string audience, string[] requiredClaims) { this.LinkObject = amqpLinkObject; this.EndpointUri = endpointUri; this.Audience = audience; this.requiredClaims = requiredClaims; }
/// <summary> /// Performs the actions needed to configure and begin tracking the specified AMQP /// link as an active link bound to this scope. /// </summary> /// <param name="entityPath"></param> /// /// <param name="link">The link to begin tracking.</param> /// <param name="authorizationRefreshTimer">The timer used to manage refreshing authorization, if the link requires it.</param> /// /// <remarks> /// This method does operate on the specified <paramref name="link"/> in order to configure it /// for active tracking; no assumptions are made about the open/connected state of the link nor are /// its communication properties modified. /// </remarks> /// protected virtual void BeginTrackingLinkAsActive( string entityPath, AmqpObject link, Timer authorizationRefreshTimer = null) { // Register the link as active and having authorization automatically refreshed, so that it can be // managed with the scope. if (!ActiveLinks.TryAdd(link, authorizationRefreshTimer)) { throw new ServiceBusException(true, entityPath, Resources.CouldNotCreateLink); } // When the link is closed, stop refreshing authorization and remove it from the // set of associated links. var closeHandler = default(EventHandler); closeHandler = (snd, args) => { ActiveLinks.TryRemove(link, out var timer); timer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); timer?.Dispose(); link.Closed -= closeHandler; }; link.Closed += closeHandler; }
public static Exception GetInnerException(this AmqpObject amqpObject) { var connectionError = false; Exception innerException; switch (amqpObject) { case AmqpSession amqpSession: innerException = amqpSession.TerminalException ?? amqpSession.Connection.TerminalException; break; case AmqpLink amqpLink: connectionError = amqpLink.Session.IsClosing(); innerException = amqpLink.TerminalException ?? amqpLink.Session.TerminalException ?? amqpLink.Session.Connection.TerminalException; break; case RequestResponseAmqpLink amqpReqRespLink: innerException = amqpReqRespLink.TerminalException ?? amqpReqRespLink.Session.TerminalException ?? amqpReqRespLink.Session.Connection.TerminalException; break; default: return(null); } return(innerException == null ? null : GetClientException(innerException, null, null, connectionError)); }
void OnLinkClosed(AmqpObject sender, Error error) { lock (this.queue.publishers) { this.queue.publishers.Remove(this.id); } }
protected override async Task OpenLinkAsync(AmqpObject link, IotHubConnectionString connectionString, string audience, TimeSpan timeout, CancellationToken token) { var timeoutHelper = new TimeoutHelper(timeout); token.ThrowIfCancellationRequested(); try { // this is a device-scope connection string. We need to send a CBS token for this specific link before opening it. var iotHubLinkTokenRefresher = new IotHubTokenRefresher(this.FaultTolerantSession.Value, connectionString, audience); if (this.iotHubTokenRefreshers.TryAdd(link, iotHubLinkTokenRefresher)) { link.SafeAddClosed((s, e) => { if (this.iotHubTokenRefreshers.TryRemove(link, out iotHubLinkTokenRefresher)) { iotHubLinkTokenRefresher.Cancel(); } }); // Send Cbs token for new link first // This will throw an exception if the device is not valid or if the token is not valid await iotHubLinkTokenRefresher.SendCbsTokenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); } token.ThrowIfCancellationRequested(); // Open Amqp Link await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); } catch (Exception exception) when(!exception.IsFatal()) { link.SafeClose(exception); throw; } }
/// <summary> /// Creates the timer event handler to support refreshing AMQP link authorization /// on a recurring basis. /// </summary> /// /// <param name="connection">The AMQP connection to which the link being refreshed is bound to.</param> /// <param name="amqpLink">The AMQO link to refresh authorization for.</param> /// <param name="tokenProvider">The <see cref="CbsTokenProvider" /> to use for obtaining access tokens.</param> /// <param name="endpoint">The Event Hubs service endpoint that the AMQP link is communicating with.</param> /// <param name="audience">The audience associated with the authorization. This is likely the <paramref name="endpoint"/> absolute URI.</param> /// <param name="resource">The resource associated with the authorization. This is likely the <paramref name="endpoint"/> absolute URI.</param> /// <param name="requiredClaims">The set of claims required to support the operations of the AMQP link.</param> /// <param name="refreshTimeout">The timeout to apply when requesting authorization refresh.</param> /// <param name="refreshTimerFactory">A function to allow retrieving the <see cref="Timer" /> associated with the link authorization.</param> /// /// <returns>A <see cref="TimerCallback"/> delegate to perform the refresh when a timer is due.</returns> /// protected virtual TimerCallback CreateAuthorizationRefreshHandler(AmqpConnection connection, AmqpObject amqpLink, CbsTokenProvider tokenProvider, Uri endpoint, string audience, string resource, string[] requiredClaims, TimeSpan refreshTimeout, Func <Timer> refreshTimerFactory) { return(async _ => { EventHubsEventSource.Log.AmqpLinkAuthorizationRefreshStart(EventHubName, endpoint.AbsoluteUri); var refreshTimer = refreshTimerFactory(); try { var authExpirationUtc = await RequestAuthorizationUsingCbsAsync(connection, tokenProvider, endpoint, audience, resource, requiredClaims, refreshTimeout).ConfigureAwait(false); // Reset the timer for the next refresh. if (authExpirationUtc >= DateTimeOffset.UtcNow) { refreshTimer.Change(CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan); } EventHubsEventSource.Log.AmqpLinkAuthorizationRefreshComplete(EventHubName, endpoint.AbsoluteUri); } catch (Exception ex) { EventHubsEventSource.Log.AmqpLinkAuthorizationRefreshError(EventHubName, endpoint.AbsoluteUri, ex.Message); refreshTimer.Change(Timeout.Infinite, Timeout.Infinite); } }); }
/// <summary> /// Callback for link close events /// </summary> /// <param name="sender"></param> /// <param name="error"></param> protected virtual void OnLinkClosed(AmqpObject sender, Error error) { if (error != null) { Debug.WriteLine("Link Closed {0} {1}", error.Condition, error.Description); } }
void OnConnectionClosed(AmqpObject sender, Error error) { lock (this.connections) { this.connections.Remove((Connection)sender); } }
public async Task <Tuple <AmqpObject, DateTime> > CreateAndOpenAmqpLinkAsync() { var timeoutHelper = new TimeoutHelper(this.serviceBusConnection.OperationTimeout); MessagingEventSource.Log.AmqpGetOrCreateConnectionStart(); var amqpConnection = await this.serviceBusConnection.ConnectionManager.GetOrCreateAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); MessagingEventSource.Log.AmqpGetOrCreateConnectionStop(this.entityPath, amqpConnection.ToString(), amqpConnection.State.ToString()); // Authenticate over CBS var cbsLink = amqpConnection.Extensions.Find <AmqpCbsLink>(); var resource = this.endpointAddress.AbsoluteUri; MessagingEventSource.Log.AmqpSendAuthenticationTokenStart(this.endpointAddress, resource, resource, this.requiredClaims); var cbsTokenExpiresAtUtc = await cbsLink.SendTokenAsync(this.cbsTokenProvider, this.endpointAddress, resource, resource, this.requiredClaims, timeoutHelper.RemainingTime()).ConfigureAwait(false); MessagingEventSource.Log.AmqpSendAuthenticationTokenStop(); AmqpSession session = null; try { // Create Session var amqpSessionSettings = new AmqpSessionSettings { Properties = new Fields() }; session = amqpConnection.CreateSession(amqpSessionSettings); await session.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); } catch (Exception exception) { MessagingEventSource.Log.AmqpSessionCreationException(this.entityPath, amqpConnection, exception); session?.Abort(); throw AmqpExceptionHelper.GetClientException(exception, null, session.GetInnerException()); } AmqpObject link = null; try { // Create Link link = this.OnCreateAmqpLink(amqpConnection, this.amqpLinkSettings, session); await link.OpenAsync(timeoutHelper.RemainingTime()).ConfigureAwait(false); return(new Tuple <AmqpObject, DateTime>(link, cbsTokenExpiresAtUtc)); } catch (Exception exception) { MessagingEventSource.Log.AmqpLinkCreationException( this.entityPath, session, amqpConnection, exception); session.SafeClose(exception); throw AmqpExceptionHelper.GetClientException(exception, null, link?.GetInnerException(), session.IsClosing()); } }
protected ActiveClientLinkObject(AmqpObject amqpLinkObject, Uri endpointUri, string[] audience, string[] requiredClaims, DateTime authorizationValidUntilUtc) { this.LinkObject = amqpLinkObject; this.EndpointUri = endpointUri; this.Audience = audience; this.requiredClaims = requiredClaims; this.AuthorizationValidUntilUtc = authorizationValidUntilUtc; }
/// <summary> /// Callback for connection close events /// </summary> /// <param name="sender"></param> /// <param name="error"></param> protected virtual void OnConnectionClosed(AmqpObject sender, Error error) { if (error != null) { Debug.WriteLine("Connection Closed {0} {1}", error.Condition, error.Description); } m_closed = true; }
protected override AmqpObject OnCreateAmqpLink(AmqpConnection connection, AmqpLinkSettings linkSettings, AmqpSession amqpSession) { AmqpObject link = (linkSettings.IsReceiver()) ? (AmqpObject) new ReceivingAmqpLink(linkSettings) : (AmqpObject) new SendingAmqpLink(linkSettings); linkSettings.LinkName = $"{connection.Settings.ContainerId};{connection.Identifier}:{amqpSession.Identifier}:{link.Identifier}"; ((AmqpLink)link).AttachTo(amqpSession); return(link); }
protected void OnSenderClosedCallback(AmqpObject sender, Error error) { _Logger.LogError("OnSenderClosedCallback: " + error.Info + error.Description); // signal the connection will fail SetDead( ); // re-create the connection pro-actively EstablishSender( ); }
public ActiveClientLinkObject(AmqpObject amqpLinkObject, string audience, string endpointUri, string[] requiredClaims, bool isClientToken, DateTime authorizationValidToUtc) { this.amqpLinkObject = amqpLinkObject; this.audience = audience; this.endpointUri = endpointUri; this.requiredClaims = requiredClaims; this.isClientToken = isClientToken; this.authorizationValidToUtc = authorizationValidToUtc; }
void OnLinkClosed(AmqpObject sender, Error error) { ListenerLink link = (ListenerLink)sender; lock (this.collection) { this.collection.Remove(link); } }
static void OnLinkClosed(AmqpObject sender, Error error) { ListenerLink link = (ListenerLink)sender; var thisPtr = (MessageProcessor)link.State; lock (thisPtr.links) { thisPtr.links.Remove(link); } }
protected override void SessionClosed(AmqpObject sender, Error error) { Logger.TraceLog("AMQP Session Closed. Error: " + error.ToString()); if (this._amqpReceiver != null) { this._amqpReceiver.Close(); this._amqpReceiver = null; } this.AmqpSession = null; }
/// <summary> /// Creates the timer event handler to support refreshing AMQP link authorization /// on a recurring basis. /// </summary> /// <param name="entityPath"></param> /// /// <param name="connection">The AMQP connection to which the link being refreshed is bound to.</param> /// <param name="amqpLink">The AMQO link to refresh authorization for.</param> /// <param name="tokenProvider">The <see cref="CbsTokenProvider" /> to use for obtaining access tokens.</param> /// <param name="endpoint">The Service Bus service endpoint that the AMQP link is communicating with.</param> /// <param name="audience">The audience associated with the authorization. This is likely the <paramref name="endpoint"/> absolute URI.</param> /// <param name="resource">The resource associated with the authorization. This is likely the <paramref name="endpoint"/> absolute URI.</param> /// <param name="requiredClaims">The set of claims required to support the operations of the AMQP link.</param> /// <param name="refreshTimeout">The timeout to apply when requesting authorization refresh.</param> /// <param name="refreshTimerFactory">A function to allow retrieving the <see cref="Timer" /> associated with the link authorization.</param> /// /// <returns>A <see cref="TimerCallback"/> delegate to perform the refresh when a timer is due.</returns> /// protected virtual TimerCallback CreateAuthorizationRefreshHandler( string entityPath, AmqpConnection connection, AmqpObject amqpLink, CbsTokenProvider tokenProvider, Uri endpoint, string audience, string resource, string[] requiredClaims, TimeSpan refreshTimeout, Func <Timer> refreshTimerFactory) { return(async _ => { ServiceBusEventSource.Log.AmqpLinkAuthorizationRefreshStart(entityPath, endpoint.AbsoluteUri); Timer refreshTimer = refreshTimerFactory(); try { if (refreshTimer == null) { return; } DateTime authExpirationUtc = await RequestAuthorizationUsingCbsAsync(connection, tokenProvider, endpoint, audience, resource, requiredClaims, refreshTimeout).ConfigureAwait(false); // Reset the timer for the next refresh. if (authExpirationUtc >= DateTimeOffset.UtcNow) { refreshTimer.Change(CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan); } } catch (ObjectDisposedException) { // This can occur if the connection is closed or the scope disposed after the factory // is called but before the timer is updated. The callback may also fire while the timer is // in the act of disposing. Do not consider it an error. } catch (Exception ex) { ServiceBusEventSource.Log.AmqpLinkAuthorizationRefreshError(entityPath, endpoint.AbsoluteUri, ex.Message); // Attempt to unset the timer; there's a decent chance that it has been disposed at this point or // that the connection has been closed. Ignore potential exceptions, as they won't impact operation. // At worse, another timer tick will occur and the operation will be retried. try { refreshTimer.Change(Timeout.Infinite, Timeout.Infinite); } catch {} } finally { ServiceBusEventSource.Log.AmqpLinkAuthorizationRefreshComplete(entityPath, endpoint.AbsoluteUri); } }); }
void ReceiverLinkClosed(AmqpObject sender, Error error) { Logger.TraceLog("AMQP Link Closed. Error: " + error.ToString()); this._amqpReceiver = null; // TODO: Only reconnect on transient/recoverable errors if (error != null) { // TODO - keep retrying (on background thread) if network is down for longe time periods this.Start(); } }
internal static Exception ConvertToIoTHubException(Exception exception, AmqpObject source) { Exception e = ConvertToIoTHubException(exception); if (source.IsClosing() && (e is InvalidOperationException || e is OperationCanceledException)) { return(RESOUCE_DISCONNECTED_EXCEPTION); } else { return(e); } }
internal static Exception ConvertToIoTHubException(Exception exception, AmqpObject source) { Exception e = ConvertToIoTHubException(exception); if (source.IsClosing() && (e is InvalidOperationException || e is OperationCanceledException)) { return(new IotHubCommunicationException("Amqp resource is disconnected.")); } else { return(e); } }
private void OnAmqpObjectClosed(object sender, EventArgs args) { AmqpObject amqpObject = (AmqpObject)sender; amqpObject.Closed -= this.onAmqpObjectClosed; Exception terminalException = amqpObject.TerminalException; if (terminalException == null) { terminalException = new ConnectionLostException("The connection to the connect service was lost."); } this.OnDisconnected(ExceptionHelper.ToRelayContract(terminalException)); }
private void Sender_Closed(AmqpObject sender, Amqp.Framing.Error error) { if (!_closing) { var senderLink = (SenderLink)sender; var key = _senders.ToArray().Where(pair => pair.Value == senderLink).Select(pair => pair.Key).FirstOrDefault(); if (key != null) { SenderLink returnedLink; _senders.TryRemove(key, out returnedLink); } } }
void OnControllerClosed(AmqpObject obj, Error error) { var controller = (Controller)obj; bool removed; lock (this.SyncRoot) { removed = this.controllers.Remove(controller.Session.Connection); } if (removed) { controller.Session.Close(0); } }
/// <summary> /// Creates the timer event handler to support refreshing AMQP link authorization /// on a recurring basis. /// </summary> /// /// <param name="connection">The AMQP connection to which the link being refreshed is bound to.</param> /// <param name="amqpLink">The AMQO link to refresh authorization for.</param> /// <param name="tokenProvider">The <see cref="CbsTokenProvider" /> to use for obtaining access tokens.</param> /// <param name="endpoint">The Event Hubs service endpoint that the AMQP link is communicating with.</param> /// <param name="audience">The audience associated with the authorization. This is likely the <paramref name="endpoint"/> absolute URI.</param> /// <param name="resource">The resource associated with the authorization. This is likely the <paramref name="endpoint"/> absolute URI.</param> /// <param name="requiredClaims">The set of claims required to support the operations of the AMQP link.</param> /// <param name="refreshTimeout">The timeout to apply when requesting authorization refresh.</param> /// <param name="refreshTimerFactory">A function to allow retrieving the <see cref="Timer" /> associated with the link authorization.</param> /// /// <returns>A <see cref="TimerCallback"/> delegate to perform the refresh when a timer is due.</returns> /// protected virtual TimerCallback CreateAuthorizationRefreshHandler(AmqpConnection connection, AmqpObject amqpLink, CbsTokenProvider tokenProvider, Uri endpoint, string audience, string resource, string[] requiredClaims, TimeSpan refreshTimeout, Func <Timer> refreshTimerFactory) { return(async _ => { EventHubsEventSource.Log.AmqpLinkAuthorizationRefreshStart(EventHubName, endpoint.AbsoluteUri); var refreshTimer = refreshTimerFactory(); try { if (refreshTimer == null) { return; } var authExpirationUtc = await RequestAuthorizationUsingCbsAsync(connection, tokenProvider, endpoint, audience, resource, requiredClaims, refreshTimeout).ConfigureAwait(false); // Reset the timer for the next refresh. if (authExpirationUtc >= DateTimeOffset.UtcNow) { refreshTimer.Change(CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan); } } catch (ObjectDisposedException) { // This can occur if the connection is closed or the scope disposed after the factory // is called but before the timer is updated. The callback may also fire while the timer is // in the act of disposing. Do not consider it an error. } catch (Exception ex) { EventHubsEventSource.Log.AmqpLinkAuthorizationRefreshError(EventHubName, endpoint.AbsoluteUri, ex.Message); refreshTimer.Change(Timeout.Infinite, Timeout.Infinite); } finally { EventHubsEventSource.Log.AmqpLinkAuthorizationRefreshComplete(EventHubName, endpoint.AbsoluteUri); } }); }
protected override async Task OpenLinkAsync(AmqpObject link, IotHubConnectionString doNotUse, string doNotUse2, TimeSpan timeout) { try { await link.OpenAsync(timeout); } catch (Exception exception) { if (exception.IsFatal()) { throw; } link.SafeClose(exception); throw; } }
static async Task OpenLinkAsync(AmqpObject link, TimeSpan timeout) { var timeoutHelper = new TimeoutHelper(timeout); try { await link.OpenAsync(timeoutHelper.RemainingTime()); } catch (Exception exception) { if (exception.IsFatal()) { throw; } link.SafeClose(exception); throw; } }
protected override async Task OpenLinkAsync(AmqpObject link, IotHubConnectionString connectionString, string audience, TimeSpan timeout) { var timeoutHelper = new TimeoutHelper(timeout); try { // this is a device-scope connection string. We need to send a CBS token for this specific link before opening it. var iotHubLinkTokenRefresher = new IotHubTokenRefresher( this.FaultTolerantSession.Value, connectionString, audience ); if (this.iotHubTokenRefreshers.TryAdd(link, iotHubLinkTokenRefresher)) { link.SafeAddClosed((s, e) => { if (this.iotHubTokenRefreshers.TryRemove(link, out iotHubLinkTokenRefresher)) { iotHubLinkTokenRefresher.Cancel(); } }); // Send Cbs token for new link first await iotHubLinkTokenRefresher.SendCbsTokenAsync(timeoutHelper.RemainingTime()); } // Open Amqp Link await link.OpenAsync(timeoutHelper.RemainingTime()); } catch (Exception exception) { if (exception.IsFatal()) { throw; } link.SafeClose(exception); throw; } }
protected abstract Task OpenLinkAsync(AmqpObject link, IotHubConnectionString connectionString, string audience, TimeSpan timeout);
void OnLinkClosed(AmqpObject sender, Error error) { this.Credit = 0; this.queue.OnConsumerClosed(this.id, this); }
void OnOperationComplete(AmqpObject link, IAsyncResult result, bool isOpen) { Exception completeException = null; try { if (isOpen) { link.EndOpen(result); } else { link.EndClose(result); } } catch (Exception exception) { if (Fx.IsFatal(exception)) { throw; } completeException = exception; } bool shouldComplete = true; if (completeException == null) { AmqpObjectState initialState = isOpen ? AmqpObjectState.OpenSent : AmqpObjectState.CloseSent; lock (this.ThisLock) { shouldComplete = this.sender.State != initialState && this.receiver.State != initialState; } } if (shouldComplete) { if (isOpen) { this.CompleteOpen(false, completeException); } else { this.CompleteClose(false, completeException); } } }