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



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
284
285
286
287
# File 'lib/roby/task.rb', line 233

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



862
863
864
# File 'lib/roby/task.rb', line 862

def bound_events
  @bound_events
end

#dataObject

The internal data for this task



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

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:



1157
1158
1159
# File 'lib/roby/task.rb', line 1157

def execute_handlers
  @execute_handlers
end

#failed_to_start_timeObject (readonly)

The time at which the task failed to start



818
819
820
# File 'lib/roby/task.rb', line 818

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



825
826
827
# File 'lib/roby/task.rb', line 825

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.



815
816
817
# File 'lib/roby/task.rb', line 815

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:



1164
1165
1166
# File 'lib/roby/task.rb', line 1164

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:



580
581
582
# File 'lib/roby/task.rb', line 580

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



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

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


1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
# File 'lib/roby/task.rb', line 1714

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>


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

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



1794
1795
1796
1797
1798
1799
# File 'lib/roby/task.rb', line 1794

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



1392
1393
1394
1395
1396
1397
1398
1399
1400
# File 'lib/roby/task.rb', line 1392

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:



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

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

#apply_terminal_flags(terminal_events, success_events, failure_events) ⇒ Object



733
734
735
736
737
738
739
740
741
742
743
744
745
746
# File 'lib/roby/task.rb', line 733

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



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

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:



1641
1642
1643
# File 'lib/roby/task.rb', line 1641

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



213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/roby/task.rb', line 213

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



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/roby/task.rb', line 187

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)


1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
# File 'lib/roby/task.rb', line 1342

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)


1331
1332
1333
# File 'lib/roby/task.rb', line 1331

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



772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
# File 'lib/roby/task.rb', line 772

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



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/roby/task.rb', line 650

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



697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'lib/roby/task.rb', line 697

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



1406
1407
1408
1409
1410
1411
1412
1413
1414
# File 'lib/roby/task.rb', line 1406

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)


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

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



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
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
# File 'lib/roby/task.rb', line 1435

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.



1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
# File 'lib/roby/task.rb', line 1553

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



1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
# File 'lib/roby/task.rb', line 1537

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



403
404
405
# File 'lib/roby/task.rb', line 403

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.



1754
1755
1756
# File 'lib/roby/task.rb', line 1754

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

#current_roby_task_stateSymbol

Return a symbol representing 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)


299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/roby/task.rb', line 299

def current_roby_task_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_roby_task_state?(state) ⇒ Boolean

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

Parameters:

  • state (Symbol)

Returns:

  • (Boolean)


328
329
330
# File 'lib/roby/task.rb', line 328

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

#current_stateObject

Deprecated.


333
334
335
# File 'lib/roby/task.rb', line 333

def current_state
    current_roby_task_state
end

#current_state?(state) ⇒ Boolean

Deprecated.

Returns:

  • (Boolean)


338
339
340
# File 'lib/roby/task.rb', line 338

def current_state?(state)
    current_roby_task_state?(state)
end

#do_not_reusevoid

This method returns an undefined value.

Call to force the value of #reusable? to false



554
555
556
# File 'lib/roby/task.rb', line 554

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



1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
# File 'lib/roby/task.rb', line 1234

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:



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

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



982
983
984
985
986
987
988
989
# File 'lib/roby/task.rb', line 982

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



1384
1385
1386
# File 'lib/roby/task.rb', line 1384

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)



891
892
893
894
895
896
897
898
# File 'lib/roby/task.rb', line 891

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



390
391
392
393
394
# File 'lib/roby/task.rb', line 390

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



1215
1216
1217
1218
1219
1220
1221
1222
# File 'lib/roby/task.rb', line 1215

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



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

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



1004
1005
1006
# File 'lib/roby/task.rb', line 1004

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.



480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/roby/task.rb', line 480

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:



465
466
467
468
469
470
471
# File 'lib/roby/task.rb', line 465

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



1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
# File 'lib/roby/task.rb', line 1171

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



641
642
643
644
645
646
647
# File 'lib/roby/task.rb', line 641

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)


607
608
609
# File 'lib/roby/task.rb', line 607

def failed_to_start?
    @failed_to_start
end


678
679
680
681
682
683
684
685
686
687
688
689
690
# File 'lib/roby/task.rb', line 678

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:



868
869
870
871
# File 'lib/roby/task.rb', line 868

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.



798
799
800
801
# File 'lib/roby/task.rb', line 798

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



1358
1359
1360
# File 'lib/roby/task.rb', line 1358

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)



943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
# File 'lib/roby/task.rb', line 943

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)


1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
# File 'lib/roby/task.rb', line 1306

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)


514
515
516
517
518
519
520
# File 'lib/roby/task.rb', line 514

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

#garbage!Object



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

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:



1373
1374
1375
1376
1377
1378
1379
1380
1381
# File 'lib/roby/task.rb', line 1373

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)


1326
1327
1328
# File 'lib/roby/task.rb', line 1326

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)


530
531
532
# File 'lib/roby/task.rb', line 530

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

#initialize_copy(old) ⇒ Object

:nodoc:



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/roby/task.rb', line 407

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.



1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
# File 'lib/roby/task.rb', line 1610

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



169
170
171
172
# File 'lib/roby/task.rb', line 169

def inspect
    state = task_state_to_s
    "#<#{self} executable=#{executable?} state=#{state} plan=#{plan}>"
end

#interruptible?Boolean

Returns true if this task's stop event is controlable

Returns:

  • (Boolean)


474
475
476
# File 'lib/roby/task.rb', line 474

def interruptible?
    stop_event.controlable?
end

#invalidate_terminal_flagObject



716
717
718
# File 'lib/roby/task.rb', line 716

def invalidate_terminal_flag
    @terminal_flag_invalid = true
end

#invalidated_terminal_flag?Boolean

Returns:

  • (Boolean)


712
713
714
# File 'lib/roby/task.rb', line 712

def invalidated_terminal_flag?
    !!@terminal_flag_invalid
end

#last_eventTaskEvent?

The last event emitted by this task

Returns:



399
400
401
# File 'lib/roby/task.rb', line 399

def last_event
    history.last
end

#lifetimeObject

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



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

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.



499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/roby/task.rb', line 499

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



611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
# File 'lib/roby/task.rb', line 611

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



1761
1762
1763
# File 'lib/roby/task.rb', line 1761

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)


1054
1055
1056
# File 'lib/roby/task.rb', line 1054

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| ... }



902
903
904
905
906
907
908
909
# File 'lib/roby/task.rb', line 902

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)


524
525
526
# File 'lib/roby/task.rb', line 524

def partially_instanciated?
    !fully_instanciated?
end

#plan=(new_plan) ⇒ Object

:nodoc:



427
428
429
430
431
432
433
434
435
436
437
# File 'lib/roby/task.rb', line 427

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:



1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
# File 'lib/roby/task.rb', line 1190

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



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

def poll_handler; end

#pretty_print(pp, with_owners = true) ⇒ Object

:nodoc:



1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
# File 'lib/roby/task.rb', line 1027

def pretty_print(pp, with_owners = true) # :nodoc:
    pp.text "#{model.name}<id:#{droby_id.id}> #{task_state_to_s}"
    if with_owners && !owners.empty?
        pp.nest(2) do
            pp.breakable
            pp.text "owners: "
            pp.nest(2) do
                pp.seplist(owners) { |r| pp.text r.to_s }
            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



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

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



597
598
599
600
601
602
603
604
605
# File 'lib/roby/task.rb', line 597

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)


570
571
572
# File 'lib/roby/task.rb', line 570

def quarantined?
    @quarantined
end

Returns the set of events directly related to this task



761
762
763
764
765
766
767
768
# File 'lib/roby/task.rb', line 761

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



751
752
753
754
755
756
757
758
# File 'lib/roby/task.rb', line 751

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:



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

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:



1207
1208
1209
# File 'lib/roby/task.rb', line 1207

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.



1579
1580
1581
1582
1583
1584
1585
1586
1587
# File 'lib/roby/task.rb', line 1579

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:



1599
1600
1601
1602
1603
1604
1605
1606
1607
# File 'lib/roby/task.rb', line 1599

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



1421
1422
1423
# File 'lib/roby/task.rb', line 1421

def respawn
    plan.respawn(self)
end

#reusable?Boolean

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

Returns:

  • (Boolean)


559
560
561
562
# File 'lib/roby/task.rb', line 559

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)


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

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)



913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
# File 'lib/roby/task.rb', line 913

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



1627
1628
1629
1630
1631
# File 'lib/roby/task.rb', line 1627

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



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

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

#task_state_to_sObject



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

def task_state_to_s
    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
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:



999
1000
1001
# File 'lib/roby/task.rb', line 999

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

#to_execution_exceptionObject



1749
1750
1751
# File 'lib/roby/task.rb', line 1749

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

#to_sObject

:nodoc:



1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
# File 'lib/roby/task.rb', line 1008

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



1059
1060
1061
# File 'lib/roby/task.rb', line 1059

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



1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
# File 'lib/roby/task.rb', line 1520

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



828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
# File 'lib/roby/task.rb', line 828

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.



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

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



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

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



1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
# File 'lib/roby/task.rb', line 1286

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



1653
1654
1655
1656
1657
1658
# File 'lib/roby/task.rb', line 1653

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


1739
1740
1741
1742
1743
1744
1745
1746
1747
# File 'lib/roby/task.rb', line 1739

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