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
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -14,8 +14,10 @@ namespace util {
/********************************** 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() {
......@@ -26,6 +28,16 @@ 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() {
......@@ -35,9 +47,11 @@ 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());
}
......@@ -74,9 +88,10 @@ 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) {
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(){
......@@ -177,7 +192,7 @@ StateModel::getEvent(unsigned int event_value) {
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."
......@@ -186,7 +201,7 @@ StateModel::defineState(unsigned int state_value, const std::string& label,
// 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());
}
......@@ -248,6 +263,11 @@ StateModel::endModel() {
transition(END_ST, END_EVT);
}
void
StateModel::unpauseModel() {
paused_ = false;
}
void
StateModel::abortModel(const std::string& explanation) {
transition(END_ST, FAIL_EVT);
......@@ -272,6 +292,12 @@ StateModel::setState(unsigned int 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_ && (getState(state)->shouldPause())) {
paused_ = true;
}
}
void
......@@ -345,6 +371,11 @@ StateModel::didModelFail() const {
return (isModelDone() && (next_event_ == FAIL_EVT));
}
bool
StateModel::isModelPaused() const {
return (paused_);
}
std::string
StateModel::getStateLabel(const int state) const {
return (states_.getLabel(state));
......
......@@ -35,12 +35,28 @@ typedef LabeledValuePtr EventPtr;
/// @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
......@@ -49,6 +65,8 @@ public:
/// @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:
///
......@@ -58,7 +76,8 @@ public:
/// @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();
......@@ -66,9 +85,25 @@ public:
/// @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.
......@@ -92,10 +127,12 @@ public:
/// @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.
///
......@@ -223,6 +260,14 @@ public:
/// 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:
......@@ -307,6 +352,9 @@ 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
......@@ -421,11 +469,14 @@ protected:
/// 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.
///
......@@ -593,6 +644,11 @@ public:
/// @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
......@@ -662,6 +718,9 @@ private:
/// @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.
......
......@@ -40,6 +40,12 @@ public:
///@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;
......@@ -56,6 +62,9 @@ public:
///@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.
......@@ -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.
virtual void defineEvents() {
// Invoke the base call implementation first.
......@@ -170,6 +184,7 @@ public:
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.
......@@ -183,6 +198,7 @@ public:
getEvent(ALL_DONE_EVT);
getEvent(FORCE_UNDEFINED_ST_EVT);
getEvent(SIMULATE_ERROR_EVT);
getEvent(UNPAUSED_EVT);
}
/// @brief Construct the state dictionary.
......@@ -202,6 +218,14 @@ public:
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.
......@@ -214,6 +238,8 @@ public:
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.
......@@ -279,6 +305,8 @@ const int StateModelTest::DONE_ST;
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) {
......@@ -830,4 +858,61 @@ TEST_F(StateModelTest, stateModelTest) {
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