My (boost.msm
) state machine appears to 'roll-back' when using signal handlers
to trigger events. However, when I use direct calls to trigger events the state
machine behaves correctly.
I looked in the boost documentation and searched the web, but it seems that all of the examples use direct calls for event triggering. I also searched SO, but couldn't find anything addressing this topic.
I'm in the process of learning the boost meta state machine library to see if it would be useful to replace the existing "home grown" state machine library currently used by my development team.
In order for this to work, I'll need to be able to trigger state machine events
from signal handlers (handling signals from boost.signals2
).
I created a simple, but contrived, example to give it a test run and was baffled when I saw that after the first event was triggered, the state machine correctly (but temporarily) changed states (while in the signal handler) but apparently 'rolled back' after returning to main.
When I bypassed the signal handlers (by using direct calls to process_event) everything worked correctly.
The, admittedly contrived, test state machine is designed to do this:
[state_a]--event_a-->[state_b]--event_b-->[state_c]--event_c-->{back-to-state_a}
I would like to know how I can make this design (or something similar) work using signal handlers to trigger state machine events correctly. Using direct calls isn't an option for me since I only receive signals to work with.
I've included the test code below. Note that the first half of the main
function exercises the signal handler triggering and the second half of main
exercises the direct call triggering
(compiled using g++ main.cpp -omain' or 'clang++ main.cpp -omain
):
#include <iostream>
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/back/tools.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
typedef boost::signals2::signal<void()> sig1_t;
//================================================================================
// ------- Sensors section
struct sensor_a {
sig1_t& get_sig() { return sig; }
void emit() { sig(); }
private:
sig1_t sig;
};
struct sensor_b {
sig1_t& get_sig() { return sig; }
void emit() { sig(); }
private:
sig1_t sig;
};
struct sensor_c {
sig1_t& get_sig() { return sig; }
void emit() { sig(); }
private:
sig1_t sig;
};
//========================================
// Sensors class
struct Sensors {
sensor_a& get_sa() {
return sa;
}
sensor_b& get_sb() {
return sb;
}
sensor_c& get_sc() {
return sc;
}
private:
sensor_a sa;
sensor_b sb;
sensor_c sc;
};
// ----- Events
struct event_a {
std::string name() const { return "event_a"; }
};
struct event_b {
std::string name() const { return "event_b"; }
};
struct event_c {
std::string name() const { return "event_c"; }
};
struct exit {
std::string name() const { return "exit"; }
};
//================================================================================
// ----- State machine section
namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;
class Controller; // forward declaration
//========================================
// testmachine class (the state machine)
struct testmachine : msmf::state_machine_def<testmachine>
{
testmachine(Controller& c) : controller(c) {}
template <class Fsm,class Event>
void no_transition(Event const& e, Fsm& ,int state) {
std::cout << "testmachine::no_transition -- No transition for event: '"
<< e.name() << "'" << " on state: " << state << std::endl;
}
//---------
struct state_a : msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const&, Fsm&) const {
std::cout << "state_a::on_entry() " << std::endl;
}
template <class Event,class Fsm>
void on_exit(Event const&, Fsm&) const {
std::cout << "state_a::on_exit()" << std::endl;
}
};
//---------
struct state_b : msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const& e, Fsm&) const {
std::cout << "state_b::on_entry() -- event: " << e.name() << std::endl;
}
template <class Event,class Fsm>
void on_exit(Event const& e, Fsm&) const {
std::cout << "state_b::on_exit() -- event: " << e.name() << std::endl;
}
};
//---------
struct state_c : msmf::state<> {
template <class Event,class Fsm>
void on_entry(Event const& e, Fsm&) const {
std::cout << "state_c::on_entry() -- event: " << e.name() << std::endl;
}
template <class Event,class Fsm>
void on_exit(Event const& e, Fsm&) const {
std::cout << "state_c::on_exit() -- event: " << e.name() << std::endl;
}
};
//---------
// Set initial state
typedef mpl::vector<state_a> initial_state;
//---------
// Transition table
struct transition_table:mpl::vector<
// Start Event Next Action Guard
msmf::Row < state_a, event_a, state_b, msmf::none, msmf::none >,
msmf::Row < state_b, event_b, state_c, msmf::none, msmf::none >,
msmf::Row < state_c, event_c, state_a, msmf::none, msmf::none >
> {};
private:
Controller& controller;
};
// state-machine back-end
typedef msm::back::state_machine<testmachine> TestMachine;
//================================================================================
// --------- controller section
namespace msm = boost::msm;
namespace mpl = boost::mpl;
// debug print helper:
std::string demangle(const std::string& mangled) {
int status;
char* c_name = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);
if(c_name){
std::string retval(c_name);
free((void*)c_name);
return retval;
}
return mangled;
}
// debug print helper (from boost msm documentation):
void pstate(TestMachine const& sm) {
typedef TestMachine::stt Stt;
typedef msm::back::generate_state_set<Stt>::type all_states;
static char const* state_names[mpl::size<all_states>::value];
mpl::for_each<all_states,boost::msm::wrap<mpl::placeholders::_1> >
(msm::back::fill_state_names<Stt>(state_names));
for (unsigned int i=0;i<TestMachine::nr_regions::value;++i){
std::cout << " -> " << demangle(state_names[sm.current_state()[i]])
<< std::endl;
}
}
//========================================
// Controller class
struct Controller {
Controller(Sensors& s) :
sensors(s),
tm(boost::ref(*this)) {
s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, *this));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, *this));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, *this));
tm.start();
}
void on_sa_event() {
std::cout << "Controller::on_sa_event function entered ++++++++" << std::endl;
current_state(__FUNCTION__);
trigger_event_a();
current_state(__FUNCTION__);
std::cout << "Controller::on_sa_event function exiting --------" << std::endl;
};
void on_sb_event() {
std::cout << "Controller::on_sb_event function entered ++++++++" << std::endl;
current_state(__FUNCTION__);
trigger_event_b();
current_state(__FUNCTION__);
std::cout << "Controller::on_sb_event function exiting --------" << std::endl;
};
void on_sc_event() {
std::cout << "Controller::on_sc_event function entered ++++++++" << std::endl;
current_state(__FUNCTION__);
trigger_event_c();
current_state(__FUNCTION__);
std::cout << "Controller::on_sc_event function exiting --------" << std::endl;
};
// debug print function
void current_state(const std::string& f) {
std::cout << "\nController::current_state ("
<< "called from function: " << f
<<")" << std::endl;
pstate(tm);
std::cout << std::endl;
}
void trigger_event_a() {
std::cout << "Controller::trigger_event_a" << std::endl;
tm.process_event(event_a());
current_state(__FUNCTION__);
}
void trigger_event_b() {
std::cout << "Controller::trigger_event_b" << std::endl;
tm.process_event(event_b());
current_state(__FUNCTION__);
}
void trigger_event_c() {
std::cout << "Controller::trigger_event_c" << std::endl;
tm.process_event(event_c());
current_state(__FUNCTION__);
}
private:
Sensors& sensors;
TestMachine tm;
};
//================================================================================
// --------- main
int main() {
Sensors sensors;
Controller controller(sensors);
std::cout << "Exercise state machine using signal handlers (fails):" << std::endl;
controller.current_state("***** main");
sensors.get_sa().emit();
controller.current_state("***** main");
sensors.get_sb().emit();
controller.current_state("***** main");
sensors.get_sc().emit();
controller.current_state("***** main");
std::cout << "\nExercise state machine using direct calls (works):" << std::endl;
controller.current_state("***** main");
controller.trigger_event_a();
controller.current_state("***** main");
controller.trigger_event_b();
controller.current_state("***** main");
controller.trigger_event_c();
controller.current_state("***** main");
}
Here is the output:
1 state_a::on_entry()
2 Exercise state machine using signal handlers (fails):
3 Controller::current_state (called from function: ***** main)
4 -> testmachine::state_a
5 Controller::on_sa_event function entered ++++++++
6 Controller::current_state (called from function: on_sa_event)
7 -> testmachine::state_a
8 Controller::trigger_event_a
9 state_a::on_exit()
10 state_b::on_entry() -- event: event_a
11 Controller::current_state (called from function: trigger_event_a)
12 -> testmachine::state_b
13 Controller::current_state (called from function: on_sa_event)
14 -> testmachine::state_b
15 Controller::on_sa_event function exiting --------
16 Controller::current_state (called from function: ***** main)
17 -> testmachine::state_a
18 Controller::on_sb_event function entered ++++++++
19 Controller::current_state (called from function: on_sb_event)
20 -> testmachine::state_a
21 Controller::trigger_event_b
22 testmachine::no_transition -- No transition for event: 'event_b' on state: 0
23 Controller::current_state (called from function: trigger_event_b)
24 -> testmachine::state_a
25 Controller::current_state (called from function: on_sb_event)
26 -> testmachine::state_a
27 Controller::on_sb_event function exiting --------
28 Controller::current_state (called from function: ***** main)
29 -> testmachine::state_a
30 Controller::on_sc_event function entered ++++++++
31 Controller::current_state (called from function: on_sc_event)
32 -> testmachine::state_a
33 Controller::trigger_event_c
34 testmachine::no_transition -- No transition for event: 'event_c' on state: 0
35 Controller::current_state (called from function: trigger_event_c)
36 -> testmachine::state_a
37 Controller::current_state (called from function: on_sc_event)
38 -> testmachine::state_a
39 Controller::on_sc_event function exiting --------
40 Controller::current_state (called from function: ***** main)
41 -> testmachine::state_a
42 Exercise state machine using direct calls (works):
43 Controller::current_state (called from function: ***** main)
44 -> testmachine::state_a
45 Controller::trigger_event_a
46 state_a::on_exit()
47 state_b::on_entry() -- event: event_a
48 Controller::current_state (called from function: trigger_event_a)
49 -> testmachine::state_b
50 Controller::current_state (called from function: ***** main)
51 -> testmachine::state_b
52 Controller::trigger_event_b
53 state_b::on_exit() -- event: event_b
54 state_c::on_entry() -- event: event_b
55 Controller::current_state (called from function: trigger_event_b)
56 -> testmachine::state_c
57 Controller::current_state (called from function: ***** main)
58 -> testmachine::state_c
59 Controller::trigger_event_c
60 state_c::on_exit() -- event: event_c
61 state_a::on_entry()
62 Controller::current_state (called from function: trigger_event_c)
63 -> testmachine::state_a
64 Controller::current_state (called from function: ***** main)
65 -> testmachine::state_a
I added line numbers by post-processing the output file for easier reference.
Line 01 of the output shows that the state machine correctly transitioned from the initial pseudo-state to state_a.
Line 14 of the output shows that the state machine correctly transitioned from state_a to state_b when inside of the on_sa_event function.
However, line 17 shows the state machine returned to state_a when tested from main (!)
The state machine remains in state_a for the remaining transitions of the signal handler tests (lines 18-41), resulting in a few 'No Transition' error messages.
For the direct call exercise (output lines 42-65), the state machine transitions correctly through all states and there is no difference in it's 'current state' from within the triggering function and when in main (after the triggering function call).
Environment: OS: "Ubuntu 16.04 LTS"
g++ version: (Ubuntu 5.3.1-14ubuntu2) 5.3.1 20160413
boost version: boost_1_60_0
The problem is caused by copying *this. See the following code. boost::bind copies *this. Each copied *this is at the initial state (state_a). That's why you experienced the rollback.
s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, *this));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, *this));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, *this));
If you copy the this pointer as follows, your code works as you expected.
s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, this));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, this));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, this));
You can also bind the reference of *this as follows:
s.get_sa().get_sig().connect(boost::bind(&Controller::on_sa_event, boost::ref(*this)));
s.get_sb().get_sig().connect(boost::bind(&Controller::on_sb_event, boost::ref(*this)));
s.get_sc().get_sig().connect(boost::bind(&Controller::on_sc_event, boost::ref(*this)));