Commit 2a727b6e authored by Marcin Siodelski's avatar Marcin Siodelski

[5674] Implemented state model pausing.

parent 88262cbd
// 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 // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
...@@ -14,8 +14,10 @@ namespace util { ...@@ -14,8 +14,10 @@ namespace util {
/********************************** State *******************************/ /********************************** State *******************************/
State::State(const int value, const std::string& label, StateHandler handler) State::State(const int value, const std::string& label, StateHandler handler,
: LabeledValue(value, label), handler_(handler) { const StatePausing& state_pausing)
: LabeledValue(value, label), handler_(handler), pausing_(state_pausing),
was_paused_(false) {
} }
State::~State() { State::~State() {
...@@ -26,6 +28,16 @@ State::run() { ...@@ -26,6 +28,16 @@ State::run() {
(handler_)(); (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::StateSet() {
...@@ -35,9 +47,11 @@ StateSet::~StateSet() { ...@@ -35,9 +47,11 @@ StateSet::~StateSet() {
} }
void 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 { try {
LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler))); LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler,
state_pausing)));
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what()); isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what());
} }
...@@ -74,9 +88,10 @@ const int StateModel::FAIL_EVT; ...@@ -74,9 +88,10 @@ const int StateModel::FAIL_EVT;
const int StateModel::SM_DERIVED_EVENT_MIN; const int StateModel::SM_DERIVED_EVENT_MIN;
StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false), StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false),
curr_state_(NEW_ST), prev_state_(NEW_ST), curr_state_(NEW_ST), prev_state_(NEW_ST),
last_event_(NOP_EVT), next_event_(NOP_EVT), last_event_(NOP_EVT), next_event_(NOP_EVT),
on_entry_flag_(false), on_exit_flag_(false) { on_entry_flag_(false), on_exit_flag_(false),
paused_(false) {
} }
StateModel::~StateModel(){ StateModel::~StateModel(){
...@@ -177,7 +192,7 @@ StateModel::getEvent(unsigned int event_value) { ...@@ -177,7 +192,7 @@ StateModel::getEvent(unsigned int event_value) {
void void
StateModel::defineState(unsigned int state_value, const std::string& label, StateModel::defineState(unsigned int state_value, const std::string& label,
StateHandler handler) { StateHandler handler, const StatePausing& state_pausing) {
if (!isModelNew()) { if (!isModelNew()) {
// Don't allow for self-modifying maps. // Don't allow for self-modifying maps.
isc_throw(StateModelError, "States may only be added to a new model." isc_throw(StateModelError, "States may only be added to a new model."
...@@ -186,7 +201,7 @@ StateModel::defineState(unsigned int state_value, const std::string& label, ...@@ -186,7 +201,7 @@ StateModel::defineState(unsigned int state_value, const std::string& label,
// Attempt to add the state to the set. // Attempt to add the state to the set.
try { try {
states_.add(state_value, label, handler); states_.add(state_value, label, handler, state_pausing);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
isc_throw(StateModelError, "Error adding state: " << ex.what()); isc_throw(StateModelError, "Error adding state: " << ex.what());
} }
...@@ -248,6 +263,11 @@ StateModel::endModel() { ...@@ -248,6 +263,11 @@ StateModel::endModel() {
transition(END_ST, END_EVT); transition(END_ST, END_EVT);
} }
void
StateModel::unpauseModel() {
paused_ = false;
}
void void
StateModel::abortModel(const std::string& explanation) { StateModel::abortModel(const std::string& explanation) {
transition(END_ST, FAIL_EVT); transition(END_ST, FAIL_EVT);
...@@ -272,6 +292,12 @@ StateModel::setState(unsigned int state) { ...@@ -272,6 +292,12 @@ StateModel::setState(unsigned int state) {
// At this time they are calculated the same way. // At this time they are calculated the same way.
on_exit_flag_ = on_entry_flag_; 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 void
...@@ -345,6 +371,11 @@ StateModel::didModelFail() const { ...@@ -345,6 +371,11 @@ StateModel::didModelFail() const {
return (isModelDone() && (next_event_ == FAIL_EVT)); return (isModelDone() && (next_event_ == FAIL_EVT));
} }
bool
StateModel::isModelPaused() const {
return (paused_);
}
std::string std::string
StateModel::getStateLabel(const int state) const { StateModel::getStateLabel(const int state) const {
return (states_.getLabel(state)); return (states_.getLabel(state));
......
...@@ -35,12 +35,28 @@ typedef LabeledValuePtr EventPtr; ...@@ -35,12 +35,28 @@ typedef LabeledValuePtr EventPtr;
/// @brief Defines a pointer to an instance method for handling a state. /// @brief Defines a pointer to an instance method for handling a state.
typedef boost::function<void()> StateHandler; 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. /// @brief Defines a State within the State Model.
/// ///
/// This class provides the means to define a state within a set or dictionary /// 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 /// 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 /// actions. It derives from LabeledValue which allows a set of states to be
/// keyed by integer constants. /// 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 { class State : public LabeledValue {
public: public:
/// @brief Constructor /// @brief Constructor
...@@ -49,6 +65,8 @@ public: ...@@ -49,6 +65,8 @@ public:
/// @param label is the text label to assign to 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 handler is the bound instance method which handles the state's
/// action. /// 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: /// A typical invocation might look this:
/// ///
...@@ -58,7 +76,8 @@ public: ...@@ -58,7 +76,8 @@ public:
/// @endcode /// @endcode
/// ///
/// @throw StateModelError if label is null or blank. /// @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 /// @brief Destructor
virtual ~State(); virtual ~State();
...@@ -66,9 +85,25 @@ public: ...@@ -66,9 +85,25 @@ public:
/// @brief Invokes the State's handler. /// @brief Invokes the State's handler.
void run(); 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: private:
/// @brief Bound instance method pointer to the state's handler method. /// @brief Bound instance method pointer to the state's handler method.
StateHandler handler_; 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. /// @brief Defines a shared pointer to a State.
...@@ -92,10 +127,12 @@ public: ...@@ -92,10 +127,12 @@ public:
/// @param value is the numeric value of the state /// @param value is the numeric value of the state
/// @param label is the text label to assign to 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 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 /// @throw StateModelError if the value is already defined in the set, or
/// if the label is null or blank. /// 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. /// @brief Fetches a state for the given value.
/// ///
...@@ -223,6 +260,14 @@ public: ...@@ -223,6 +260,14 @@ public:
/// which transitions the model to END_ST with END_EVT. Bringing the model to /// 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 /// an abnormal end is done via the abortModel method, which transitions the
/// model to END_ST with FAILED_EVT. /// 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 { class StateModel {
public: public:
...@@ -307,6 +352,9 @@ public: ...@@ -307,6 +352,9 @@ public:
/// handler should call endModel. /// handler should call endModel.
void endModel(); void endModel();
/// @brief Unpauses state model.
void unpauseModel();
/// @brief An empty state handler. /// @brief An empty state handler.
/// ///
/// This method is primarily used to permit special states, NEW_ST and /// This method is primarily used to permit special states, NEW_ST and
...@@ -421,11 +469,14 @@ protected: ...@@ -421,11 +469,14 @@ protected:
/// exceptions. /// exceptions.
/// @param handler is the bound instance method which implements the state's /// @param handler is the bound instance method which implements the state's
/// actions. /// 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 /// @throw StateModelError if the model has already been started, if
/// the value is already defined, or if the label is empty. /// the value is already defined, or if the label is empty.
void defineState(unsigned int value, const std::string& label, 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. /// @brief Fetches the state referred to by value.
/// ///
...@@ -593,6 +644,11 @@ public: ...@@ -593,6 +644,11 @@ public:
/// @return Boolean true if the model has reached the END_ST. /// @return Boolean true if the model has reached the END_ST.
bool isModelDone() const; 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. /// @brief Returns whether or not the model failed.
/// ///
/// @return Boolean true if the model has reached the END_ST and the last /// @return Boolean true if the model has reached the END_ST and the last
...@@ -662,6 +718,9 @@ private: ...@@ -662,6 +718,9 @@ private:
/// @brief Indicates if state exit logic should be executed. /// @brief Indicates if state exit logic should be executed.
bool on_exit_flag_; bool on_exit_flag_;
/// @brief Indicates if the state model is paused.
bool paused_;
}; };
/// @brief Defines a pointer to a StateModel. /// @brief Defines a pointer to a StateModel.
......
...@@ -40,6 +40,12 @@ public: ...@@ -40,6 +40,12 @@ public:
///@brief State which finishes off processing. ///@brief State which finishes off processing.
static const int DONE_ST = SM_DERIVED_STATE_MIN + 4; 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 // StateModelTest events
///@brief Event used to trigger initiation of asynchronous work. ///@brief Event used to trigger initiation of asynchronous work.
static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1; static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1;
...@@ -56,6 +62,9 @@ public: ...@@ -56,6 +62,9 @@ public:
///@brief Event used to trigger an attempt to transition to bad state ///@brief Event used to trigger an attempt to transition to bad state
static const int SIMULATE_ERROR_EVT = SM_DERIVED_EVENT_MIN + 5; 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 /// @brief Constructor
/// ///
/// Parameters match those needed by StateModel. /// Parameters match those needed by StateModel.
...@@ -159,6 +168,11 @@ public: ...@@ -159,6 +168,11 @@ public:
} }
} }
/// @brief State handler for PAUSE_ALWAYS_ST and PAUSE_ONCE_ST.
void pauseHandler() {
postNextEvent(NOP_EVT);
}
/// @brief Construct the event dictionary. /// @brief Construct the event dictionary.
virtual void defineEvents() { virtual void defineEvents() {
// Invoke the base call implementation first. // Invoke the base call implementation first.
...@@ -170,6 +184,7 @@ public: ...@@ -170,6 +184,7 @@ public:
defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT"); defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT");
defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT"); defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT");
defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT"); defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT");
defineEvent(UNPAUSED_EVT, "UNPAUSED_EVT");
} }
/// @brief Verify the event dictionary. /// @brief Verify the event dictionary.
...@@ -183,6 +198,7 @@ public: ...@@ -183,6 +198,7 @@ public:
getEvent(ALL_DONE_EVT); getEvent(ALL_DONE_EVT);
getEvent(FORCE_UNDEFINED_ST_EVT); getEvent(FORCE_UNDEFINED_ST_EVT);
getEvent(SIMULATE_ERROR_EVT); getEvent(SIMULATE_ERROR_EVT);
getEvent(UNPAUSED_EVT);
} }
/// @brief Construct the state dictionary. /// @brief Construct the state dictionary.
...@@ -202,6 +218,14 @@ public: ...@@ -202,6 +218,14 @@ public:
defineState(DONE_ST, "DONE_ST", defineState(DONE_ST, "DONE_ST",
boost::bind(&StateModelTest::doneWorkHandler, this)); 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. /// @brief Verify the state dictionary.
...@@ -214,6 +238,8 @@ public: ...@@ -214,6 +238,8 @@ public:
getState(READY_ST); getState(READY_ST);
getState(DO_WORK_ST); getState(DO_WORK_ST);
getState(DONE_ST); getState(DONE_ST);
getState(PAUSE_ALWAYS_ST);
getState(PAUSE_ONCE_ST);
} }
/// @brief Manually construct the event and state dictionaries. /// @brief Manually construct the event and state dictionaries.
...@@ -279,6 +305,8 @@ const int StateModelTest::DONE_ST; ...@@ -279,6 +305,8 @@ const int StateModelTest::DONE_ST;
const int StateModelTest::WORK_START_EVT; const int StateModelTest::WORK_START_EVT;
const int StateModelTest::WORK_DONE_EVT; const int StateModelTest::WORK_DONE_EVT;
const int StateModelTest::ALL_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. /// @brief Checks the fundamentals of defining and retrieving events.
TEST_F(StateModelTest, eventDefinition) { TEST_F(StateModelTest, eventDefinition) {
...@@ -830,4 +858,61 @@ TEST_F(StateModelTest, stateModelTest) { ...@@ -830,4 +858,61 @@ TEST_F(StateModelTest, stateModelTest) {
EXPECT_TRUE(getWorkCompleted()); 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());
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment