From 2a727b6e3f7d67951a468882a08abbee7bae825c Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Wed, 18 Jul 2018 16:17:05 +0200 Subject: [PATCH] [5674] Implemented state model pausing. --- src/lib/util/state_model.cc | 51 ++++++++++--- src/lib/util/state_model.h | 65 ++++++++++++++++- src/lib/util/tests/state_model_unittest.cc | 85 ++++++++++++++++++++++ 3 files changed, 188 insertions(+), 13 deletions(-) diff --git a/src/lib/util/state_model.cc b/src/lib/util/state_model.cc index dc647b1e1f..38ac9d73f1 100644 --- a/src/lib/util/state_model.cc +++ b/src/lib/util/state_model.cc @@ -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)); diff --git a/src/lib/util/state_model.h b/src/lib/util/state_model.h index 938be57227..0299dbaebc 100644 --- a/src/lib/util/state_model.h +++ b/src/lib/util/state_model.h @@ -35,12 +35,28 @@ typedef LabeledValuePtr EventPtr; /// @brief Defines a pointer to an instance method for handling a state. typedef boost::function 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. diff --git a/src/lib/util/tests/state_model_unittest.cc b/src/lib/util/tests/state_model_unittest.cc index c6b2b67339..b8b2611509 100644 --- a/src/lib/util/tests/state_model_unittest.cc +++ b/src/lib/util/tests/state_model_unittest.cc @@ -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()); +} + } -- 2.47.2