简体   繁体   中英

How do I get boost.msm to properly change state when using a signal handler to trigger events?

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)));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM