Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

psoetens
Copy link
Member

As discussed in length in this forum thread: http://www.orocos.org/forum/rtt/rtt-dev/changing-behaviour-updatehook :

We have observed lately two major issues:

  1. updateHook() is called too many times (even according to
    specifications), especially when 'OwnThread' operations are implemented OR
    called(!).
    -> This was due to internal bookkeeping of the ExecutionEngine to dispatch
    the asynchronous requests. Users found it confusing that updateHook was
    called during the use of operations. This also kills performance of Lua
    state machines
    sitting in updateHook(), since they are evaluated too many times.
  2. Installing a callback for an event port caused calling that callback AND
    updateHook(), instead of calling only the callback.
    -> The callback does not replace the call to updateHook(). This is also
    related to users being surprised that an event port callback is only called
    periodically in periodic threads, while they thought it would be called
    immediately upon each event reception, and eventually updateHook
    periodically (but serialized with the callbacks).

The idea then rose to have updateHook() to only be called after:

  1. any call to TaskContext::trigger()
    -> for activities which allow this user control
  2. the component's activity triggered itself (periodic, file descriptor,
    ...)
    -> what users expect
  3. the component is being update()
    -> for slave activity behavior

I'm creating a simple patch that addresses both issues, in a backwards compatible way.

@psoetens psoetens self-assigned this Oct 21, 2014
This patch addresses issue orocos-toolchain#70 by a minor modification of TaskCore and
the ExecutionEngine. The big scheme throughout this patch are these 3
rules:

1. one uses task.setPeriod( s ) to set the period adn task.getPeriod() to
read the period

2. event port callbacks no longer schedule an updateHook(). Also completed
operation calls no longer schedule an updateHook.

3. one uses task.trigger() to schedule the execution of updateHook() or
processFunctions() in non-periodic tasks. This can be called from anywhere,
so from event port callbacks or external components.

4. one uses task.update() to directly execute an updateHook() in case of
a SlaveActivity.

Side effects:

A. For periodic components, event port callbacks will be called directly
in the thread of the component, no longer waiting for a period to expire.
This effectively decouples the reception of data on ports from updateHook(),
while still being fully thread-safe.

B. If rule 1. is followed, a task.getPeriod() will return 's', while a
task.engine()->getActivity()->getPeriod() will return '0.0'. This could be
fixed though...

C. For non-periodic components, the execution of an event port callback
will no longer schedule an updateHook(), unless task.trigger() is called
in that callback

D. For non-periodic components, script functions and updateHook() are
only called as many times as trigger() is called, with the semantics
that trigger() calls do not accumulate, but schedule at most one execution
of updateHook/processFunctions.

This patch only required a single modification in the unit tests: a
corba server had to schedule an updateHook() (effect C.) from an event port callback.

If your code does not follow convention '1.' above, the code will behave
as before for periodic components/activities (side effect A+B not present).
For non-periodic components, side effect C and D is always present.

This patch is a work in progress, and some minor follow-up patches may
follow.

Technical solution:

- We no longer let the Activity schedule a run of the ExecutionEngine,
but use the EE's condition variable to schedule periodic/non-periodic
task execution. Because of this, we can distinguish between a 'user'
task.trigger() and a 'thread/internal' ( = getActivity()->trigger()) trigger.

Signed-off-by: Peter Soetens <[email protected]>
@doudou
Copy link
Contributor

doudou commented Oct 21, 2014

What I am missing is the event port stuff. Event ports are meant to trigger, aren't they. I have to admit that I still need clarifications on the callback mechanism / semantics. I did check the RTT API documentation but very little is said there.

@psoetens
Copy link
Member Author

I agree we need another patch that updates the docs.

The new convention would be that if you want updateHook to be executed, you need to do this->trigger() at the end of your event port callback.

This gives the user the power to decide if updateHook must be called or not. See also d03c66b which should probably be relooked at in the light of this proposal.

@meyerj
Copy link
Member

meyerj commented Oct 21, 2014

What I am missing is the event port stuff. Event ports are meant to trigger, aren't they.

I also would propose that an event port without a user callback should trigger the execution of the component (trigger() is the default callback for an event port). Only if the user provides his own callback, he can decide whether to call trigger() or not. Or is it possible to do something like

ports()->addEventPort( mi1, boost::bind(&TaskContext::trigger, this) );

? I remember that boost::function supports this kind of assignment even if the PortInterface * argument is missing. Either way, there should be simple statement to reproduce the old behavior without having to implement a custom callback like in the corba_ipc_server test.

See also d03c66b which should probably be relooked at in the light of this proposal.

I agree. But your patch does not fully solve the problem reported in #61. The core question is whether user callbacks should be executed even if the component is not running or not even configured (I tend to yes). This change should also be documented and is not mentioned in the commit message of 4d45250 yet.

@meyerj
Copy link
Member

meyerj commented Oct 21, 2014

The discussion back in 2012 (http://www.orocos.org/forum/rtt/rtt-dev/changing-behaviour-updatehook) was also about removing the trigger() call from the TaskCore::start() implementation, or not to trigger an execution of the updateHook() after the startHook(). This is not directly related to this issue, but it would be a good opportunity if the TaskContext semantics change anyway.

@doudou
Copy link
Contributor

doudou commented Oct 21, 2014

I personally believe that the port callbacks should not be executed if the component is not running. The operations should, however (as they are commonly used for configuration)

Also +1 to @meyerj analysis: trigger should be the default callback. One can override it if he so chooses, but we definitely should retain current behaviour by default.

And I would definitely love us to use the opportunity to rediscuss the trigger() in Task::start() (as @meyerj suggests). I really regularly have people asking why the task is called once just after start ...

@psoetens
Copy link
Member Author

Yes... we're getting there. I forgot about the case where we trigger by default, if no callback is given. @doudou do you agree ?

I also recall that you can provide a bind function which has less arguments. The extra args are dropped then. This can probably be fixed (and documented) in the implementation of addEventPort()

I also suggested an update on d03c66b which probably solves the 'callback when running policy' easily.

@jbohren
Copy link
Member

jbohren commented Oct 21, 2014

And I would definitely love us to use the opportunity to rediscuss the trigger() in Task::start() (as @meyerj suggests). I really regularly have people asking why the task is called once just after start ...

Yeah, I was also surprised by this when I first started using RTT.

@psoetens
Copy link
Member Author

And I would definitely love us to use the opportunity to rediscuss the trigger() in Task::start() (as
@meyerj suggests). I really regularly have people asking why the task is called once just after start ...

Yeah, I was also surprised by this when I first started using RTT.

We need a broader consensus about this... since it does most likely change some components' behavior out there ?

@doudou
Copy link
Contributor

doudou commented Oct 21, 2014

Given how both sneaky such a change would be, the only way we could do it would be by having it optional at first and let people test it on their system. The only thing I can personally say is that I doubt that any Rock component needs that particular behaviour.

If no callback is attached, the EventPort triggers updateHook(), if a callback is attached,
this replaces that trigger with the new callback instead.

Signed-off-by: Peter Soetens <[email protected]>
@meyerj
Copy link
Member

meyerj commented Oct 22, 2014

The difference between the periodicity of a component/its ExecutionEngine and the activity running the ExecutionEngine has some unexpected consequences. The API allows to set the period of a component in different ways, which would have different results with this patch:

  • Non-periodic activity with a periodic component:

    RTT::TaskContext tc;    // non-period ORO_SCHED_OTHER RTT::Activity is assigned by default
    tc->setPeriod(1.0);
    

    The activity is still non-periodic and operations and port callbacks are executed asynchronously. The updateHook() is executed at a rate 1 Hz.

  • Periodic activity:

    RTT::TaskContext tc;    // non-period ORO_SCHED_OTHER RTT::Activity is assigned by default
    tc->setActivity(new RTT::Activity(ORO_SCHED_OTHER, 0, 1.0));
    

    The activity is periodic now and operations and port callbacks are delayed until the next period expired.

    This is also the API call that the deployer uses in the DeploymentComponent::setActivity() operation and the only way to assign other thread properties like the scheduler type, priority and CPU affinity.

For now I think the cleanest solution would be to revert the separate ExecutonEngine::loop() implementation and move the new trigger logic directly in the Activity and os::Thread implementation. An activity->trigger() call could wake up the thread even if the Activity is periodic and execute a new base::RunnableInterface::triggered() method. The ExecutionEngine overwrites this method and does asynchronous work in triggered() and only the synchronous tasks in step(). This would require quite some changes in the internals of the os::Thread class.

meyerj added a commit that referenced this pull request Oct 22, 2014
…t TaskContext::dataOnPortHook()

This allow the user to decide in an overwritten dataOnPortHook() whether the user callbacks should be
enqueued, indepedent of whether the component is running or not. Note that the callbacks are not executed
until the component starts unless #70 is merged.

Signed-off-by: Johannes Meyer <[email protected]>
meyerj added a commit that referenced this pull request Oct 22, 2014
…t TaskContext::dataOnPortHook()

This allows the user to decide in an overwritten dataOnPortHook() implementation whether the user callbacks
should be enqueued or not, indepedent of whether the component is running or not. Note that the callbacks are
not executed until the component is started unless #70 is merged.

See d03c66b#commitcomment-8247917.

Signed-off-by: Johannes Meyer <[email protected]>
psoetens pushed a commit to psoetens/rtt that referenced this pull request Apr 1, 2015
This patch is supposed to fix orocos-toolchain#70 in a similar, but improved way
as the original proposal.

The main introduction is the addition of RunnableInterface::work( WorkReason )
and ActivityInterface::timeout() . This allows us to inform the runnable
interface of the reason why it is being executed: because of a TimeOut,
a Trigger, or IO ready. Depending on the reason, the RunnableInterface
subclass can choose to do something different. The ExecutionEngine uses this
to distinguish between message bookkeeping (Triggers) and real work to do
in updateHook ( TimeOut and IOReady ).

In addition, when a TaskContext is Running, an EventPort will call updateHook()
upon newdata or, if a callback is attached, that callback and not updateHook.
This works for periodic and non-periodic *Activity implementations.

Unfortunately, the TaskContext::trigger() will forward to ActivityInterface::timeout()
and the ExecutionEngine will use ActivityInterface::trigger() internally to
do the message/operation bookkeeping. This is confusing in the implementation
side, but keeps the API fully backwards portable.

The implementation implements this mechanism for each Activity kind in RTT.

The side effects are minimal to none. We do call always loop() now from Thread,
and this patch does not yet detect Thread overruns (to be implemented in Activity).

Signed-off-by: Peter Soetens <[email protected]>
meyerj added a commit that referenced this pull request Apr 1, 2015
…t TaskContext::dataOnPortHook()

This allows the user to decide in an overwritten dataOnPortHook() implementation whether the user callbacks
should be enqueued or not, indepedent of whether the component is running or not. Note that the callbacks are
not executed until the component is started unless #70 is merged.

See d03c66b#commitcomment-8247917.

Signed-off-by: Johannes Meyer <[email protected]>
psoetens pushed a commit to psoetens/rtt that referenced this pull request Apr 13, 2015
This patch is supposed to fix orocos-toolchain#70 in a similar, but improved way
as the original proposal.

The main introduction is the addition of RunnableInterface::work( WorkReason )
and ActivityInterface::timeout() . This allows us to inform the runnable
interface of the reason why it is being executed: because of a TimeOut,
a Trigger, or IO ready. Depending on the reason, the RunnableInterface
subclass can choose to do something different. The ExecutionEngine uses this
to distinguish between message bookkeeping (Triggers) and real work to do
in updateHook ( TimeOut and IOReady ).

In addition, when a TaskContext is Running, an EventPort will call updateHook()
upon newdata or, if a callback is attached, that callback and not updateHook.
This works for periodic and non-periodic *Activity implementations.

Unfortunately, the TaskContext::trigger() will forward to ActivityInterface::timeout()
and the ExecutionEngine will use ActivityInterface::trigger() internally to
do the message/operation bookkeeping. This is confusing in the implementation
side, but keeps the API fully backwards portable.

The implementation implements this mechanism for each Activity kind in RTT.

The side effects are minimal to none. We do call always loop() now from Thread,
and this patch does not yet detect Thread overruns (to be implemented in Activity).

Signed-off-by: Peter Soetens <[email protected]>
@psoetens psoetens closed this Apr 30, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants