mbed should provide "Active Object" library instead

23 Dec 2016

While it is good that mbed OS starts to provide support for event-driven programming, it seems to be painstakingly reinventing the wheel, which has been long known as the "Active Object" design pattern. A good article that describes the pattern is "Prefer Using Active Objects Instead of Naked Threads" by Herb Sutter (see http://www.drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/225700095 )

The mbed "event loop library" is a step in the right direction, because it decouples the event-production from event-processing (the central characteristics of the "Active Object" pattern). But unfortunately, the mbed library seems to repeat most of the mistakes that were made in the early event-driven systems and now recognized as sub-optimal.

For example, the events are "attached" to the specific callbacks. This hinders the use of state machines. You can see it easily in the event/state table representation of state machines, where an event-specific callback corresponds to the slice through the table that contains pieces of all the states. So, you have a piece of each state in every event-handler. A much better, more intuitive and efficient view of a state machine is state-centric, where the state-specific behavior is grouped together. In other words, the library should not slice the state machine, but instead should dispatch all events from a given event-queue to a "state machine processor".

And, of course, the next logical step would be to provide support for implementing state machines, preferably hierarchical state machines (a.k.a. UML statecharts)...

22 Dec 2016

Hi Quantum Leaps, very excited to see the interest in asynchronous paradigms on embedded systems.

The mbed-events library is more a part of a collection of building block than a model for developing asynchronous applications. mbed-events is a piece of a puzzle that we haven't quite figured out yet and I'm excited to see what everyone will build with all the pieces we have in mbed OS. As new programming strategies emerge I'm sure we will work to provide the tools necessary for their realization, but we do have to move cautiously, since technicaly debt is a real concern that limits our flexibility.

The article you posted is an interesting read, and we are trying to encourage a similar object-oriented approach to composing event loops. Here's what Herb Sutter's "Active Object" class might look like implemented with mbed-events:

Active Object using mbed-events

class Active {
private:
    EventQueue _queue;
    Thread _thread;
    
public:
    // Constructor spawns object-specific thread in background
    Active() {
        _thread.start(callback(&_queue, &EventQueue::dispatch_forever));
    }
    
    // Destructor waits for pending events to finish and gracefully cleans up thread
    ~Active() {
        _queue.break_dispatch();
        _thread.join();
    }
    
    // Send passes messages to thread running in the background
    void Send(Callback<void()> m) {
        _queue.call(m);
    }
}

However, there are some concerns with the base implementation of Active Objects, notably the resources used by each object to maintain its own thread. With the tools available in mbed-events, you could also create composable Active Objects that can share the execution resources of a parent event queue:

Active Object using mbed-events with shared resources

class Active {
private:
    EventQueue _queue;
    Thread *_thread;
    
public:
    // Constructor chains event queue to parent if provided, otherwise is lazily allocates a thread
    Active(Active *parent = NULL)
        : _thread(NULL) {
        if (parent) {
            _queue.chain(&parent->_queue);
        } else {
            _thread = new Thread;
            _thread->start(callback(&_queue, &EventQueue::dispatch_forever));
        }
    }
    
    // Destructor will unchain the event queue from its parent if necessary
    ~Active() {
        if (_thread) {
            _queue.break_dispatch();
            _thread->join();
            delete _thread;
        }
    }

    // Send passes messages to thread running in the background
    void Send(Callback<void()> m) {
        _queue.call(m);
    }
}

I'm not sure I understand the downside of associating events with callbacks. Callbacks were chosen as the purest form of computation in an effort to provide the most flexibility. You should be able to share functions between states by passing different context where needed. Let us know if I'm just misunterstanding the problem because I'd be interested to know what I'm missing. Here's a simple FSM written with mbed-events:

Simple FSM using mbed-events

class StopLight {
private:
    EventQueue _queue;
    int _pending;

    enum state { RED, GREEN, YELLOW };

    // The _step function is a traditional FSM which enqueues transitions onto the event queue
    void _step(state transition) {
        switch (transition) {
            case RED:
                set_red();
                _pending = _queue.call_in(1000, this, &StopLight::_step, GREEN);
                break;
                
            case GREEN:
                set_green();
                _pending = _queue.call_in(1000, this, &StopLight::_step, YELLOW);
                break;
            
            case YELLOW:
                set_yellow();
                _pending = _queue.call_in(500, this, &StopLight::_step, RED);
                break;
        }
    }
    
public:
    // The firetruck function can immediately change states in the FSM
    void firetruck() {
        _queue.cancel(_pending);
        _queue.call(this, &StopLight::_step, GREEN);
    }
}
23 Dec 2016

Christopher Haster wrote:

The mbed-events library is more a part of a collection of building block than a model for developing asynchronous applications...

Yes, mbed-events library is a step in the right direction, but it does not provide the right abstractions. For example, application-level developer still needs to build their own "Active object" class, and all other mechanisms around it. The main point here is that event-driven programming requires inversion of control (a.k.a. "Hollywood Principle" : "don't call us, we'll call you"). In other words, any event-driven library should become really a software framework with inversion of control as the defining characteristic (as defined on Wikipedia, for example).

Christopher Haster wrote:

I'm not sure I understand the downside of associating events with callbacks... Here's a simple FSM written with mbed-events:

Your StopLight state machine seems to use only one type of event (timeout coded as _queue.call_in()), which happens to be exactly the situation where you don't see the problem with "slicing" the state machine along the event dimension. But this is an oversimplified and unrealistic situation, as in any real-life case you will have many different events. For example, a more realistic traffic-light example is described in the article "UML Statecharts at $10.99" (http://www.drdobbs.com/architecture-and-design/uml-statecharts-at-1099/188101799). Please try to implement this Pedestrian LIght CONtrolled (PELICAN) crossing state machine with your method.

But anyway, instead of re-inventing the wheel, maybe you can take a look at a complete QP active object framework that has been available on mbed for many years (https://developer.mbed.org/users/QL/code/qp). This is an older version of the QP/C++ framework. A recent version is available from SourceForge and GitHub, as described in the online QP/C++ Manual (http://www.state-machine.com/qpcpp). The framework is supported also by a free graphical QM modeling tool for drawing hierarchical state machines and automatic code generation from them (http://www.state-machine.com/qm).