The 'midlayer mistake' design pattern

I just finished reading this interesting LWN.net article about design
patterns in the Linux kernel.
http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/

This article is very relevant for the design of certain parts of the
RTT. To get your ears sharp, in a way it compares ROS 'nodes' to
Orocos 'TaskContexts'. That's because the article favours libraries
over midlayers. A library offers a set of functions which the user can
pick from, a midlayer forces the user to call (or implement) a given
set of functions, in order to relieve him from repetitive
implementation work, which is done in the midlayer itself. This forces
midlayers to be aware of every special case (a user function returns X
instead of Y) and need to be adapted each time a new requirement is
added.

To get this more concrete, ROS is a library, which allows a 'node'
builder to select and use functions from the roscpp library. The
Orocos TaskContext provides hooks the user *may* implement. The
consequence is that in ROS nodes, only the necessary functions are
called (at the right time), while in Orocos TaskContexts, every
function is called that *might* be necessary (even if the user left it
empty, or it will always return the same value etc) and in a
predefined order.

Fortunately, not all is midlayer in the RTT. Our Method/Event/Port/etc
functions are implemented as libraries and not as midlayers. Remaining
relevant suspects are the TaskContext/ExecutionEngine/Activity (hooks
and error states) and Type system (decomposing of a type etc).

The article concludes that midlayers should be as thin as possible,
and all functionality should be moved into libraries. I tend to agree
with that. If you want an example of a midlayer in the RTT gone wrong,
look at this code of the ExecutionEngine:
http://www.orocos.org/stable/documentation/rtt/v1.6.x/api/html/Execution...

It contains a bunch of if-statements and for loops, for just-in-case
cases ( startContexts() bears the palm ). EE is certainly not thin and
with every extra Hook function added to the TaskContext (a new
requirement), the complexity of the EE raises (the midlayer is
adapted).

Much food for thought, but I'm glad I found this article that
articulated what I was feeling about this code.

Peter

The 'midlayer mistake' design pattern

Peter,

That's an old question in OS community. See exo-kernels for further info.

On Wed, Jun 24, 2009 at 10:51 AM, Peter Soetens<peter [dot] soetens [..] ...> wrote:
> I just finished reading this interesting LWN.net article about design
> patterns in the Linux kernel.
> http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/
>
> This article is very relevant for the design of certain parts of the
> RTT. To get your ears sharp, in a way it compares ROS 'nodes' to
> Orocos 'TaskContexts'. That's because the article favours libraries
> over midlayers. A library offers a set of functions which the user can
> pick from, a midlayer forces the user to call (or implement) a given
> set of functions, in order to relieve him from repetitive
> implementation work, which is done in the midlayer itself. This forces
> midlayers to be aware of every special case (a user function returns X
> instead of Y) and need to be adapted each time a new requirement is
> added.
>
> To get this more concrete, ROS is a library, which allows a 'node'
> builder to select and use functions from the roscpp library. The
> Orocos TaskContext provides hooks the user *may* implement. The
> consequence is that in ROS nodes, only the necessary functions are
> called (at the right time), while in Orocos TaskContexts, every
> function is called that *might* be necessary (even if the user left it
> empty, or it will always return the same value etc) and in a
> predefined order.
>
> Fortunately, not all is midlayer in the RTT. Our Method/Event/Port/etc
> functions are implemented as libraries and not as midlayers. Remaining
> relevant suspects are the TaskContext/ExecutionEngine/Activity (hooks
> and error states) and Type system (decomposing of a type etc).
>
> The article concludes that midlayers should be as thin as possible,
> and all functionality should be moved into libraries. I tend to agree
> with that. If you want an example of a midlayer in the RTT gone wrong,
> look at this code of the ExecutionEngine:
> http://www.orocos.org/stable/documentation/rtt/v1.6.x/api/html/Execution...
>
> It contains a bunch of if-statements and for loops, for just-in-case
> cases ( startContexts() bears the palm ). EE is certainly not thin and
> with every extra Hook function added to the TaskContext (a new
> requirement), the complexity of the EE raises (the midlayer is
> adapted).
>
> Much food for thought, but I'm glad I found this article that
> articulated what I was feeling about this code.
>
> Peter
> --
> Orocos-Dev mailing list
> Orocos-Dev [..] ...
> http://lists.mech.kuleuven.be/mailman/listinfo/orocos-dev
>

The 'midlayer mistake' design pattern

On Wednesday 24 June 2009 10:51:02 Peter Soetens wrote:
> I just finished reading this interesting LWN.net article about design
> patterns in the Linux kernel.
> http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/
After having read it, I personally think that this "midlayer" term is not
relevant to RTT. I think that RTT, right now, is already a library.

The problem ExecutionEngine::startContexts() has does not come from this
midlayer-vs-library concepts but a "let's do a completely magic method"
problem. This method is supposed to relieve the caller from any knowledge
about the execution state. This not only makes the method's implementation
complex and error-prone, but also is IMO useless because some method that
would call startContexts (or TaskContext::start or TaskContext::stop) should
already be aware of the actual task context state. If it is not, it has no
business meddling with it and the result should be an error (possibly an
exception).

And, in general, magic methods are bad. They give a false sense of security.
False, because they delay when problem appear. I personally came to strongly
preferring approaches that validate prerequisites early.

On TaskContext. The purpose of the TaskContext is to provide an integration
path between the computation and the coordination (as Herman puts them). For
that, you actually need to provide a model of your computation, and that model
is called TaskContext. So, it is in my opinion not a midlayer (providing
generic functionality for a set of specific devices for instance), but a model
(structuring code so that other parts can use the now structured code in
various ways).

For the record, I think that removing that strong focus of RTT would be
killing it altogether. It is what makes tools like genom or RTT worthwile in a
context where everybody else focusses on communication libraries (like ROS).
Both approaches are complementary, and what makes RTT interesting is the
ability to integrate an already written task context into different
communication libraries in a way that is transparent to the task context
itself.

Meaning that you could take your integrated system (i.e. computation +
coordination) from CORBA and move it to ROS. And that is possible *because*
RTT requires the robot designer to describe its computation/coordination in
terms of RTT primitives through the use of TaskContext and friends.

So, to summarize, it's in my opinion not a bug, it's a feature.

The 'midlayer mistake' design pattern

On Wed, Jun 24, 2009 at 16:14, Sylvain Joyeux <sylvain [dot] joyeux [..] ...> wrote:
>
> On Wednesday 24 June 2009 10:51:02 Peter Soetens wrote:
> > I just finished reading this interesting LWN.net article about design
> > patterns in the Linux kernel.
> > http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/
> After having read it, I personally think that this "midlayer" term is not
> relevant to RTT. I think that RTT, right now, is already a library.

And still an RTT component's programming pattern is very distinct from
what ROS nodes look like. If it's not a midlayer, than at least
something else. A few years ago, a group did a classification of
robotics frameworks (was it ROSTA ?). They claimed that Orocos/RTT
(back then) wasn't really middleware, but more a framework. I'm sure
we didn't have CORBA back then, but the point is, frameworks impose a
restriction. The article claims that restrictions will always beg for
special cases, which the framework must work around, which leads to
complexity and inefficiency.

>
> The problem ExecutionEngine::startContexts() has does not come from this
> midlayer-vs-library concepts but a "let's do a completely magic method"
> problem. This method is supposed to relieve the caller from any knowledge
> about the execution state. This not only makes the method's implementation
> complex and error-prone, but also is IMO useless because some method that
> would call startContexts (or TaskContext::start or TaskContext::stop) should
> already be aware of the actual task context state. If it is not, it has no
> business meddling with it and the result should be an error (possibly an
> exception).

This may be true, but the point I wanted to make is that we could
implement the EE in two ways: the library way and the midlayer way. As
an example I pick the 'sharing an EE between multiple TaskContexs'. A
feature present today.
The library way goes like this:

// Inside user's TaskContext: (TC derives from RunnableInterface)
void step() {
   // my code...
   this->engine()->processCommands();
   this->child->step();
   // other code
   this->child->stop();
   // etc.
}

i.e. only what is necessary is done at the time it must be done, and
the user controls this. The children are 'run' by SlaveActivity
objects, they can be independently started and stopped.

The midlayer way goes like this (schematically, because it would be
already too much code to post here, '->' means 'calls'; {...} means
'executes statements' ):

