private async Task TerminateAsync(string terminationReason, bool aborted, Exception abortException = null) { try { this.state = ClientInternalState.Terminated; if (aborted) { if (abortException != null) { logger.Error(this.clientId, $"Client aborting due: {terminationReason}. Exception: {abortException}"); } else { logger.Error(this.clientId, $"Client aborting due: {terminationReason}"); } } else { logger.Info(this.clientId, $"Client terminating due: {terminationReason}"); } await this.zooKeeperService.CloseSessionAsync(); await this.resourceManager.InvokeOnStopActionsAsync(this.clientId, "No role"); if (aborted) { await this.resourceManager.InvokeOnAbortActionsAsync(this.clientId, $"The client has aborted due to: {terminationReason}", abortException); } logger.Info(this.clientId, "Client terminated"); } catch (TerminateClientException e) { logger.Error(this.clientId, "Client termination failure during invocation of on stop/abort actions", e); } finally { if (aborted) { this.aborted = true; } this.started = false; } }
private async Task RunStateMachine(CancellationToken token, ClientOptions clientOptions) { while (this.state != ClientInternalState.Terminated) { if (token.IsCancellationRequested) { await TerminateAsync("cancellation", false); } try { switch (this.state) { case ClientInternalState.NoSession: var established = await EstablishSessionAsync(token); switch (established) { case NewSessionResult.Established: this.state = ClientInternalState.NoClientNode; break; case NewSessionResult.TimeOut: this.state = ClientInternalState.NoSession; await WaitRandomTime(TimeSpan.FromSeconds(5)); break; default: this.state = ClientInternalState.Error; break; } break; case ClientInternalState.NoClientNode: var created = await CreateClientNodeAsync(); if (created) { this.state = ClientInternalState.NoRole; } else { this.state = ClientInternalState.Error; } break; case ClientInternalState.NoRole: var epochAttained = await CacheEpochLocallyAsync(); if (!epochAttained) { await EvaluateTerminationAsync(token, clientOptions, "Couldn't read the current epoch."); } var(electionResult, lowerSiblingPath) = await DetermineLeadershipAsync(); switch (electionResult) { case ElectionResult.IsLeader: this.state = ClientInternalState.IsLeader; this.watchSiblingNodePath = string.Empty; break; case ElectionResult.IsFollower: this.state = ClientInternalState.IsFollower; this.watchSiblingNodePath = lowerSiblingPath; break; default: await EvaluateTerminationAsync(token, clientOptions, "The client has entered an unknown state"); break; } break; case ClientInternalState.IsLeader: var coordinatorExitReason = await BecomeCoordinatorAsync(token); switch (coordinatorExitReason) { case CoordinatorExitReason.NoLongerCoordinator: SetStateToNoSession(); // need a new client node break; case CoordinatorExitReason.Cancelled: await TerminateAsync("cancellation", false); break; case CoordinatorExitReason.SessionExpired: SetStateToNoSession(); break; case CoordinatorExitReason.PotentialInconsistentState: await EvaluateTerminationAsync(token, clientOptions, "The client has entered a potentially inconsistent state"); break; case CoordinatorExitReason.FatalError: await TerminateAsync("fatal error", true); break; default: await EvaluateTerminationAsync(token, clientOptions, "The client has entered an unknown state"); break; } break; case ClientInternalState.IsFollower: var followerExitReason = await BecomeFollowerAsync(token); switch (followerExitReason) { case FollowerExitReason.PossibleRoleChange: this.state = ClientInternalState.NoRole; break; case FollowerExitReason.Cancelled: await TerminateAsync("cancellation", false); break; case FollowerExitReason.SessionExpired: SetStateToNoSession(); break; case FollowerExitReason.FatalError: await TerminateAsync("fatal error", true); break; case FollowerExitReason.PotentialInconsistentState: await EvaluateTerminationAsync(token, clientOptions, "The client has entered an potential inconsistent state"); break; default: await EvaluateTerminationAsync(token, clientOptions, "The client has entered an unknown state"); break; } break; case ClientInternalState.Error: await EvaluateTerminationAsync(token, clientOptions, "The client has entered an error state"); break; default: await EvaluateTerminationAsync(token, clientOptions, "The client has entered an unknown state"); break; } } catch (ZkSessionExpiredException) { this.logger.Info(this.clientId, "ZooKeeper session lost"); SetStateToNoSession(); } catch (ZkOperationCancelledException) { await TerminateAsync("cancellation", false); } catch (TerminateClientException e) { await TerminateAsync("Fatal error", true, e); } catch (InconsistentStateException e) { await EvaluateTerminationAsync(token, clientOptions, "An error has caused that may have left the client in an inconsistent state.", e); } catch (Exception e) { await EvaluateTerminationAsync(token, clientOptions, "An unexpected error has been caught", e); } } }
private void SetStateToNoSession() { ResetMutableState(); this.state = ClientInternalState.NoSession; }