Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Kea
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
416
Issues
416
List
Boards
Labels
Service Desk
Milestones
Merge Requests
66
Merge Requests
66
Operations
Operations
Incidents
Packages & Registries
Packages & Registries
Container Registry
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
ISC Open Source Projects
Kea
Commits
2a727b6e
Commit
2a727b6e
authored
Jul 18, 2018
by
Marcin Siodelski
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[5674] Implemented state model pausing.
parent
88262cbd
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
188 additions
and
13 deletions
+188
-13
src/lib/util/state_model.cc
src/lib/util/state_model.cc
+41
-10
src/lib/util/state_model.h
src/lib/util/state_model.h
+62
-3
src/lib/util/tests/state_model_unittest.cc
src/lib/util/tests/state_model_unittest.cc
+85
-0
No files found.
src/lib/util/state_model.cc
View file @
2a727b6e
// Copyright (C) 2013-201
7
Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-201
8
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
));
...
...
src/lib/util/state_model.h
View file @
2a727b6e
...
...
@@ -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.
...
...
src/lib/util/tests/state_model_unittest.cc
View file @
2a727b6e
...
...
@@ -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
());
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment