Class: Roby::Task

Inherits:
PlanObject show all
Extended by:
Logger::Hierarchy, DRoby::Identifiable, DRoby::V5::Models::TaskDumper, Models::Task, TaskStateHelper
Includes:
Logger::Hierarchy, MetaRuby::DSLs::FindThroughMethodMissing, DRoby::V5::TaskDumper, ExceptionHandlingObject, GUI::GraphvizTask, GUI::RelationsCanvasTask
Defined in:
lib/roby/task.rb,
lib/roby.rb,
lib/roby/state/task.rb,
lib/roby/coordination/task_script.rb,
lib/roby/coordination/task_state_machine.rb,
lib/roby/droby/enable.rb

Overview

In a plan, Task objects represent the system’s activities.

Task models

A task model is mainly described by:

a set of named arguments, which are required to parametrize the task instance. The argument list is described using Task.argument and arguments are either set at object creation by passing an argument hash to Task.new, or by calling Task#argument explicitely.

a set of events, which are situations describing the task progression. The base Roby::Task model defines the start,success,failed and stop events. Events can be defined on the models by using Task.event:

class MyTask < Roby::Task
    event :intermediate_event
end

defines a non-controllable event, i.e. an event which can be emitted, but cannot be triggered explicitely by the system. Controllable events are defined by associating a block of code with the event, this block being responsible for making the event emitted either in the future or just now. For instance,

class MyTask < Roby::Task
    event :intermediate_event do |context|
        emit :intermediate_event
    end

    event :other_event do |context|
        execution_engine.once { emit :other_event }
    end
end

define two controllable event. In the first case, the event is immediately emitted, and in the second case it will be emitted at the beginning of the next execution cycle.

Task relations

Task relations are defined in the TaskStructure Relations::Space instance. See TaskStructure documentation for the list of special methods defined by the various graphs, and the TaskStructure namespace for the name and purpose of the various relation graphs themselves.

Executability

By default, a task is not executable, which means that no event command can be called and no event can be emitted. A task becomes executable either because Task#executable= has explicitely been called or because it has been inserted in a Plan object. Note that forcing executability with #executable= is only useful for testing. When the Roby controller manages a real systems, the executability property enforces the constraint that a task cannot be executed outside of the plan supervision.

Finally, it is possible to describe abstract task models: tasks which do represent an action, but for which the means to perform that action are still unknown. This is done by calling Task.abstract in the task definition:

class AbstTask < Roby::Task
    abstract
end

An instance of an abstract model cannot be executed, even if it is included in a plan.

Inheritance rules

On task models, a submodel can inherit from a parent model if the actions described by the parent model are also performed by the child model. For instance, a Goto(x, y) model could be subclassed into a Goto::ByFoot(x, y) model.

The following constraints apply when subclassing a task model:

  • a task subclass has at least the same events than the parent class

  • changes to event attributes are limited. The rules are:

    • a controlable event must remain controlable. Nonetheless, a non-controlable event can become a controlable one

    • a terminal event (i.e. a terminal event which ends the task execution) cannot become non-terminal. Nonetheless, a non-terminal event can become terminal.

Defined Under Namespace

Classes: InternalError

Constant Summary

Constants included from Models::Arguments

Models::Arguments::NO_DEFAULT_ARGUMENT

Deprecated Event API collapse

Instance Attribute Summary collapse

Attributes included from GUI::RelationsCanvasTask

#displayed_state

Attributes inherited from PlanObject

#addition_time, #executable, #execution_engine, #finalization_handlers, #finalization_time, #model, #plan, #promise_executor, #removed_at

Attributes included from Roby::Transaction::Proxying::Cache

#transaction_forwarder_module, #transaction_proxy_module

Attributes included from Relations::DirectedRelationSupport

#relation_graphs

Attributes inherited from DistributedObject

#local_owner_id, #owners

Deprecated Event API collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Task

abstract, all_models, causal_link, clear_model, compute_terminal_events, define_command_method, define_event_methods, define_method_unless_present, discover_terminal_events, enum_events, find_event_model, forward, from, from_state, instantiate_event_relations, interruptible, invalidate_template, model_attribute_list, model_relation, on_exception, precondition, provided_services, query, signal, template, terminates, to_coordination_task, to_execution_exception_matcher, with_arguments

Methods included from Models::Arguments

#argument, #default_argument

Methods included from TaskStateHelper

import_events_to_roby, namespace, namespace=, refine_running_state

Methods included from DRoby::Identifiable

droby_id

Methods included from DRoby::V5::Models::TaskDumper

droby_dump

Methods included from DRoby::V5::ModelDumper

#droby_dump, #droby_marshallable?

Methods included from DRoby::V5::TaskDumper

#droby_dump

Methods included from GUI::GraphvizTask

#apply_layout, #dot_label, #to_dot_events

Methods included from GUI::GraphvizPlanObject

#apply_layout, #dot_label, #to_dot

Methods included from GUI::RelationsCanvasTask

#display, #display_create, #display_name, #display_time_end, #display_time_start, #layout_events, to_svg, #update_graphics

Methods included from GUI::RelationsCanvasPlanObject

#display, #display_create, #display_events, #display_name, #display_parent

Methods included from ExceptionHandlingObject

#add_error, #pass_exception

Methods inherited from PlanObject

#apply_relation_changes, #can_finalize?, #concrete_model, #connection_space, #each_finalization_handler, #each_in_neighbour_merged, #each_out_neighbour_merged, #engine, #finalized!, #finalized?, #forget_peer, #garbage?, #merged_relations, #read_write?, #real_object, #remotely_useful?, #root_object, #root_object?, #subscribed?, #transaction_proxy?, #transaction_stack, #update_on?, #updated_by?

Methods included from Models::PlanObject

#child_plan_object, #finalization_handler

Methods included from Relations::DirectedRelationSupport

#[], #[]=, #add_parent_object, #child_object?, #child_objects, #clear_vertex, #each_child_object, #each_in_neighbour, #each_out_neighbour, #each_parent_object, #each_relation, #each_relation_graph, #each_relation_sorted, #each_root_relation_graph, #enum_child_objects, #enum_parent_objects, #enum_relations, #leaf?, #parent_object?, #parent_objects, #related_object?, #related_objects, #relation_graph_for, #relations, #remove_child_object, #remove_children, #remove_parent_object, #remove_parents, #remove_relations, #root?, #sorted_relations

Methods inherited from DistributedObject

#add_owner, #clear_owners, #owned_by?, #remove_owner

Constructor Details

#initialize(plan: TemplatePlan.new, **arguments) ⇒ Task

Create a new task object

Parameters:

  • plan (Plan) (defaults to: TemplatePlan.new)

    the plan this task should be added two. The default is to add it to its own TemplatePlan object

  • arguments (Hash<Symbol,Object>)

    assignation to task arguments



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/roby/task.rb', line 229

def initialize(plan: TemplatePlan.new, **arguments)
    @bound_events = {}
    super(plan: plan)

    @model = self.class
    @abstract = @model.abstract?

    @failed_to_start = false
    @pending = true
    @started = false
    @running = false
    @starting = false
    @finished = false
    @finishing = false
    @success = nil
    @reusable = true
    @history = []
    @coordination_objects = []

    @arguments = TaskArguments.new(self)
    assign_arguments(**arguments)
    # Now assign default values for the arguments that have not yet been
    # set
    model.arguments.each do |argname|
        next if @arguments.has_key?(argname)

        has_default, default = model.default_argument(argname)
        if has_default
            assign_argument(argname, default)
        end
    end

    @poll_handlers = []
    @execute_handlers = []

    initialize_events
    plan.register_task(self)
    template = self.model.template

    mappings = {}
    template.events_by_name.each do |name, template_event|
        mappings[template_event] = bound_events[name]
    end
    template.copy_relation_graphs_to(plan, mappings)
    apply_terminal_flags(
        template.terminal_events.map(&mappings.method(:[])),
        template.success_events.map(&mappings.method(:[])),
        template.failure_events.map(&mappings.method(:[]))
    )
    @terminal_flag_invalid = false

    if self.model.state_machine
        @state_machine = TaskStateMachine.new(self.model.state_machine)
    end
end

Instance Attribute Details

#argumentsTaskArguments (readonly)

The task arguments

Returns:



106
107
108
# File 'lib/roby/task.rb', line 106

def arguments
  @arguments
end

#bound_eventsObject (readonly)

List of EventGenerator objects bound to this task



848
849
850
# File 'lib/roby/task.rb', line 848

def bound_events
  @bound_events
end

#dataObject

The internal data for this task



1121
1122
1123
# File 'lib/roby/task.rb', line 1121

def data
  @data
end

#execute_handlersArray<InstanceHandler> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The set of instance-level execute blocks

Returns:



1147
1148
1149
# File 'lib/roby/task.rb', line 1147

def execute_handlers
  @execute_handlers
end

#failed_to_start_timeObject (readonly)

The time at which the task failed to start



804
805
806
# File 'lib/roby/task.rb', line 804

def failed_to_start_time
  @failed_to_start_time
end

#failure_eventObject (readonly)

The event that caused this task to fail. This is equivalent to taking the first emitted element of

task.event(:failed).last.task_sources

It is only much more efficient



811
812
813
# File 'lib/roby/task.rb', line 811

def failure_event
  @failure_event
end

#failure_reasonObject (readonly)

The reason for which this task failed.

It can either be an event or a LocalizedError object.

If it is an event, it is the most specialized event whose emission has been forwarded to :failed

If it is a LocalizedError object, it is the exception that caused the task failure.



801
802
803
# File 'lib/roby/task.rb', line 801

def failure_reason
  @failure_reason
end

#historyArray<Event> (readonly)

The accumulated history of this task

This is the list of events that this task ever emitted, sorted by emission time (oldest first)

Returns:



114
115
116
# File 'lib/roby/task.rb', line 114

def history
  @history
end

#poll_handlersArray<InstanceHandler> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The set of instance-level poll blocks

Returns:



1154
1155
1156
# File 'lib/roby/task.rb', line 1154

def poll_handlers
  @poll_handlers
end

#quarantine_reasonException? (readonly)

The reason why the task is in quarantine

If the quarantine was caused by an exception, this will return the original exception

Returns:



566
567
568
# File 'lib/roby/task.rb', line 566

def quarantine_reason
  @quarantine_reason
end

#state_machineObject (readonly)

Returns the value of attribute state_machine.



303
304
305
# File 'lib/roby/coordination/task_state_machine.rb', line 303

def state_machine
  @state_machine
end

#terminal_eventObject (readonly)

The most specialized event that caused this task to end



790
791
792
# File 'lib/roby/task.rb', line 790

def terminal_event
  @terminal_event
end

Class Method Details

.create_script(*task, &block) ⇒ Object



264
265
266
267
268
269
270
271
# File 'lib/roby/coordination/task_script.rb', line 264

def self.create_script(*task, &block)
    script_model = Coordination::TaskScript.new_submodel(root: self)
    script = script_model.new(*task)
    if block_given?
        script.parse(&block)
    end
    script
end

.goalObject



23
24
25
26
27
28
29
30
31
# File 'lib/roby/state/task.rb', line 23

def self.goal
    unless @goal
        if superclass.respond_to?(:goal)
            supermodel = superclass.goal
        end
        @goal = GoalModel.new(self.state, supermodel)
    end
    @goal
end

.script(&block) ⇒ Object

Adds a script that is going to be executed for every instance of this task model



275
276
277
278
279
# File 'lib/roby/coordination/task_script.rb', line 275

def self.script(&block)
    s = create_script(&block)
    scripts << s
    s
end

.stateObject



5
6
7
8
9
10
11
12
13
# File 'lib/roby/state/task.rb', line 5

def self.state
    unless @state
        if superclass.respond_to?(:state)
            supermodel = superclass.state
        end
        @state = StateModel.new(supermodel)
    end
    @state
end

Instance Method Details

#+(other) ⇒ Object

Creates a sequence where self will be started first, and task is started if self finished successfully. The returned value is an instance of Sequence.

Note that this operator always creates a new Sequence object, so

a + b + c + d

will create 3 Sequence instances. If more than two tasks should be organized in a sequence, one should instead use Sequence#<<:

Sequence.new << a << b << c << d


1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
# File 'lib/roby/task.rb', line 1704

def +(other)
    # !!!! + is NOT commutative
    if other.null?
        self
    elsif self.null?
        other
    else
        Tasks::Sequence.new << self << other
    end
end

#abstract?Object

:method:abstract?

If true, this instance is marked as abstract, i.e. as a placeholder for future actions.

By default, it takes the value of its model, i.e. through model.abstract, set by calling abstract in a task model definition as in

class MyModel < Roby::Task
  abstract
end

It can also be overriden on a per instance basis with

task.abstract = <value>


445
# File 'lib/roby/task.rb', line 445

attr_predicate :abstract?, true

#action_state_machine(&block) ⇒ Object

Create an action state machine and attach it to this task

Unlike ‘Actions::Interface#action_state_machine`, states must be defined from explicit action objects



1784
1785
1786
1787
1788
1789
# File 'lib/roby/task.rb', line 1784

def action_state_machine(&block)
    model = Coordination::ActionStateMachine
        .new_submodel(action_interface: nil, root: self.model)
    model.parse(&block)
    model.new(self)
end

#add_child_object(child, type, info) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Validates that both self and the child object are owned by the local instance



1382
1383
1384
1385
1386
1387
1388
1389
1390
# File 'lib/roby/task.rb', line 1382

def add_child_object(child, type, info)
    unless read_write? && child.read_write?
        raise OwnershipError,
              "cannot add a relation between tasks we don't own. #{self} by "\
              "#{owners.to_a} and #{child} is owned by #{child.owners.to_a}"
    end

    super
end

#add_coordination_object(object) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Declare that a coordination object is attached to this task

Parameters:



1767
1768
1769
# File 'lib/roby/task.rb', line 1767

def add_coordination_object(object)
    @coordination_objects.push(object)
end

#apply_terminal_flags(terminal_events, success_events, failure_events) ⇒ Object



719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/roby/task.rb', line 719

def apply_terminal_flags(terminal_events, success_events, failure_events)
    for ev in bound_events.each_value
        ev.terminal_flag = nil
        if terminal_events.include?(ev)
            if success_events.include?(ev)
                ev.terminal_flag = :success
            elsif failure_events.include?(ev)
                ev.terminal_flag = :failure
            else
                ev.terminal_flag = true
            end
        end
    end
end

#as_planObject



1635
1636
1637
# File 'lib/roby/task.rb', line 1635

def as_plan
    self
end

#as_servicePlanService

Returns an object that will allow to track this task’s role in the plan regardless of replacements

The returning object will point to the replacing object when self is replaced by something. In effect, it points to the task’s role in the plan instead of to the actual task itself.

Returns:



1631
1632
1633
# File 'lib/roby/task.rb', line 1631

def as_service
    @service ||= plan.find_plan_service(self) || PlanService.new(self)
end

#assign_argument(key, value) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Sets one of this task’s arguments



209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/roby/task.rb', line 209

def assign_argument(key, value)
    key = key.to_sym
    if TaskArguments.delayed_argument?(value)
        @arguments[key] = value
    else
        if self.respond_to?("#{key}=")
            self.send("#{key}=", value)
        end
        if @arguments.writable?(key, value)
            # The accessor did not write the argument. That's alright
            @arguments[key] = value
        end
    end
end

#assign_arguments(**arguments) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Helper to assign multiple argument values at once

It differs from calling assign_argument in a loop in two ways:

  • it is common for subclasses to define a high-level argument that is, in the end, propagated to lower-level arguments. This method handles the fact that, when doing this, one will get parallel assignment of the high-level and low-level values during e.g. log replay which would fail in assign_arguments since arguments are single-assignation

  • assignation is all-or-nothing



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/roby/task.rb', line 183

def assign_arguments(**arguments)
    initial_arguments = @arguments
    initial_set_arguments = initial_arguments.assigned_arguments
    current_arguments = initial_set_arguments.dup

    # First assign normal values
    arguments.each do |key, value|
        @arguments = TaskArguments.new(self)
        @arguments.merge!(initial_set_arguments)
        assign_argument(key, value)
        current_arguments.merge!(@arguments) do |k, v1, v2|
            if v1 != v2
                raise ArgumentError, "trying to override #{k}=#{v1} to #{v2}"
            end

            v1
        end
    end
    initial_arguments.merge!(current_arguments)
ensure
    @arguments = initial_arguments
end

#can_merge?(target) ⇒ Boolean

Tests if a task could be merged within self

Unlike a replacement, a merge implies that self is modified to match both its current role and the target’s role. Roby has no built-in merge logic (no merge method). This method is a helper for Roby extensions that implement such a scheme, to check for attributes common to all tasks that would forbid a merge

Returns:

  • (Boolean)


1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
# File 'lib/roby/task.rb', line 1332

def can_merge?(target)
    if defined?(super) && !super
        return false
    elsif finished? || target.finished?
        return false
    elsif !model.can_merge?(target.model)
        return false
    end

    arguments.can_semantic_merge?(target.arguments)
end

#can_replace?(target) ⇒ Boolean

True if self can be used to replace target

Returns:

  • (Boolean)


1321
1322
1323
# File 'lib/roby/task.rb', line 1321

def can_replace?(target)
    fullfills?(*target.fullfilled_model)
end

#check_emission_validity(event) ⇒ Object

This method is called by TaskEventGenerator#fire just before the event handlers and commands are called



758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# File 'lib/roby/task.rb', line 758

def check_emission_validity(event) # :nodoc:
    if finished? && !event.terminal?
        EmissionRejected.new(event).exception(
            "#{self}.emit(#{event.symbol}) called by "\
            "#{execution_engine.propagation_sources.to_a} but the task "\
            "has finished. Task has been terminated by "\
            "#{stop_event.last.sources.to_a}."
        )
    elsif pending? && event.symbol != :start
        EmissionRejected.new(event).exception(
            "#{self}.emit(#{event.symbol}) called by "\
            "#{execution_engine.propagation_sources.to_a} but the task "\
            "has never been started"
        )
    elsif running? && event.symbol == :start
        EmissionRejected.new(event).exception(
            "#{self}.emit(#{event.symbol}) called by "\
            "#{execution_engine.propagation_sources.to_a} but the task "\
            "is already running. Task has been started by "\
            "#{start_event.last.sources.to_a}."
        )
    end
end

#clear_events_external_relations(remove_strong: true) ⇒ Object

Clear relations events of this task have with events outside the task



636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/roby/task.rb', line 636

def clear_events_external_relations(remove_strong: true)
    removed = false
    task_events = bound_events.values
    each_event do |event|
        for rel in event.sorted_relations
            graph = plan.event_relation_graph_for(rel)
            next if !remove_strong && graph.strong?

            parents = graph.each_in_neighbour(event).find_all do |neighbour|
                !task_events.include?(neighbour)
            end
            children = graph.each_out_neighbour(event).find_all do |neighbour|
                !task_events.include?(neighbour)
            end

            unless remove_strong
                parents = filter_events_from_strongly_related_tasks(parents)
                children = filter_events_from_strongly_related_tasks(children)
            end

            parents.each { |from| graph.remove_edge(from, event) }
            children.each { |to| graph.remove_edge(event, to) }
            removed ||= !parents.empty? || !children.empty?
        end
    end
    removed
end

#clear_relations(remove_internal: false, remove_strong: true) ⇒ Object

Remove all relations in which self or its event are involved

Parameters:

  • remove_internal (Boolean) (defaults to: false)

    if true, remove in-task relations between events

  • remove_strong (Boolean) (defaults to: true)

    if true, remove strong relations as well



683
684
685
686
687
688
689
690
691
692
693
694
695
696
# File 'lib/roby/task.rb', line 683

def clear_relations(remove_internal: false, remove_strong: true)
    modified_plan = false
    if remove_internal
        each_event do |ev|
            if ev.clear_relations(remove_strong: remove_strong)
                modified_plan = true
            end
        end
    else
        modified_plan =
            clear_events_external_relations(remove_strong: remove_strong)
    end
    super(remove_strong: remove_strong) || modified_plan
end

#commit_transactionObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method is called during the commit process to apply changes stored in a proxy



1396
1397
1398
1399
1400
1401
1402
1403
1404
# File 'lib/roby/task.rb', line 1396

def commit_transaction
    super

    arguments.dup.each do |key, value|
        if value.respond_to?(:transaction_proxy?) && value.transaction_proxy?
            arguments.update!(key, value.__getobj__)
        end
    end
end

#compatible_state?(task) ⇒ Boolean

Checks if task is in the same execution state than self Returns true if they are either both running or both pending

Returns:

  • (Boolean)


1138
1139
1140
# File 'lib/roby/task.rb', line 1138

def compatible_state?(task)
    finished? || !(running? ^ task.running?)
end

#compute_replacement_candidates(object, filter, with_subplan) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Computes the list of edge replacements that might be necessary to perform a replacement in a transaction-aware way

At this stage, we make little difference between subplan and task replacement

Parameters:

  • with_subplan (Boolean)

    whether the subplan should be includede in edge discovery



1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
# File 'lib/roby/task.rb', line 1425

def compute_replacement_candidates(object, filter, with_subplan)
    edges, edges_candidates = [], []
    subplan_tasks = Set[self, object]
    subplan_tasks.compare_by_identity
    parent_tasks = Set.new
    parent_tasks.compare_by_identity
    plan.each_task_relation_graph do |g|
        next if g.strong? || filter.excluded_graph?(g)

        rel = g.class
        next if filter.excluded_relation?(rel)

        each_in_neighbour_merged(rel, intrusive: true) do |parent|
            parent_tasks << parent
            unless filter.excluded_task?(parent)
                edges << [g, parent, self, parent, object]
            end
        end

        if with_subplan || g.weak?
            each_out_neighbour_merged(rel, intrusive: true) do |child|
                edges_candidates << [child, [g, self, child, object, child]]
            end
        else
            object.each_out_neighbour_merged(rel, intrusive: true) do |child|
                subplan_tasks << child
            end
            each_out_neighbour_merged(rel, intrusive: true) do |child|
                subplan_tasks << child
            end
        end
    end

    transaction_stack = plan.each_object_in_transaction_stack(self).to_a
    object_transaction_stack = plan.each_object_in_transaction_stack(object).to_a
    event_pairs = []
    model.each_event do |_, event|
        event = transaction_stack
            .find { |_, o| o.find_event(event.symbol) }
            .last.event(event.symbol)
        object_event = object_transaction_stack
            .find { |_, o| o.find_event(event.symbol) }
            .last.event(event.symbol)
        event_pairs << [event, object_event]
    end

    plan.each_event_relation_graph do |g|
        next if g.strong? || filter.excluded_graph?(g)

        rel = g.class
        next if filter.excluded_relation?(rel)

        event_pairs.each do |event, object_event|
            event.each_in_neighbour_merged(rel, intrusive: false) do |_, parent|
                if parent.respond_to?(:task) &&
                   !transaction_stack.include?(parent.task)
                    edges_candidates << [
                        plan[parent.task],
                        [g, parent, event, parent, object_event]
                    ]
                end
            end
            event.each_out_neighbour_merged(rel, intrusive: false) do |_, child|
                if child.respond_to?(:task) &&
                   !transaction_stack.include?(child.task)
                    edges_candidates << [
                        plan[child.task],
                        [g, event, child, object_event, child]
                    ]
                end
            end
        end
    end

    [edges, edges_candidates, subplan_tasks, parent_tasks]
end

#compute_subplan_replacement_operation(object, filter) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
# File 'lib/roby/task.rb', line 1543

def compute_subplan_replacement_operation(object, filter)
    edges, edges_candidates, subplan_tasks, parent_tasks =
        compute_replacement_candidates(object, filter, false)
    edges_candidates.each do |reference_task, op|
        if filter.excluded_task?(reference_task)
            next
        elsif subplan_tasks.include?(reference_task)
            next
        elsif parent_tasks.include?(reference_task)
            edges << op
        elsif plan.in_useful_subplan?(self, reference_task) ||
              plan.in_useful_subplan?(object, reference_task)
            subplan_tasks << reference_task
        else
            edges << op
        end
    end
    transform_candidates_into_operations(edges)
end

#compute_task_replacement_operation(object, filter) ⇒ Object



1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
# File 'lib/roby/task.rb', line 1527

def compute_task_replacement_operation(object, filter)
    edges, edges_candidates, =
        compute_replacement_candidates(object, filter, true)
    edges_candidates.each do |reference_task, op|
        if filter.excluded_task?(reference_task)
            next
        elsif reference_task == object || reference_task == self
            next
        else
            edges << op
        end
    end
    transform_candidates_into_operations(edges)
end

#create_fresh_copyObject



389
390
391
# File 'lib/roby/task.rb', line 389

def create_fresh_copy
    model.new(**arguments.dup)
end

#create_transaction_proxy(transaction) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



1744
1745
1746
# File 'lib/roby/task.rb', line 1744

def create_transaction_proxy(transaction)
    transaction.create_and_register_proxy_task(self)
end

#current_stateSymbol

Retrieve the current state of the task

Can be one of the core states: pending, failed_to_start, starting, started, running, finishing, succeeded or failed

If the task has a state machine defined with Roby::TaskStateHelper#refine_running_state, the state machine’s current state will be returned in place of :running

Returns:

  • (Symbol)


295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/roby/task.rb', line 295

def current_state
    # Started and not finished
    # True, when task has never been started
    if pending?
        :pending
    elsif failed_to_start?
        :failed_to_start
    elsif starting?
        :starting
    # True, when terminal event is pending. Note that a finishing task
    # is running
    elsif finishing?
        :finishing
    elsif running?
        state_machine&.status_name || :running
    # Terminated with success or failure
    elsif success?
        :succeeded
    elsif failed?
        :failed
    elsif stop_event.emitted?
        :finished
    end
end

#current_state?(state) ⇒ Boolean

Test if that current state corresponds to the provided state (symbol)

Parameters:

  • state (Symbol)

Returns:

  • (Boolean)


324
325
326
# File 'lib/roby/task.rb', line 324

def current_state?(state)
    state == current_state.to_sym
end

#do_not_reusevoid

This method returns an undefined value.

Call to force the value of #reusable? to false



540
541
542
# File 'lib/roby/task.rb', line 540

def do_not_reuse
    @reusable = false
end

#do_poll(plan) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Internal method used to register the poll blocks in the engine execution cycle



1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
# File 'lib/roby/task.rb', line 1224

def do_poll(plan) # :nodoc:
    return unless self_owned?
    # Don't call if we are terminating
    return if finished?
    # Don't call if we already had an error in the poll block
    return if event(:internal_error).emitted?

    begin
        while execute_block = @execute_handlers.pop
            execute_block.block.call(self)
        end

        poll_handler

        if machine = state_machine
            machine.do_poll(self)
        end

        @poll_handlers.each do |poll_block|
            poll_block.block.call(self)
        end
    rescue LocalizedError => e
        execution_engine.add_error(e)
    rescue Exception => e
        execution_engine.add_error(CodeError.new(e, self))
    end
end

#each_coordination_object {|object| ... } ⇒ Object

Enumerate the coordination objects currently attached to this task

Yield Parameters:



1758
1759
1760
# File 'lib/roby/task.rb', line 1758

def each_coordination_object(&block)
    @coordination_objects.each(&block)
end

#each_event(only_wrapped = true) {|generator| ... } ⇒ Object Also known as: each_plan_child

Iterates on all the events defined for this task

Parameters:

  • only_wrapped (Boolean) (defaults to: true)

    For consistency with transaction proxies. Should not be used in user code.

Yields:

  • (generator)

Yield Parameters:

Returns:

  • self



968
969
970
971
972
973
974
975
# File 'lib/roby/task.rb', line 968

def each_event(only_wrapped = true)
    return enum_for(__method__, only_wrapped) unless block_given?

    for ev in bound_events.each_value
        yield(ev)
    end
    self
end

#each_exception_handler(&iterator) ⇒ Object

Lists all exception handlers attached to this task



1374
1375
1376
# File 'lib/roby/task.rb', line 1374

def each_exception_handler(&iterator)
    model.each_exception_handler(&iterator)
end

#emit(event_model, *context) ⇒ Object

Deprecated.

use EventGenerator#emit instead (e.g. task.start_event.emit)



877
878
879
880
881
882
883
884
# File 'lib/roby/task.rb', line 877

def emit(event_model, *context)
    Roby.warn_deprecated(
        "Roby::Task#emit(event_name) is deprecated, use EventGenerator#emit "\
        "(e.g. task.start_event.emit or task.event(:start).emit)"
    )
    event(event_model).emit(*context)
    self
end

#end_timeObject

Returns when this task has finished



376
377
378
379
380
# File 'lib/roby/task.rb', line 376

def end_time
    if ev = stop_event.last
        ev.time
    end
end

#ensure_poll_handler_calledObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Helper for #execute and #poll that ensures that the #do_poll is called by the execution engine



1205
1206
1207
1208
1209
1210
1211
1212
# File 'lib/roby/task.rb', line 1205

def ensure_poll_handler_called
    return if transaction_proxy? || !running?

    @poll_handler_id ||= execution_engine.add_propagation_handler(
        description: "poll block for #{self}",
        type: :external_events, &method(:do_poll)
    )
end

#event(event_model) ⇒ TaskEventGenerator?

Returns the event generator by its name or model

Parameters:

  • name (Symbol)

    the event name

Returns:

Raises:

  • (ArgumentError)

    if the event does not exist



864
865
866
867
868
869
870
871
872
# File 'lib/roby/task.rb', line 864

def event(event_model)
    unless (event = find_event(event_model))
        raise ArgumentError,
              "cannot find #{event_model} in the set of bound events in "\
              "#{self}. Known events are #{bound_events}."
    end

    event
end

#event_model(model) ⇒ Model<TaskEvent>

Accesses an event model

This method gives access to this task’s event models. If given a name, it returns the corresponding event model. If given an event model, it verifies that the model is part of the events of self and returns it.

Returns:

  • (Model<TaskEvent>)

    a subclass of Roby::TaskEvent

Raises:

  • (ArgumentError)

    if the provided event name or model does not exist on self



990
991
992
# File 'lib/roby/task.rb', line 990

def event_model(model)
    self.model.event_model(model)
end

#executable=(flag) ⇒ Object

Set the executable flag. executable cannot be set to false if the task is running, and cannot be set to true on a finished task.



466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/roby/task.rb', line 466

def executable=(flag)
    return if flag == @executable
    return unless self_owned?

    if flag && !pending?
        raise ModelViolation,
              "cannot set the executable flag of #{self} since it is not pending"
    elsif !flag && running?
        raise ModelViolation,
              "cannot unset the executable flag of #{self} since it is running"
    end

    super
end

#executable?Boolean

True if this task is executable. A task is not executable if it is abstract or partially instanciated.

Returns:

  • (Boolean)

See Also:



451
452
453
454
455
456
457
# File 'lib/roby/task.rb', line 451

def executable?
    if @executable == true
        true
    elsif @executable.nil?
        !abstract? && !partially_instanciated? && super
    end
end

#execute(options = {}, &block) ⇒ void

This method returns an undefined value.

Add a block that is going to be executed once, either at the next cycle if the task is already running, or when the task is started

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :on_replace (:copy, :drop)

    defines the behaviour when this object gets replaced in the plan. If :copy is used, the handler is added to the replacing task and is also kept in the original task. If :drop, it is not copied (but is kept).



1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
# File 'lib/roby/task.rb', line 1161

def execute(options = {}, &block)
    default_on_replace = abstract? ? :copy : :drop
    options = InstanceHandler.validate_options(
        options, on_replace: default_on_replace
    )

    check_arity(block, 1)
    @execute_handlers <<
        InstanceHandler.new(block, (options[:on_replace] == :copy))
    ensure_poll_handler_called
end

#failed_to_start!(reason, time = Time.now) ⇒ Object

Declares that this task has failed to start

#failure_reason will be set to FailedToStart with the given reason

Parameters:

  • reason (Object)

    the failure reason. Can either be an exception or an event



627
628
629
630
631
632
633
# File 'lib/roby/task.rb', line 627

def failed_to_start!(reason, time = Time.now)
    mark_failed_to_start(reason, time)
    each_event do |ev|
        ev.unreachable!(reason)
    end
    execution_engine.log(:task_failed_to_start, self, reason)
end

#failed_to_start?Boolean

Returns:

  • (Boolean)


593
594
595
# File 'lib/roby/task.rb', line 593

def failed_to_start?
    @failed_to_start
end


664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/roby/task.rb', line 664

def filter_events_from_strongly_related_tasks(events)
    return events if events.empty?

    strong_graphs = plan.each_relation_graph.find_all(&:strong?)
    events.find_all do |ev|
        next(true) unless ev.respond_to?(:task)

        task = ev.task
        strong_graphs.none? do |g|
            g.has_edge?(self, task) || g.has_edge?(task, self)
        end
    end
end

#find_event(name) ⇒ TaskEventGenerator?

Returns the event generator by its name

Parameters:

  • name (Symbol)

    the event name

Returns:



854
855
856
857
# File 'lib/roby/task.rb', line 854

def find_event(name)
    bound_events[name] ||
        bound_events[event_model(name).symbol]
end

#fired_event(event) ⇒ Object

Hook called by TaskEventGenerator#fired when one of this task’s events has been fired.



784
785
786
787
# File 'lib/roby/task.rb', line 784

def fired_event(event)
    history << event
    update_task_status(event)
end

#forcefully_terminateObject

“Simply” mark this task as terminated. This is meant to be used on quarantined tasks in tests.

Do not use this unless you really know what you are doing



1348
1349
1350
# File 'lib/roby/task.rb', line 1348

def forcefully_terminate
    update_task_status(event(:stop).new([]))
end

#forward_to(event_model, to, *to_task_events) ⇒ Object

Deprecated.

use EventGenerator#forward_to instead (e.g.

task.start_event.forward_to other_task.stop_event)



929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
# File 'lib/roby/task.rb', line 929

def forward_to(event_model, to, *to_task_events)
    Roby.warn_deprecated(
        "Task#forward_to is deprecated, use EventGenerator#forward_to "\
        "instead (e.g. #{event_model}_event.forward_to other_event)"
    )

    generator = event(event_model)
    if Hash === to_task_events.last
        delay = to_task_events.pop
    end
    to_events =
        case to
        when Task
            to_task_events.map { |ev| to.event(ev) }
        when EventGenerator
            [to]
        else
            raise ArgumentError,
                  "expected Task or EventGenerator, got #{to}(#{to.class}: "\
                  "#{to.class.ancestors})"
        end

    to_events.each do |ev|
        generator.forward_to ev, delay
    end
end

#freeze_delayed_argumentsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Evaluate delayed arguments, and replace in #arguments the ones that currently have a value



127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/roby/task.rb', line 127

def freeze_delayed_arguments
    unless arguments.static?
        result = {}
        arguments.each do |key, value|
            if TaskArguments.delayed_argument?(value)
                catch(:no_value) do
                    result[key] = value.evaluate_delayed_argument(self)
                end
            end
        end
        assign_arguments(**result)
    end
end

#fullfills?(models, args = nil) ⇒ Boolean

Whether this task instance provides a set of models and arguments

The fullfills? predicate checks if this task can be used to fullfill the need of the given model and arguments The default is to check if

* the needed task model is an ancestor of this task
* the task
* +args+ is included in the task arguments

Returns:

  • (Boolean)


1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
# File 'lib/roby/task.rb', line 1296

def fullfills?(models, args = nil)
    if models.kind_of?(Roby::Task)
        args ||= models.meaningful_arguments
        models = models.model
    end
    unless model.fullfills?(models)
        return false
    end

    args&.each do |key, name|
        if self.arguments[key] != name
            return false
        end
    end

    true
end

#fully_instanciated?Boolean

True if all arguments defined by Task.argument on the task model are either explicitely set or have a default value.

Returns:

  • (Boolean)


500
501
502
503
504
505
506
# File 'lib/roby/task.rb', line 500

def fully_instanciated?
    if arguments.static?
        @fully_instanciated ||= list_unset_arguments.empty?
    else
        list_unset_arguments.empty?
    end
end

#garbage!Object



550
551
552
553
# File 'lib/roby/task.rb', line 550

def garbage!
    bound_events.each_value(&:garbage!)
    super
end

#goalObject



33
34
35
# File 'lib/roby/state/task.rb', line 33

def goal
    @goal ||= GoalSpace.new(self.model.goal)
end

#handle_exception(e) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Handles the given exception.

In addition to the exception handlers provided by ExceptionHandlingObject, it checks for repair tasks (as defined by TaskStructure::ErrorHandling)

Parameters:



1363
1364
1365
1366
1367
1368
1369
1370
1371
# File 'lib/roby/task.rb', line 1363

def handle_exception(e)
    return unless plan

    tasks = find_all_matching_repair_tasks(e)
    return super if tasks.empty?

    tasks.first.start! if tasks.none?(&:running?)
    true
end

#has_argument?(key) ⇒ Boolean

True if this model requires an argument named key and that argument is set

Returns:

  • (Boolean)


1316
1317
1318
# File 'lib/roby/task.rb', line 1316

def has_argument?(key)
    self.arguments.set?(key)
end

#has_event?(event_model) ⇒ Boolean

True if this task has an event of the required model. The event model can either be a event class or an event name.

Returns:

  • (Boolean)


516
517
518
# File 'lib/roby/task.rb', line 516

def has_event?(event_model)
    bound_events.has_key?(event_model)
end

#initialize_copy(old) ⇒ Object

:nodoc:



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/roby/task.rb', line 393

def initialize_copy(old) # :nodoc:
    super

    @name = nil

    @arguments = TaskArguments.new(self)
    arguments.force_merge! old.arguments
    arguments.instance_variable_set(:@task, self)

    @instantiated_model_events = false

    # Create all event generators
    @bound_events = {}
    @execute_handlers = old.execute_handlers.dup
    @poll_handlers = old.poll_handlers.dup
    if m = old.instance_variable_get(:@fullfilled_model)
        @fullfilled_model = m.dup
    end
end

#initialize_replacement(task) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
# File 'lib/roby/task.rb', line 1600

def initialize_replacement(task)
    super

    execute_handlers.each do |handler|
        if handler.copy_on_replace?
            task.execute(handler.as_options, &handler.block)
        end
    end

    poll_handlers.each do |handler|
        if handler.copy_on_replace?
            task.poll(handler.as_options, &handler.block)
        end
    end
end

#inspectObject



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/roby/task.rb', line 158

def inspect
    state = if pending? then "pending"
            elsif failed_to_start? then "failed to start"
            elsif starting? then "starting"
            elsif finishing? then "finishing"
            elsif running? then "running"
            else
                "finished"
            end
    "#<#{self} executable=#{executable?} state=#{state} plan=#{plan}>"
end

#interruptible?Boolean

Returns true if this task’s stop event is controlable

Returns:

  • (Boolean)


460
461
462
# File 'lib/roby/task.rb', line 460

def interruptible?
    stop_event.controlable?
end

#invalidate_terminal_flagObject



702
703
704
# File 'lib/roby/task.rb', line 702

def invalidate_terminal_flag
    @terminal_flag_invalid = true
end

#invalidated_terminal_flag?Boolean

Returns:

  • (Boolean)


698
699
700
# File 'lib/roby/task.rb', line 698

def invalidated_terminal_flag?
    !!@terminal_flag_invalid
end

#last_eventTaskEvent?

The last event emitted by this task

Returns:



385
386
387
# File 'lib/roby/task.rb', line 385

def last_event
    history.last
end

#lifetimeObject

Returns for how many seconds this task is running. Returns nil if the task is not running.



360
361
362
363
364
365
366
# File 'lib/roby/task.rb', line 360

def lifetime
    if running?
        Time.now - start_time
    elsif finished?
        end_time - start_time
    end
end

#list_unset_argumentsObject

Lists all arguments, that are set to be needed via the :argument syntax but are not set.

This is needed for debugging purposes.



485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/roby/task.rb', line 485

def list_unset_arguments # :nodoc:
    actual_arguments =
        if arguments.static?
            arguments
        else
            arguments.evaluate_delayed_arguments
        end

    model.arguments.find_all do |name|
        !actual_arguments.has_key?(name)
    end
end

#mark_failed_to_start(reason, time) ⇒ Object



597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/roby/task.rb', line 597

def mark_failed_to_start(reason, time)
    if failed_to_start?
        return
    elsif !pending? && !starting?
        raise Roby::InternalError,
              "#{self} is neither pending nor starting, "\
              "cannot mark as failed_to_start!"
    end

    @failed_to_start = true
    @failed_to_start_time = time
    @failure_reason =
        if reason.kind_of?(LocalizedError) && reason.failed_task == self
            reason
        else
            FailedToStart.new(self, reason, time)
        end

    @pending = false
    @starting = false
    @failed   = true
    plan.task_index.set_state(self, :failed?)
end

#matchQueries::TaskMatcher

Return a task match object that matches self



1751
1752
1753
# File 'lib/roby/task.rb', line 1751

def match
    self.class.match.with_instance(self)
end

#meaningful_arguments(task_model = self.model) ⇒ Object

The part of #arguments that is meaningful for this task model. I.e. it returns the set of elements in #arguments that are listed in the task model



119
120
121
# File 'lib/roby/task.rb', line 119

def meaningful_arguments(task_model = self.model)
    task_model.meaningful_arguments(arguments)
end

#nameString

The task name

Returns:

  • (String)


144
145
146
147
148
149
150
# File 'lib/roby/task.rb', line 144

def name
    return @name if @name

    name = model.name || self.class.name
    @name = name unless frozen?
    name
end

#null?Boolean

True if this task is a null task. See NullTask.

Returns:

  • (Boolean)


1044
1045
1046
# File 'lib/roby/task.rb', line 1044

def null?
    false
end

#on(event_model, options = {}, &user_handler) ⇒ Object

Deprecated.

use Roby::TaskEventGenerator#on on the event object, e.g. task.start_event.on { |event| … }



888
889
890
891
892
893
894
895
# File 'lib/roby/task.rb', line 888

def on(event_model, options = {}, &user_handler)
    Roby.warn_deprecated(
        "Task#on is deprecated, use EventGenerator#on instead "\
        "(e.g. #{event_model}_event.signals other_event)"
    )
    event(event_model).on(options, &user_handler)
    self
end

#partially_instanciated?Boolean

True if at least one argument required by the task model is not set. See Task.argument.

Returns:

  • (Boolean)


510
511
512
# File 'lib/roby/task.rb', line 510

def partially_instanciated?
    !fully_instanciated?
end

#plan=(new_plan) ⇒ Object

:nodoc:



413
414
415
416
417
418
419
420
421
422
423
# File 'lib/roby/task.rb', line 413

def plan=(new_plan) # :nodoc:
    null = self.plan&.null_task_relation_graphs

    super

    @relation_graphs =
        plan&.task_relation_graphs || null || @relation_graphs
    for ev in bound_events.each_value
        ev.plan = plan
    end
end

#poll(options = {}) {|task| ... } ⇒ Object

Adds a new poll block on this instance

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :on_replace (:copy, :drop)

    defines the behaviour when this object gets replaced in the plan. If :copy is used, the handler is added to the replacing task and is also kept in the original task. If :drop, it is not copied (but is kept).

Yield Parameters:

  • task (Roby::Task)

    the task on which the poll block is executed. It might be different than the one on which it has been added because of replacements.

Returns:



1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
# File 'lib/roby/task.rb', line 1180

def poll(options = {}, &block)
    default_on_replace = abstract? ? :copy : :drop
    options = InstanceHandler.validate_options(
        options, on_replace: default_on_replace
    )

    check_arity(block, 1)
    handler = InstanceHandler.new(block, (options[:on_replace] == :copy))
    @poll_handlers << handler
    ensure_poll_handler_called
    Roby.disposable { @poll_handlers.delete(handler) }
end

#poll_handlerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Method under which ‘Models::Task#poll` registers its given block. Defined empty at this level to allow calling super() unconditionally



1218
# File 'lib/roby/task.rb', line 1218

def poll_handler; end

#pretty_print(pp, with_owners = true) ⇒ Object

:nodoc:



1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
# File 'lib/roby/task.rb', line 1013

def pretty_print(pp, with_owners = true) # :nodoc:
    pp.text "#{model.name}<id:#{droby_id.id}>"
    if with_owners
        pp.nest(2) do
            pp.breakable
            if owners.empty?
                pp.text "no owners"
            else
                pp.text "owners: "
                pp.nest(2) do
                    pp.seplist(owners) { |r| pp.text r.to_s }
                end
            end
        end
    end

    pp.nest(2) do
        pp.breakable
        if arguments.empty?
            pp.text "no arguments"
        else
            pp.text "arguments:"
            pp.nest(2) do
                pp.breakable
                arguments.pretty_print(pp)
            end
        end
    end
end

#promise(description: "#{self}.promise", executor: promise_executor, &block) ⇒ Promise

Create a promise that is serialized with all promises created for this object

Parameters:

  • description (String) (defaults to: "#{self}.promise")

    a textual description of the promise’s role (used for debugging and timing)

Returns:

Raises:

  • (PromiseInFinishedTask)

    if attempting to create a promise on a task that is either finished, or failed to start



346
347
348
349
350
351
352
353
354
355
356
# File 'lib/roby/task.rb', line 346

def promise(description: "#{self}.promise", executor: promise_executor, &block)
    if failed_to_start?
        raise PromiseInFinishedTask,
              "attempting to create a promise on #{self} that has failed to start"
    elsif finished?
        raise PromiseInFinishedTask,
              "attempting to create a promise on #{self} that is finished"
    end

    super
end

#quarantined!(reason: nil) ⇒ Object

Mark the task as quarantined

Quarantined tasks are essentially tasks that are present in the plan, but cannot be used because they are known to misbehave and themselves can’t be killed. The prime example is a task the system tried to stop but for which the stop process failed.

Once set it cannot be unset. The engine will generate a QuarantinedTaskError error as long as there are tasks that depend on the task, to make sure that anything that depend on it either stops using it, or is killed itself.

Parameters:

  • reason (Exception, String, nil) (defaults to: nil)

    if the quarantine was caused by an exception, pass it.there. It will be stored in #quarantine_reason and will be made available in the Quarantine error. Otherwise, pass a message that explains the quarantine



583
584
585
586
587
588
589
590
591
# File 'lib/roby/task.rb', line 583

def quarantined!(reason: nil)
    return if quarantined?

    @quarantined = true
    @quarantine_reason = reason

    fatal "#{self} entered quarantine: #{reason}"
    plan.register_quarantined_task(self)
end

#quarantined?Boolean

Whether this task has been quarantined

Returns:

  • (Boolean)


556
557
558
# File 'lib/roby/task.rb', line 556

def quarantined?
    @quarantined
end

Returns the set of events directly related to this task



747
748
749
750
751
752
753
754
# File 'lib/roby/task.rb', line 747

def related_events(result = Set.new)
    each_event do |ev|
        ev.related_events(result)
    end

    result.reject { |ev| ev.respond_to?(:task) && ev.task == self }
        .to_set
end

Returns the set of tasks directly related to this task, either because of task relations or because of task events that are related to other task events



737
738
739
740
741
742
743
744
# File 'lib/roby/task.rb', line 737

def related_tasks(result = Set.new)
    result = related_objects(nil, result)
    each_event do |ev|
        ev.related_tasks(result)
    end

    result
end

#remove_coordination_object(object) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Declare that a coordination object is no longer attached to this task

Parameters:



1776
1777
1778
# File 'lib/roby/task.rb', line 1776

def remove_coordination_object(object)
    @coordination_objects.delete(object)
end

#remove_poll_handler(handler) ⇒ void

This method returns an undefined value.

Remove a poll handler from this instance

Parameters:



1197
1198
1199
# File 'lib/roby/task.rb', line 1197

def remove_poll_handler(handler)
    handler.dispose
end

#replace_by(object, filter: Plan::ReplacementFilter::Null.new) ⇒ Object

Replaces self by object

It replaces self by object in all relations self is part of, and do the same for the task’s event generators.



1569
1570
1571
1572
1573
1574
1575
1576
1577
# File 'lib/roby/task.rb', line 1569

def replace_by(object, filter: Plan::ReplacementFilter::Null.new)
    added, removed = compute_task_replacement_operation(object, filter)
    plan.apply_replacement_operations(added, removed)

    initialize_replacement(object)
    each_event do |event|
        event.initialize_replacement(nil) { object.event(event.symbol) }
    end
end

#replace_subplan_by(object, filter: Plan::ReplacementFilter::Null.new) ⇒ Object

Replaces self’s subplan by another subplan

Replaces the subplan generated by self by the one generated by object. In practice, it means that we transfer all parent edges whose target is self from the receiver to object. It calls the various add/remove hooks defined in DirectedRelationSupport.

Relations to free events are not copied during replacement

See Also:



1589
1590
1591
1592
1593
1594
1595
1596
1597
# File 'lib/roby/task.rb', line 1589

def replace_subplan_by(object, filter: Plan::ReplacementFilter::Null.new)
    added, removed = compute_subplan_replacement_operation(object, filter)
    plan.apply_replacement_operations(added, removed)

    initialize_replacement(object)
    each_event do |event|
        event.initialize_replacement(object.event(event.symbol))
    end
end

#resolve_goalsObject



37
38
39
40
41
42
43
# File 'lib/roby/state/task.rb', line 37

def resolve_goals
    unless fully_instanciated?
        raise ArgumentError, "cannot resolve goals on a task that is not fully instanciated"
    end

    self.model.goal.resolve_goals(self, self.goal)
end

#resolve_state_sourcesObject



19
20
21
# File 'lib/roby/state/task.rb', line 19

def resolve_state_sources
    model.state.resolve_data_sources(self, state)
end

#respawnObject

Create a new task of the same model and with the same arguments than this one. Insert this task in the plan and make it replace the fresh one.

See Plan#respawn



1411
1412
1413
# File 'lib/roby/task.rb', line 1411

def respawn
    plan.respawn(self)
end

#reusable?Boolean

True if this task can be reused by some other parts in the plan

Returns:

  • (Boolean)


545
546
547
548
# File 'lib/roby/task.rb', line 545

def reusable?
    plan && @reusable && !quarantined? && !garbage? && !failed_to_start? &&
        !finished? && !finishing?
end

#running?Boolean

True if this task is currently running (i.e. is has already started, and is not finished)

Returns:

  • (Boolean)


527
528
529
# File 'lib/roby/task.rb', line 527

def running?
    started? && !finished?
end

#script(options = {}, &block) ⇒ Object

Adds a task script that is going to be executed while this task instance runs.



290
291
292
293
294
295
296
297
# File 'lib/roby/coordination/task_script.rb', line 290

def script(options = {}, &block)
    execute do |task|
        script = model.create_script(task, &block)
        script.prepare
        script.step
    end
    model.create_script(self, &block)
end

#signals(event_model, to, *to_task_events) ⇒ Object

Deprecated.

use EventGenerator#signal instead (e.g.

task.start_event.signal other_task.stop_event)



899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
# File 'lib/roby/task.rb', line 899

def signals(event_model, to, *to_task_events)
    Roby.warn_deprecated(
        "Task#signals is deprecated, use EventGenerator#signal instead "\
        "(e.g. #{event_model}_event.signals other_event)"
    )

    generator = event(event_model)
    if Hash === to_task_events.last
        delay = to_task_events.pop
    end
    to_events =
        case to
        when Task
            to_task_events.map { |ev_model| to.event(ev_model) }
        when EventGenerator
            [to]
        else
            raise ArgumentError,
                  "expected Task or EventGenerator, got #{to}(#{to.class}: "\
                  "#{to.class.ancestors})"
        end

    to_events.each do |event|
        generator.signals event, delay
    end
    self
end

#simulateObject

Deprecated.

this has no equivalent. It really has never seen proper support



1617
1618
1619
1620
1621
# File 'lib/roby/task.rb', line 1617

def simulate
    simulation_task = self.model.simulation_model.new(arguments.to_hash)
    plan.force_replace(self, simulation_task)
    simulation_task
end

#start_timeObject

Returns when this task has been started



369
370
371
372
373
# File 'lib/roby/task.rb', line 369

def start_time
    if ev = start_event.last
        ev.time
    end
end

#stateObject



15
16
17
# File 'lib/roby/state/task.rb', line 15

def state
    @state ||= StateSpace.new(self.model.state)
end

#terminal_eventsArray<TaskEventGenerator>

Returns this task’s set of terminal events.

A terminal event is an event whose emission announces the end of the task. In most case, it is an event which is forwarded directly on indirectly to stop.

Returns:



985
986
987
# File 'lib/roby/task.rb', line 985

def terminal_events
    bound_events.each_value.find_all(&:terminal?)
end

#to_execution_exceptionObject



1739
1740
1741
# File 'lib/roby/task.rb', line 1739

def to_execution_exception
    ExecutionException.new(LocalizedError.new(self))
end

#to_sObject

:nodoc:



994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
# File 'lib/roby/task.rb', line 994

def to_s # :nodoc:
    s = "#{name}<id:#{droby_id.id}>(#{arguments})".dup
    id = owners.map do |owner|
        next if plan && (owner == plan.local_owner)

        sibling = remote_siblings[owner]
        sibling_address =
            if sibling
                Object.address_from_id(sibling.ref).to_s(16)
            else
                "nil"
            end

        "#{sibling_address}@#{owner.remote_name}"
    end
    s << "[" << id.join(",") << "]" unless id.empty?
    s
end

#to_taskObject

Converts this object into a task object



1049
1050
1051
# File 'lib/roby/task.rb', line 1049

def to_task
    self
end

#transform_candidates_into_operations(edges) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The compute_ methods work on a edge set that looks like this:

[graph, [add_parent, add_child, remove_parent, remove_child]]

while Plan#apply_replacement_operations works on two sets

[[graph, add_parent, add_child, info], ...]
[[graph, remove_parent, remove_child], ...]

This transforms the first form into the second



1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
# File 'lib/roby/task.rb', line 1510

def transform_candidates_into_operations(edges)
    added, removed = [], []
    edges.each do |g, removed_parent, removed_child, added_parent, added_child|
        added_parent   = plan[added_parent]
        added_child    = plan[added_child]
        removed_parent = plan[removed_parent]
        removed_child  = plan[removed_child]
        info = g.edge_info(removed_parent, removed_child)

        added << [g, added_parent, added_child, info]
        unless g.copy_on_replace?
            removed << [g, removed_parent, removed_child]
        end
    end
    [added, removed]
end

#transition!Object



299
300
301
# File 'lib/roby/coordination/task_script.rb', line 299

def transition!
    poll_transition_event.emit
end

#update_task_status(event) ⇒ Object

Call to update the task status because of event



814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
# File 'lib/roby/task.rb', line 814

def update_task_status(event) # :nodoc:
    if event.symbol == :start
        plan.task_index.set_state(self, :running?)
        @starting = false
        @pending  = false
        @started  = true
        @running  = true
        @executable = true
    end

    if event.success?
        plan.task_index.add_state(self, :success?)
        @success = true
    elsif event.failure?
        plan.task_index.add_state(self, :failed?)
        @failed = true
        @failure_reason ||= event
        @failure_event  ||= event
    end

    @terminal_event ||= event if event.terminal?

    if event.symbol == :stop
        plan.task_index.remove_state(self, :running?)
        plan.task_index.add_state(self, :finished?)
        @running    = false
        @finishing  = false
        @finished   = true
        @executable = false
    end
    nil
end

#update_terminal_flagObject

Updates the terminal flag for all events in the task. An event is terminal if the stop event of the task will be called because this event is.



709
710
711
712
713
714
715
716
717
# File 'lib/roby/task.rb', line 709

def update_terminal_flag # :nodoc:
    return unless invalidated_terminal_flag?

    terminal_events, success_events, failure_events =
        self.model.compute_terminal_events(bound_events)
    apply_terminal_flags(terminal_events, success_events, failure_events)
    @terminal_flag_invalid = false
    [terminal_events, success_events, failure_events]
end

#updated_dataObject

This hook is called whenever the internal data of this task is updated. See #data, #data= and the updated_data event



1133
# File 'lib/roby/task.rb', line 1133

def updated_data; end

#use_fault_response_table(table_model, arguments = {}) ⇒ Object

Declares that this fault response table should be made active when this task starts, and deactivated when it ends



1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
# File 'lib/roby/task.rb', line 1276

def use_fault_response_table(table_model, arguments = {})
    arguments = table_model.validate_arguments(arguments)

    table = nil
    execute do |task|
        table = task.plan.use_fault_response_table(table_model, arguments)
    end
    stop_event.on do |event|
        plan.remove_fault_response_table(table)
    end
end

#when_finalized(options = {}, &block) ⇒ Object

Register a hook that is called when this task is finalized (removed from its plan)

Parameters:

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :on_replace (:copy, :drop)

    defines the behaviour when this object gets replaced in the plan. If :copy is used, the handler is added to the replacing task and is also kept in the original task. If :drop, it is not copied (but is kept).



1643
1644
1645
1646
1647
1648
# File 'lib/roby/task.rb', line 1643

def when_finalized(options = {}, &block)
    default = abstract? ? :copy : :drop
    options, remaining =
        InstanceHandler.filter_options options, on_replace: default
    super(options.merge(remaining), &block)
end

#|(other) ⇒ Object

Creates a parallel aggregation between self and task. Both tasks are started at the same time, and the returned instance finishes when both tasks are finished. The returned value is an instance of Parallel.

Note that this operator always creates a new Parallel object, so

a | b | c | d

will create three instances of Parallel. If more than two tasks should be organized that way, one should instead use Parallel#<<:

Parallel.new << a << b << c << d


1729
1730
1731
1732
1733
1734
1735
1736
1737
# File 'lib/roby/task.rb', line 1729

def |(other)
    if self.null?
        other
    elsif other.null?
        self
    else
        Tasks::Parallel.new << self << other
    end
end