diff options
Diffstat (limited to 'src/lib/util/state_model.cc')
-rw-r--r-- | src/lib/util/state_model.cc | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/src/lib/util/state_model.cc b/src/lib/util/state_model.cc new file mode 100644 index 0000000..6c9a13d --- /dev/null +++ b/src/lib/util/state_model.cc @@ -0,0 +1,465 @@ +// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <util/state_model.h> +#include <string> + +namespace isc { +namespace util { + +/********************************** State *******************************/ + +State::State(const int value, const std::string& label, StateHandler handler, + const StatePausing& state_pausing) + : LabeledValue(value, label), handler_(handler), pausing_(state_pausing), + was_paused_(false) { +} + +State::~State() { +} + +void +State::run() { + (handler_)(); +} + +bool +State::shouldPause() { + if ((pausing_ == STATE_PAUSE_ALWAYS) || + ((pausing_ == STATE_PAUSE_ONCE) && (!was_paused_))) { + was_paused_ = true; + return (true); + } + return (false); +} + +/********************************** StateSet *******************************/ + +StateSet::StateSet() { +} + +StateSet::~StateSet() { +} + +void +StateSet::add(const int value, const std::string& label, StateHandler handler, + const StatePausing& state_pausing) { + try { + LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler, + state_pausing))); + } catch (const std::exception& ex) { + isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what()); + } + +} + +const StatePtr +StateSet::getState(int value) { + if (!isDefined(value)) { + isc_throw(StateModelError," StateSet: state is undefined"); + } + + // Since we have to use dynamic casting, to get a state pointer + // we can't return a reference. + StatePtr state = boost::dynamic_pointer_cast<State>(get(value)); + return (state); +} + +/********************************** StateModel *******************************/ + + +// Common state model states +const int StateModel::NEW_ST; +const int StateModel::END_ST; + +const int StateModel::SM_DERIVED_STATE_MIN; + +// Common state model events +const int StateModel::NOP_EVT; +const int StateModel::START_EVT; +const int StateModel::END_EVT; +const int StateModel::FAIL_EVT; + +const int StateModel::SM_DERIVED_EVENT_MIN; + +StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false), + curr_state_(NEW_ST), prev_state_(NEW_ST), + last_event_(NOP_EVT), next_event_(NOP_EVT), + on_entry_flag_(false), on_exit_flag_(false), + paused_(false), mutex_(new std::mutex) { +} + +StateModel::~StateModel(){ +} + +void +StateModel::startModel(const int start_state) { + // Initialize dictionaries of events and states. + initDictionaries(); + + // Set the current state to starting state and enter the run loop. + setState(start_state); + + // Start running the model. + runModel(START_EVT); +} + +void +StateModel::runModel(unsigned int run_event) { + /// If the dictionaries aren't built bail out. + if (!dictionaries_initted_) { + abortModel("runModel invoked before model has been initialized"); + } + + try { + // Seed the loop with the given event as the next to process. + postNextEvent(run_event); + do { + // Invoke the current state's handler. It should consume the + // next event, then determine what happens next by setting + // current state and/or the next event. + getState(curr_state_)->run(); + + // Keep going until a handler sets next event to a NOP_EVT. + } while (!isModelDone() && getNextEvent() != NOP_EVT); + } catch (const std::exception& ex) { + // The model has suffered an unexpected exception. This constitutes + // a model violation and indicates a programmatic shortcoming. + // In theory, the model should account for all error scenarios and + // deal with them accordingly. Transition to END_ST with FAILED_EVT + // via abortModel. + abortModel(ex.what()); + } +} + +void +StateModel::nopStateHandler() { +} + +void +StateModel::initDictionaries() { + std::lock_guard<std::mutex> lock(*mutex_); + if (dictionaries_initted_) { + isc_throw(StateModelError, "Dictionaries already initialized"); + } + // First let's build and verify the dictionary of events. + try { + defineEvents(); + verifyEvents(); + } catch (const std::exception& ex) { + isc_throw(StateModelError, "Event set is invalid: " << ex.what()); + } + + // Next let's build and verify the dictionary of states. + try { + defineStates(); + verifyStates(); + } catch (const std::exception& ex) { + isc_throw(StateModelError, "State set is invalid: " << ex.what()); + } + + // Record that we are good to go. + dictionaries_initted_ = true; +} + +void +StateModel::defineEvent(unsigned int event_value, const std::string& label) { + if (!isModelNewInternal()) { + // Don't allow for self-modifying models. + isc_throw(StateModelError, "Events may only be added to a new model." + << event_value << " - " << label); + } + + // Attempt to add the event to the set. + try { + events_.add(event_value, label); + } catch (const std::exception& ex) { + isc_throw(StateModelError, "Error adding event: " << ex.what()); + } +} + +const EventPtr& +StateModel::getEvent(unsigned int event_value) { + if (!events_.isDefined(event_value)) { + isc_throw(StateModelError, + "Event value is not defined:" << event_value); + } + + return (events_.get(event_value)); +} + +void +StateModel::defineState(unsigned int state_value, const std::string& label, + StateHandler handler, const StatePausing& state_pausing) { + if (!isModelNewInternal()) { + // Don't allow for self-modifying maps. + isc_throw(StateModelError, "States may only be added to a new model." + << state_value << " - " << label); + } + + // Attempt to add the state to the set. + try { + states_.add(state_value, label, handler, state_pausing); + } catch (const std::exception& ex) { + isc_throw(StateModelError, "Error adding state: " << ex.what()); + } +} + +const StatePtr +StateModel::getState(unsigned int state_value) { + std::lock_guard<std::mutex> lock(*mutex_); + return getStateInternal(state_value); +} + +const StatePtr +StateModel::getStateInternal(unsigned int state_value) { + if (!states_.isDefined(state_value)) { + isc_throw(StateModelError, + "State value is not defined:" << state_value); + } + + return (states_.getState(state_value)); +} + +void +StateModel::defineEvents() { + defineEvent(NOP_EVT, "NOP_EVT"); + defineEvent(START_EVT, "START_EVT"); + defineEvent(END_EVT, "END_EVT"); + defineEvent(FAIL_EVT, "FAIL_EVT"); +} + +void +StateModel::verifyEvents() { + getEvent(NOP_EVT); + getEvent(START_EVT); + getEvent(END_EVT); + getEvent(FAIL_EVT); +} + +void +StateModel::defineStates() { + defineState(NEW_ST, "NEW_ST", + std::bind(&StateModel::nopStateHandler, this)); + defineState(END_ST, "END_ST", + std::bind(&StateModel::nopStateHandler, this)); +} + +void +StateModel::verifyStates() { + getStateInternal(NEW_ST); + getStateInternal(END_ST); +} + +void +StateModel::onModelFailure(const std::string&) { + // Empty implementation to make deriving classes simpler. +} + +void +StateModel::transition(unsigned int state, unsigned int event) { + std::lock_guard<std::mutex> lock(*mutex_); + setStateInternal(state); + postNextEventInternal(event); +} + +void +StateModel::endModel() { + transition(END_ST, END_EVT); +} + +void +StateModel::unpauseModel() { + std::lock_guard<std::mutex> lock(*mutex_); + paused_ = false; +} + +void +StateModel::abortModel(const std::string& explanation) { + transition(END_ST, FAIL_EVT); + + std::ostringstream stream ; + stream << explanation << " : " << getContextStr(); + onModelFailure(stream.str()); +} + +void +StateModel::setState(unsigned int state) { + std::lock_guard<std::mutex> lock(*mutex_); + setStateInternal(state); +} + +void +StateModel::setStateInternal(unsigned int state) { + if (state != END_ST && !states_.isDefined(state)) { + isc_throw(StateModelError, + "Attempt to set state to an undefined value: " << state ); + } + + prev_state_ = curr_state_; + curr_state_ = state; + + // Set the "do" flags if we are transitioning. + on_entry_flag_ = ((state != END_ST) && (prev_state_ != curr_state_)); + + // At this time they are calculated the same way. + on_exit_flag_ = on_entry_flag_; + + // If we're entering the new state we need to see if we should + // pause the state model in this state. + if (on_entry_flag_ && !paused_ && getStateInternal(state)->shouldPause()) { + paused_ = true; + } +} + +void +StateModel::postNextEvent(unsigned int event_value) { + std::lock_guard<std::mutex> lock(*mutex_); + postNextEventInternal(event_value); +} + +void +StateModel::postNextEventInternal(unsigned int event_value) { + // Check for FAIL_EVT as special case of model error before events are + // defined. + if (event_value != FAIL_EVT && !events_.isDefined(event_value)) { + isc_throw(StateModelError, + "Attempt to post an undefined event, value: " << event_value); + } + + last_event_ = next_event_; + next_event_ = event_value; +} + +bool +StateModel::doOnEntry() { + std::lock_guard<std::mutex> lock(*mutex_); + bool ret = on_entry_flag_; + on_entry_flag_ = false; + return (ret); +} + +bool +StateModel::doOnExit() { + std::lock_guard<std::mutex> lock(*mutex_); + bool ret = on_exit_flag_; + on_exit_flag_ = false; + return (ret); +} + +unsigned int +StateModel::getCurrState() const { + std::lock_guard<std::mutex> lock(*mutex_); + return (curr_state_); +} + +unsigned int +StateModel::getPrevState() const { + std::lock_guard<std::mutex> lock(*mutex_); + return (prev_state_); +} + +unsigned int +StateModel::getLastEvent() const { + std::lock_guard<std::mutex> lock(*mutex_); + return (last_event_); +} + +unsigned int +StateModel::getNextEvent() const { + std::lock_guard<std::mutex> lock(*mutex_); + return (next_event_); +} + +bool +StateModel::isModelNew() const { + std::lock_guard<std::mutex> lock(*mutex_); + return isModelNewInternal(); +} + +bool +StateModel::isModelNewInternal() const { + return (curr_state_ == NEW_ST); +} + +bool +StateModel::isModelRunning() const { + std::lock_guard<std::mutex> lock(*mutex_); + return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST)); +} + +bool +StateModel::isModelWaiting() const { + std::lock_guard<std::mutex> lock(*mutex_); + return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST) && + (next_event_ == NOP_EVT)); +} + +bool +StateModel::isModelDone() const { + std::lock_guard<std::mutex> lock(*mutex_); + return (curr_state_ == END_ST); +} + +bool +StateModel::didModelFail() const { + std::lock_guard<std::mutex> lock(*mutex_); + return ((curr_state_ == END_ST) && (next_event_ == FAIL_EVT)); +} + +bool +StateModel::isModelPaused() const { + std::lock_guard<std::mutex> lock(*mutex_); + return (paused_); +} + +std::string +StateModel::getStateLabel(const int state) const { + std::lock_guard<std::mutex> lock(*mutex_); + return getStateLabelInternal(state); +} + +std::string +StateModel::getStateLabelInternal(const int state) const { + return (states_.getLabel(state)); +} + +std::string +StateModel::getEventLabel(const int event) const { + std::lock_guard<std::mutex> lock(*mutex_); + return getEventLabelInternal(event); +} + +std::string +StateModel::getEventLabelInternal(const int event) const { + return (events_.getLabel(event)); +} + +std::string +StateModel::getContextStr() const { + std::lock_guard<std::mutex> lock(*mutex_); + std::ostringstream stream; + stream << "current state: [ " + << curr_state_ << " " << getStateLabelInternal(curr_state_) + << " ] next event: [ " + << next_event_ << " " << getEventLabelInternal(next_event_) << " ]"; + return (stream.str()); +} + +std::string +StateModel::getPrevContextStr() const { + std::lock_guard<std::mutex> lock(*mutex_); + std::ostringstream stream; + stream << "previous state: [ " + << prev_state_ << " " << getStateLabelInternal(prev_state_) + << " ] last event: [ " + << next_event_ << " " << getEventLabelInternal(last_event_) << " ]"; + return (stream.str()); +} + +} // namespace isc::util +} // namespace isc |