mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-02 23:15:20 +00:00
[5674] Implemented state model pausing.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user