'thread'->EE::step{
->processcommands;
->processevents;...;
->TC::updateHook;
->{updateHook of all child TC's}
etc
}

What is remarkable here is that the concept of 'sharing' an EE between
multiple TC's is present in both cases, but only in the 'midlayer'
case, the EE must accommodate code for it (see startContexs). In the
library case, there is no need for that (the 'child' TC is just a TC
with a SlaveActivity).

>
> And, in general, magic methods are bad. They give a false sense of security.
> False, because they delay when problem appear. I personally came to strongly
> preferring approaches that validate prerequisites early.
>
> On TaskContext. The purpose of the TaskContext is to provide an integration
> path between the computation and the coordination (as Herman puts them). For
> that, you actually need to provide a model of your computation, and that model
> is called TaskContext. So, it is in my opinion not a midlayer (providing
> generic functionality for a set of specific devices for instance), but a model
> (structuring code so that other parts can use the now structured code in
> various ways).

That's true. Maybe the problem is more in the current EE's
implementation and less in the TC as it stands today.

>
> For the record, I think that removing that strong focus of RTT would be
> killing it altogether. It is what makes tools like genom or RTT worthwile in a
> context where everybody else focusses on communication libraries (like ROS).

I couldn't agree more. I wasn't really on the path of changing the
concepts, but more on the path of how users can express these concepts
in code.

> Both approaches are complementary, and what makes RTT interesting is the
> ability to integrate an already written task context into different
> communication libraries in a way that is transparent to the task context
> itself.
>
> Meaning that you could take your integrated system (i.e. computation +
> coordination) from CORBA and move it to ROS. And that is possible *because*
> RTT requires the robot designer to describe its computation/coordination in
> terms of RTT primitives through the use of TaskContext and friends.
>
> So, to summarize, it's in my opinion not a bug, it's a feature.

I agree fully with these statements.

Peter

The 'midlayer mistake' design pattern

On Thu, Jun 25, 2009 at 01:07:41PM +0200, Peter Soetens wrote:
> On Wed, Jun 24, 2009 at 16:14, Sylvain Joyeux <sylvain [dot] joyeux [..] ...> wrote:
> >
> > On Wednesday 24 June 2009 10:51:02 Peter Soetens wrote:
> > > I just finished reading this interesting LWN.net article about design
> > > patterns in the Linux kernel.
> > > http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/
> > After having read it, I personally think that this "midlayer" term is not
> > relevant to RTT. I think that RTT, right now, is already a library.
>
> And still an RTT component's programming pattern is very distinct from
> what ROS nodes look like. If it's not a midlayer, than at least
> something else. A few years ago, a group did a classification of
> robotics frameworks (was it ROSTA ?). They claimed that Orocos/RTT
> (back then) wasn't really middleware, but more a framework. I'm sure
> we didn't have CORBA back then, but the point is, frameworks impose a
> restriction. The article claims that restrictions will always beg for
> special cases, which the framework must work around, which leads to
> complexity and inefficiency.

I don't think that comparing the RTT with the "midlayer" from the
article is very helpful, for the following reason. "midlayers" are
layers which are "sandwitched" between two other layers while the RTT
is at topmost level. I find it much more helpful to think of the RTT
as a language (an internal DSL built in C++ if you want) which
provides a set of operations from which applications can be
built. There are no special cases, because when there is something
which we can't express in this "language" it should be able to be
constructed from primitives or if necessary (should be rare) added to
the language itself.

And for (computer) languages, being restrictive is IMHO much more a
virtue than a deficiency.

If you want a real midlayer here's one ugly one: CORBA. Stuffed in as
a generic layer between the RTT and communication primitives and
failing to deal with special cases - think of rt to rt communication
via message queues or rt to nrt via xeno-pipes.

> > The problem ExecutionEngine::startContexts() has does not come from this
> > midlayer-vs-library concepts but a "let's do a completely magic method"
> > problem. This method is supposed to relieve the caller from any knowledge
> > about the execution state. This not only makes the method's implementation
> > complex and error-prone, but also is IMO useless because some method that
> > would call startContexts (or TaskContext::start or TaskContext::stop) should
> > already be aware of the actual task context state. If it is not, it has no
> > business meddling with it and the result should be an error (possibly an
> > exception).
>
> This may be true, but the point I wanted to make is that we could
> implement the EE in two ways: the library way and the midlayer way. As
> an example I pick the 'sharing an EE between multiple TaskContexs'. A
> feature present today.
> The library way goes like this:
>

> // Inside user's TaskContext: (TC derives from RunnableInterface)
> void step() {
>    // my code...
>    this->engine()->processCommands();
>    this->child->step();
>    // other code
>    this->child->stop();
>    // etc.
> }
> 

> i.e. only what is necessary is done at the time it must be done, and
> the user controls this. The children are 'run' by SlaveActivity
> objects, they can be independently started and stopped.
>
> The midlayer way goes like this (schematically, because it would be
> already too much code to post here, '->' means 'calls'; {...} means
> 'executes statements' ):
>
> 'thread'->EE::step{
> ->processcommands;
> ->processevents;...;
> ->TC::updateHook;
> ->{updateHook of all child TC's}
> etc
> }
>
> What is remarkable here is that the concept of 'sharing' an EE between
> multiple TC's is present in both cases, but only in the 'midlayer'
> case, the EE must accommodate code for it (see startContexs). In the
> library case, there is no need for that (the 'child' TC is just a TC
> with a SlaveActivity).

I'm not quite sure what you are after here... are you concerned about
the overhead of calling functions which might not be necessary? I do
agree that it might be nice to control how the execution engine
executes these steps (e.g. ordering), but this is something which
should be configured explicitely, not left to the user.

Markus

The 'midlayer mistake' design pattern

On Wed, 24 Jun 2009, Sylvain Joyeux wrote:

> On Wednesday 24 June 2009 10:51:02 Peter Soetens wrote:
>> I just finished reading this interesting LWN.net article about design
>> patterns in the Linux kernel.
>> http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/
> After having read it, I personally think that this "midlayer" term is not
> relevant to RTT. I think that RTT, right now, is already a library.
>
> The problem ExecutionEngine::startContexts() has does not come from this
> midlayer-vs-library concepts but a "let's do a completely magic method"
> problem. This method is supposed to relieve the caller from any knowledge
> about the execution state. This not only makes the method's implementation
> complex and error-prone, but also is IMO useless because some method that
> would call startContexts (or TaskContext::start or TaskContext::stop) should
> already be aware of the actual task context state. If it is not, it has no
> business meddling with it and the result should be an error (possibly an
> exception).
>
> And, in general, magic methods are bad. They give a false sense of security.
> False, because they delay when problem appear. I personally came to strongly
> preferring approaches that validate prerequisites early.

Or that only give "advice" to components to do things, and let _them_
actually do it. (Or not, if their internal state gives good reasons for
this decision.)

> On TaskContext. The purpose of the TaskContext is to provide an integration
> path between the computation and the coordination (as Herman puts them).

And Configuration _and_ Communication! It's really a "best practice"(?) in
the tradeoff between the desired design goal of "loose coupling" on the one
hand, and the "performance" and "minimal set of concepts" goal on the other
hand. (Please comment on this bold statement! :-) Anyway, you can expect
more "best practice" statements or questions from me in the near future, to
show that I am seriously working on the BRICS project: Best practices in
robotics :-))

