-// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2018 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
/********************************** State *******************************/
-State::State(const int value, const std::string& label, StateHandler handler)
- : LabeledValue(value, label), handler_(handler) {
+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() {
(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() {
}
void
-StateSet::add(const int value, const std::string& label, StateHandler handler) {
+StateSet::add(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing) {
try {
- LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler)));
+ 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 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) {
+ 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) {
}
StateModel::~StateModel(){
void
StateModel::defineState(unsigned int state_value, const std::string& label,
- StateHandler handler) {
+ StateHandler handler, const StatePausing& state_pausing) {
if (!isModelNew()) {
// Don't allow for self-modifying maps.
isc_throw(StateModelError, "States may only be added to a new model."
// Attempt to add the state to the set.
try {
- states_.add(state_value, label, handler);
+ states_.add(state_value, label, handler, state_pausing);
} catch (const std::exception& ex) {
isc_throw(StateModelError, "Error adding state: " << ex.what());
}
transition(END_ST, END_EVT);
}
+void
+StateModel::unpauseModel() {
+ paused_ = false;
+}
+
void
StateModel::abortModel(const std::string& explanation) {
transition(END_ST, FAIL_EVT);
// 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_ && (getState(state)->shouldPause())) {
+ paused_ = true;
+ }
}
void
return (isModelDone() && (next_event_ == FAIL_EVT));
}
+bool
+StateModel::isModelPaused() const {
+ return (paused_);
+}
+
std::string
StateModel::getStateLabel(const int state) const {
return (states_.getLabel(state));
/// @brief Defines a pointer to an instance method for handling a state.
typedef boost::function<void()> StateHandler;
+/// @brief State machine pausing modes.
+///
+/// Supported modes are:
+/// - always pause in the given state,
+/// - never pause in the given state,
+/// - pause upon first transition to the given state.
+enum StatePausing {
+ STATE_PAUSE_ALWAYS,
+ STATE_PAUSE_NEVER,
+ STATE_PAUSE_ONCE
+};
+
/// @brief Defines a State within the State Model.
///
/// This class provides the means to define a state within a set or dictionary
/// of states, and assign the state an handler method to execute the state's
/// actions. It derives from LabeledValue which allows a set of states to be
/// keyed by integer constants.
+///
+/// Because a state model can be paused in selected states, this class also
+/// provides the means for specifying a pausing mode and for checking whether
+/// the state model should be paused when entering this state.
class State : public LabeledValue {
public:
/// @brief Constructor
/// @param label is the text label to assign to the state
/// @param handler is the bound instance method which handles the state's
/// action.
+ /// @param state_pausing pausing mode selected for the given state. The
+ /// default value is @c STATE_PAUSE_NEVER.
///
/// A typical invocation might look this:
///
/// @endcode
///
/// @throw StateModelError if label is null or blank.
- State(const int value, const std::string& label, StateHandler handler);
+ State(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing = STATE_PAUSE_NEVER);
/// @brief Destructor
virtual ~State();
/// @brief Invokes the State's handler.
void run();
+ /// @brief Indicates if the state model should pause upon entering
+ /// this state.
+ ///
+ /// It modifies the @c was_paused_ flag if the state model should
+ /// pause. That way, it keeps track of visits in this particular state,
+ /// making it possible to pause only upon the first transition to the
+ /// state when @c STATE_PAUSE_ONCE mode is used.
+ bool shouldPause();
+
private:
/// @brief Bound instance method pointer to the state's handler method.
StateHandler handler_;
+
+ /// @brief Specifies selected pausing mode for a state.
+ StatePausing pausing_;
+
+ /// @brief Indicates if the state machine was already paused in this
+ /// state.
+ bool was_paused_;
};
/// @brief Defines a shared pointer to a State.
/// @param value is the numeric value of the state
/// @param label is the text label to assign to the state
/// @param handler is the bound instance method which handles the state's
+ /// @param state_pausing state pausing mode for the given state.
///
/// @throw StateModelError if the value is already defined in the set, or
/// if the label is null or blank.
- void add(const int value, const std::string& label, StateHandler handler);
+ void add(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing);
/// @brief Fetches a state for the given value.
///
/// which transitions the model to END_ST with END_EVT. Bringing the model to
/// an abnormal end is done via the abortModel method, which transitions the
/// model to END_ST with FAILED_EVT.
+///
+/// The model can be paused in the selected states. The states in which the
+/// state model should pause (always or only once) are determined within the
+/// @c StateModel::defineStates method. The state handlers can check whether
+/// the state machine is paused or not by calling @c StateModel::isModelPaused
+/// and act accordingy. Typically, the state handler would simply post the
+/// @c NOP_EVT when it finds that the state model is paused. The model
+/// remains paused until @c StateModel::unpauseModel is called.
class StateModel {
public:
/// handler should call endModel.
void endModel();
+ /// @brief Unpauses state model.
+ void unpauseModel();
+
/// @brief An empty state handler.
///
/// This method is primarily used to permit special states, NEW_ST and
/// exceptions.
/// @param handler is the bound instance method which implements the state's
/// actions.
+ /// @param state_pausing pausing mode selected for the given state. The
+ /// default value is @c STATE_PAUSE_NEVER.
///
/// @throw StateModelError if the model has already been started, if
/// the value is already defined, or if the label is empty.
void defineState(unsigned int value, const std::string& label,
- StateHandler handler);
+ StateHandler handler,
+ const StatePausing& state_pausing = STATE_PAUSE_NEVER);
/// @brief Fetches the state referred to by value.
///
/// @return Boolean true if the model has reached the END_ST.
bool isModelDone() const;
+ /// @brief Returns whether or not the model is paused.
+ ///
+ /// @return Boolean true if the model is paused, false otherwise.
+ bool isModelPaused() const;
+
/// @brief Returns whether or not the model failed.
///
/// @return Boolean true if the model has reached the END_ST and the last
/// @brief Indicates if state exit logic should be executed.
bool on_exit_flag_;
+
+ /// @brief Indicates if the state model is paused.
+ bool paused_;
};
/// @brief Defines a pointer to a StateModel.
///@brief State which finishes off processing.
static const int DONE_ST = SM_DERIVED_STATE_MIN + 4;
+ ///@brief State in which model is always paused.
+ static const int PAUSE_ALWAYS_ST = SM_DERIVED_STATE_MIN + 5;
+
+ ///@brief State in which model is paused at most once.
+ static const int PAUSE_ONCE_ST = SM_DERIVED_STATE_MIN + 6;
+
// StateModelTest events
///@brief Event used to trigger initiation of asynchronous work.
static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1;
///@brief Event used to trigger an attempt to transition to bad state
static const int SIMULATE_ERROR_EVT = SM_DERIVED_EVENT_MIN + 5;
+ ///@brief Event used to indicate that state machine is unpaused.
+ static const int UNPAUSED_EVT = SM_DERIVED_EVENT_MIN + 6;
+
/// @brief Constructor
///
/// Parameters match those needed by StateModel.
}
}
+ /// @brief State handler for PAUSE_ALWAYS_ST and PAUSE_ONCE_ST.
+ void pauseHandler() {
+ postNextEvent(NOP_EVT);
+ }
+
/// @brief Construct the event dictionary.
virtual void defineEvents() {
// Invoke the base call implementation first.
defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT");
defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT");
defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT");
+ defineEvent(UNPAUSED_EVT, "UNPAUSED_EVT");
}
/// @brief Verify the event dictionary.
getEvent(ALL_DONE_EVT);
getEvent(FORCE_UNDEFINED_ST_EVT);
getEvent(SIMULATE_ERROR_EVT);
+ getEvent(UNPAUSED_EVT);
}
/// @brief Construct the state dictionary.
defineState(DONE_ST, "DONE_ST",
boost::bind(&StateModelTest::doneWorkHandler, this));
+
+ defineState(PAUSE_ALWAYS_ST, "PAUSE_ALWAYS_ST",
+ boost::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ALWAYS);
+
+ defineState(PAUSE_ONCE_ST, "PAUSE_ONCE_ST",
+ boost::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ONCE);
}
/// @brief Verify the state dictionary.
getState(READY_ST);
getState(DO_WORK_ST);
getState(DONE_ST);
+ getState(PAUSE_ALWAYS_ST);
+ getState(PAUSE_ONCE_ST);
}
/// @brief Manually construct the event and state dictionaries.
const int StateModelTest::WORK_START_EVT;
const int StateModelTest::WORK_DONE_EVT;
const int StateModelTest::ALL_DONE_EVT;
+const int StateModelTest::PAUSE_ALWAYS_ST;
+const int StateModelTest::PAUSE_ONCE_ST;
/// @brief Checks the fundamentals of defining and retrieving events.
TEST_F(StateModelTest, eventDefinition) {
EXPECT_TRUE(getWorkCompleted());
}
+// This test verifies the pausing and un-pausing capabilities of the state
+// model.
+TEST_F(StateModelTest, stateModelPause) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(isModelPaused());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Transition straight to the state in which the model should always
+ // pause.
+ ASSERT_NO_THROW(startModel(PAUSE_ALWAYS_ST));
+
+ // Verify it was successful and that the model is paused.
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Run the model again. It should still be paused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should be paused at most once.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // The model should still be paused until explicitly unpaused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ unpauseModel();
+
+ // Transition back to the first state. The model should pause again.
+ transition(PAUSE_ALWAYS_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should pause only once. This time it should not pause.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_FALSE(isModelPaused());
+}
+
}