> For
> that, you actually need to provide a model of your computation, and that model
> is called TaskContext.

I would rather call it a "container", with all the necessary(?) handles, and a
lot of "cross sectional infrastructure" code (logging, FSM, safe data
exchange, ...), but with a minimum(?) of imposed policies.

> So, it is in my opinion not a midlayer (providing
> generic functionality for a set of specific devices for instance), but a model
> (structuring code so that other parts can use the now structured code in
> various ways).
>
> For the record, I think that removing that strong focus of RTT would be
> killing it altogether.

Yes! It should even try to enforce the focus, instead of going in the
opposite direction, in my opinion.

Herman

> It is what makes tools like genom or RTT worthwile in a
> context where everybody else focusses on communication libraries (like ROS).
> Both approaches are complementary, and what makes RTT interesting is the
> ability to integrate an already written task context into different
> communication libraries in a way that is transparent to the task context
> itself.
>
> Meaning that you could take your integrated system (i.e. computation +
> coordination) from CORBA and move it to ROS. And that is possible *because*
> RTT requires the robot designer to describe its computation/coordination in
> terms of RTT primitives through the use of TaskContext and friends.
>
> So, to summarize, it's in my opinion not a bug, it's a feature.

The 'midlayer mistake' design pattern

On Jun 24, 2009, at 10:14 , Sylvain Joyeux wrote:

> On Wednesday 24 June 2009 10:51:02 Peter Soetens wrote:
>> I just finished reading this interesting LWN.net article about design
>> patterns in the Linux kernel.
>> http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/
> After having read it, I personally think that this "midlayer" term
> is not
> relevant to RTT. I think that RTT, right now, is already a library.
>
> The problem ExecutionEngine::startContexts() has does not come from
> this
> midlayer-vs-library concepts but a "let's do a completely magic
> method"
> problem. This method is supposed to relieve the caller from any
> knowledge
> about the execution state. This not only makes the method's
> implementation
> complex and error-prone, but also is IMO useless because some method
> that
> would call startContexts (or TaskContext::start or
> TaskContext::stop) should
> already be aware of the actual task context state. If it is not, it
> has no
> business meddling with it and the result should be an error
> (possibly an
> exception).
>
> And, in general, magic methods are bad. They give a false sense of
> security.
> False, because they delay when problem appear. I personally came to
> strongly
> preferring approaches that validate prerequisites early.
>
> On TaskContext. The purpose of the TaskContext is to provide an
> integration
> path between the computation and the coordination (as Herman puts
> them). For
> that, you actually need to provide a model of your computation, and
> that model
> is called TaskContext. So, it is in my opinion not a midlayer
> (providing
> generic functionality for a set of specific devices for instance),
> but a model
> (structuring code so that other parts can use the now structured
> code in
> various ways).
>
> For the record, I think that removing that strong focus of RTT would
> be
> killing it altogether. It is what makes tools like genom or RTT
> worthwile in a
> context where everybody else focusses on communication libraries
> (like ROS).
> Both approaches are complementary, and what makes RTT interesting is
> the
> ability to integrate an already written task context into different
> communication libraries in a way that is transparent to the task
> context
> itself.
>
> Meaning that you could take your integrated system (i.e. computation +
> coordination) from CORBA and move it to ROS. And that is possible
> *because*
> RTT requires the robot designer to describe its computation/
> coordination in
> terms of RTT primitives through the use of TaskContext and friends.
>
> So, to summarize, it's in my opinion not a bug, it's a feature.

I agree with much of what Sylvain says. I'm not personally sure
whether I would classify RTT as a midlayer or library, according to
that article, but I do think that RTT's ease of putting together
networks of components, and the ability to transparently make those
components run on different communications libraries is very helpful.
I find that RTT also helps structure your approach so that you *are
not* depending on particular communication library attributes.

YMMV
Stephen

The 'midlayer mistake' design pattern

On Wed, 24 Jun 2009, Peter Soetens wrote:

> I just finished reading this interesting LWN.net article about design
> patterns in the Linux kernel.
> http://lwn.net/SubscriberLink/336262/b82ca0894facd94c/

> This article is very relevant for the design of certain parts of the
> RTT. To get your ears sharp, in a way it compares ROS 'nodes' to
> Orocos 'TaskContexts'. That's because the article favours libraries
> over midlayers. A library offers a set of functions which the user can
> pick from, a midlayer forces the user to call (or implement) a given
> set of functions, in order to relieve him from repetitive
> implementation work, which is done in the midlayer itself. This forces
> midlayers to be aware of every special case (a user function returns X
> instead of Y) and need to be adapted each time a new requirement is
> added.
>
> To get this more concrete, ROS is a library, which allows a 'node'
> builder to select and use functions from the roscpp library. The
> Orocos TaskContext provides hooks the user *may* implement. The
> consequence is that in ROS nodes, only the necessary functions are
> called (at the right time), while in Orocos TaskContexts, every
> function is called that *might* be necessary (even if the user left it
> empty, or it will always return the same value etc) and in a
> predefined order.

This optimization will come from the toolchain support...

And ROS is relying on UNIX sockets, so you know that similar things are
happening there too, There are just not visible in their code.

> Fortunately, not all is midlayer in the RTT. Our Method/Event/Port/etc
> functions are implemented as libraries and not as midlayers. Remaining
> relevant suspects are the TaskContext/ExecutionEngine/Activity (hooks
> and error states) and Type system (decomposing of a type etc).
>
> The article concludes that midlayers should be as thin as possible,
> and all functionality should be moved into libraries. I tend to agree
> with that. If you want an example of a midlayer in the RTT gone wrong,
> look at this code of the ExecutionEngine:
> http://www.orocos.org/stable/documentation/rtt/v1.6.x/api/html/Execution...
>
> It contains a bunch of if-statements and for loops, for just-in-case
> cases ( startContexts() bears the palm ). EE is certainly not thin and
> with every extra Hook function added to the TaskContext (a new
> requirement), the complexity of the EE raises (the midlayer is
> adapted).

These are indeed the primary refactoring targets!

> Much food for thought, but I'm glad I found this article that
> articulated what I was feeling about this code.

Herman