Class: Roby::Transaction

Inherits:
Plan show all
Defined in:
lib/roby/transaction.rb,
lib/roby/transaction/proxying.rb,
lib/roby/transaction/task_proxy.rb,
lib/roby/transaction/plan_object_proxy.rb,
lib/roby/transaction/plan_service_proxy.rb,
lib/roby/transaction/event_generator_proxy.rb,
lib/roby/transaction/task_event_generator_proxy.rb

Overview

In transactions, we do not manipulate plan objects like Task and EventGenerator directly, but through proxies which make sure that nothing forbidden is done

The Proxy module define base functionalities for these proxy objects

Defined Under Namespace

Modules: EventGeneratorProxy, PlanObjectProxy, PlanServiceProxy, Proxying, TaskEventGeneratorProxy, TaskProxy Classes: FrozenTransaction

Constant Summary

Constants included from GUI::RelationsCanvasPlan

GUI::RelationsCanvasPlan::PLAN_STROKE_WIDTH

Instance Attribute Summary collapse

Attributes inherited from Plan

#active_fault_response_tables, #event_logger, #event_relation_graphs, #free_events, #graph_observer, #local_owner, #null_event_relation_graphs, #null_task_relation_graphs, #plan_services, #structure_checks, #task_events, #task_index, #task_relation_graphs, #tasks, #transactions, #triggers

Attributes included from GUI::RelationsCanvasPlan

#depth, #max_depth

Attributes included from GUI::GraphvizPlan

#depth, #layout_level

Attributes inherited from DistributedObject

#local_owner_id, #owners

Instance Method Summary collapse

Methods inherited from Plan

#add_job_action, #add_mission, #add_permanent, #add_plan_service, #add_trigger, #added_transaction, #apply_replacement_operations, #apply_triggers_matches, #call_structure_check_handler, can_gc?, check_failed_missions, #check_structure, #clear!, #compute_subplan_replacement, #compute_useful_free_events, #compute_useful_tasks, #copy_relation_graphs_to, #copy_task_marks, #copy_to, #create_null_relations, #create_relations, #dedupe, #deep_copy, #deep_copy_to, #default_useful_task_graphs, #dup, #each_event_relation_graph, #each_object_in_transaction_stack, #each_relation_graph, #each_task, #each_task_relation_graph, #empty?, #event_relation_graph_for, #finalize_event, #finalize_task, #finalized_event, #finalized_task, #find_all_plan_services, #find_local_tasks, #find_plan_difference, #find_plan_service, #find_tasks, #find_triggers_matches, #force_replace, #force_replace_task, #format_exception_set, #handle_force_replace, #handle_replace, #has_free_event?, #has_task?, #has_task_event?, #in_transaction, #in_useful_subplan?, #include?, #inspect, instanciate_relation_graphs, #local_tasks, #locally_useful_roots, #locally_useful_tasks, #make_useless, #merge, #merge!, #merge_base, #merge_relation_graphs, #merge_transaction, #merge_transaction!, #merged_plan, #merging_plan, #mission?, #mission_task?, #mission_tasks, #move_plan_service, #normalize_add_arguments, #notify_event_status_change, #notify_task_status_change, #num_events, #num_free_events, #num_tasks, #owns?, #permanent?, #permanent_event?, #permanent_events, #permanent_task?, #permanent_tasks, #real_plan, #recreate, #refresh_relations, #register_event, #register_task, #registered_plan_services_for, #remote_tasks, #remove_fault_response_table, #remove_free_event!, #remove_object, #remove_plan_service, #remove_task!, #remove_transaction, #remove_trigger, #replace, #replace_relation_graphs, #replace_subplan, #replace_task, #replaced, #replan, #same_plan?, #sibling_on?, #size, #static_garbage_collect, #task_relation_graph_for, #template?, #transaction_stack, #unmark_mission, #unmark_permanent, #unneeded_events, #unneeded_tasks, #use_fault_response_table, #useful_events, #useful_task?, #useful_tasks, #validate_graphs, #verify_plan_object_finalization_sanity

Methods included from DRoby::V5::PlanDumper

#droby_dump

Methods included from DRoby::Identifiable

#droby_id, #initialize_copy

Methods included from GUI::RelationsCanvasPlan

#display, #display_create, #display_name, #display_parent

Methods included from GUI::GraphvizPlan

#all_events, #apply_layout, #compute_depth, #each_displayed_relation, #each_edge, #each_layout_relation, #layout_relations, #relations_to_dot, #to_dot

Methods included from DRoby::EventLogging

#log, #log_flush_cycle, #log_queue_size, #log_timepoint, #log_timepoint_group, #log_timepoint_group_end, #log_timepoint_group_start

Methods inherited from DistributedObject

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

Constructor Details

#initialize(plan, options = {}) ⇒ Transaction

Creates a new transaction which applies on plan

Raises:

  • (ArgumentError)


430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/roby/transaction.rb', line 430

def initialize(plan, options = {})
    raise ArgumentError, "cannot create a transaction with no plan" unless plan

    @options = options
    @frozen = false
    @disable_proxying = false
    @invalid = false

    super()

    @plan = plan

    @proxy_tasks = {}
    @proxy_events = {}
    @unmarked_mission_tasks = Set.new
    @unmarked_permanent_tasks = Set.new
    @unmarked_permanent_events = Set.new

    plan.transactions << self
    plan.added_transaction(self)
end

Instance Attribute Details

#optionsObject (readonly)

The option hash given at initialization



421
422
423
# File 'lib/roby/transaction.rb', line 421

def options
  @options
end

#planObject (readonly)

The plan this transaction applies on



415
416
417
# File 'lib/roby/transaction.rb', line 415

def plan
  @plan
end

#proxy_eventsObject (readonly)

The proxy objects built for events this transaction



419
420
421
# File 'lib/roby/transaction.rb', line 419

def proxy_events
  @proxy_events
end

#proxy_tasksObject (readonly)

The proxy objects built for tasks in this transaction



417
418
419
# File 'lib/roby/transaction.rb', line 417

def proxy_tasks
  @proxy_tasks
end

#unmarked_mission_tasksObject (readonly)

The list of missions of the underlying plan that have been unmarked in the transaction



407
408
409
# File 'lib/roby/transaction.rb', line 407

def unmarked_mission_tasks
  @unmarked_mission_tasks
end

#unmarked_permanent_eventsObject (readonly)

The list of permanent events of the underlying plan that have been unmarked in the transaction



413
414
415
# File 'lib/roby/transaction.rb', line 413

def unmarked_permanent_events
  @unmarked_permanent_events
end

#unmarked_permanent_tasksObject (readonly)

The list of permanent tasks of the underlying plan that have been unmarked in the transaction



410
411
412
# File 'lib/roby/transaction.rb', line 410

def unmarked_permanent_tasks
  @unmarked_permanent_tasks
end

Instance Method Details

#[](object, create: true) ⇒ Object



200
201
202
# File 'lib/roby/transaction.rb', line 200

def [](object, create: true)
    wrap(object, create: create)
end

#add(objects) ⇒ Object



482
483
484
485
486
487
# File 'lib/roby/transaction.rb', line 482

def add(objects)
    validate_transaction_not_frozen

    super(objects)
    self
end

#add_mission_task(t) ⇒ Object



461
462
463
464
465
466
# File 'lib/roby/transaction.rb', line 461

def add_mission_task(t)
    validate_transaction_not_frozen

    unmarked_mission_tasks.delete(t.__getobj__) if t.transaction_proxy?
    super(t)
end

#add_permanent_event(e) ⇒ Object



475
476
477
478
479
480
# File 'lib/roby/transaction.rb', line 475

def add_permanent_event(e)
    validate_transaction_not_frozen

    unmarked_permanent_events.delete(e.__getobj__) if e.transaction_proxy?
    super(e)
end

#add_permanent_task(t) ⇒ Object



468
469
470
471
472
473
# File 'lib/roby/transaction.rb', line 468

def add_permanent_task(t)
    validate_transaction_not_frozen

    unmarked_permanent_tasks.delete(t.__getobj__) if t.transaction_proxy?
    super(t)
end

#adding_plan_relation(parent, child, relations, info) ⇒ void

This method returns an undefined value.

Hook called when a relation is added between plan objects that are present in the transaction

If the new relation is not present in the transaction as well, it invalidates the transaction and calls DecisionControl#adding_plan_relation(self, parent, child, relations, info) for further action

Parameters:

  • parent (PlanObject)

    the parent object represented by its proxy in self

  • child (PlanObject)

    the child object represented by its proxy in self

  • relations (Array<Relations::Graph>)

    the graphs in which a relation has been added

  • info (Object)

    the added information for the new edges (relation specific)



974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
# File 'lib/roby/transaction.rb', line 974

def adding_plan_relation(parent, child, relations, info)
    missing_relations = relations.find_all do |rel|
        !parent.child_object?(child, rel)
    end

    unless missing_relations.empty?
        invalidate(
            "plan added a relation #{parent} -> #{child} "\
            "in #{relations} with info #{info}"
        )
        control.adding_plan_relation(self, parent, child, relations, info)
    end

    nil
end

#apply_graph_modifications(work_graphs, added, removed, updated) ⇒ 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.

Apply the graph modifications returned by #compute_graph_modifications_for



586
587
588
589
590
591
592
593
594
595
596
# File 'lib/roby/transaction.rb', line 586

def apply_graph_modifications(work_graphs, added, removed, updated)
    added.each do |graph, parent, child, info|
        work_graphs[graph].add_edge(parent, child, info)
    end
    removed.each do |graph, parent, child|
        work_graphs[graph].remove_edge(parent, child)
    end
    updated.each do |graph, parent, child, info|
        work_graphs[graph].set_edge_info(parent, child, info)
    end
end

#apply_modifications_to_planObject

Apply the modifications represented by self to the underlying plan snippet in your redefinition if you do so.



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
# File 'lib/roby/transaction.rb', line 684

def apply_modifications_to_plan
    new_mission_tasks    = Set.new
    new_permanent_tasks  = Set.new
    new_permanent_events = Set.new

    added_relations   = []
    removed_relations = []
    updated_relations = []

    # We're doing a lot of modifications of this plan .. store some of
    # the sets we need for later, one part to keep them unchanged, one
    # part to make sure we don't do modify-while-iterate
    proxy_tasks = self.proxy_tasks.dup
    proxy_events = self.proxy_events.dup
    plan_services = self.plan_services.dup
    unmarked_mission_tasks = self.unmarked_mission_tasks.dup
    unmarked_permanent_tasks = self.unmarked_permanent_tasks.dup
    unmarked_permanent_events = self.unmarked_permanent_events.dup
    # We're taking care of the proxies first, so that we can merge the
    # transaction using Plan#merge!. However, this means that
    # #may_unwrap does not work after the first few steps. We therefore
    # have to store the object-to-proxy mapping
    real_objects = {}

    # We make a copy of all relation graphs, and update them with the
    # transaction data. The underlying plan graphs are not modified
    #
    # We do not #dup them because we don't want to dup the edge info.
    # Instead, we instanciate anew and merge. The add_vertex calls are
    # needed to make sure that the graph dups the in/out sets instead
    # of just copying them
    task_work_graphs, event_work_graphs =
        plan.class.instanciate_relation_graphs
    work_graphs = {}
    transaction_graphs = {}
    plan.each_task_relation_graph do |g|
        work_g = work_graphs[g] = task_work_graphs[g.class]
        g.each_vertex { |v| work_g.add_vertex(v) }
        work_g.merge(g)
        transaction_graphs[g] = task_relation_graph_for(g.class)
    end
    plan.each_event_relation_graph do |g|
        work_g = work_graphs[g] = event_work_graphs[g.class]
        g.each_vertex { |v| work_g.add_vertex(v) }
        work_g.merge(g)
        transaction_graphs[g] = event_relation_graph_for(g.class)
    end

    # First apply all changes related to the proxies to the underlying
    # plan. This adds some new tasks to the plan graph, but does not add
    # them to the plan itself
    #
    # Note that we need to do that in two passes. The first one keeps
    # the transaction unchanged, the second one removes the proxies from
    # the transaction. This is needed so that #commit_transaction sees
    # the graph unchanged
    proxy_objects = proxy_tasks.merge(proxy_events)

    proxy_objects.each do |object, proxy|
        real_objects[proxy] = object
        compute_graph_modifications_for(
            proxy, added_relations, removed_relations, updated_relations
        )
        proxy.commit_transaction
    end
    proxy_tasks.dup.each do |object, proxy|
        if proxy.self_owned?
            if mission_task?(proxy)
                new_mission_tasks << object
            elsif permanent_task?(proxy)
                new_permanent_tasks << object
            end
        end
        remove_task(proxy)
    end
    proxy_events.dup.each do |object, proxy|
        if proxy.root_object?
            new_permanent_events << object if permanent_event?(proxy)
            remove_free_event(proxy)
        end
    end

    work_graphs.each do |plan_g, work_g|
        work_g.merge(transaction_graphs[plan_g])
    end
    apply_graph_modifications(
        work_graphs, added_relations, removed_relations, updated_relations
    )

    begin
        validate_graphs(work_graphs.values)
    rescue StandardError => e
        raise e, "cannot apply #{self}: #{e.message}", e.backtrace
    end

    #### UNTIL THIS POINT we have not modified the underlying plan AT ALL
    # We DID update the transaction, though

    # Apply #commit_transaction on the remaining tasks
    tasks.each(&:commit_transaction)
    free_events.each(&:commit_transaction)

    # What is left in the transaction is the network of new tasks. Just
    # merge it
    plan.merge_transaction!(
        self, work_graphs,
        added_relations, removed_relations, updated_relations
    )

    # Update the plan services on the underlying plan. The only
    # thing we need to take care of is replacements and new
    # services. Other modifications will be applied automatically
    plan_services.each do |task, services|
        services.each do |srv|
            if srv.transaction_proxy?
                # Modified service. Might be moved to a new task
                original = srv.__getobj__
                # Do NOT use may_unwrap here ... See comments at the top
                # of the method
                task     = real_objects[task] || task
                srv.commit_transaction
                plan.move_plan_service(original, task) if original.task != task
            elsif task.transaction_proxy?
                # New service on an already existing task
                srv.task = task.__getobj__
                plan.add_plan_service(srv)
            else
                # New service on a new task
                plan.add_plan_service(srv)
            end
        end
    end

    new_mission_tasks.each { |t| plan.add_mission_task(t) }
    new_permanent_tasks.each { |t| plan.add_permanent_task(t) }
    new_permanent_events.each { |ev| plan.add_permanent_event(ev) }

    active_fault_response_tables.each do |tbl|
        plan.use_fault_response_table tbl.model, tbl.arguments
    end

    unmarked_permanent_events.each { |t| plan.unmark_permanent_event(t) }
    unmarked_permanent_tasks.each { |t| plan.unmark_permanent_task(t) }
    unmarked_mission_tasks.each { |t| plan.unmark_mission_task(t) }

    proxy_objects.each do |object, proxy|
        forwarder_module =
            Transaction::Proxying.forwarder_module_for(object.model)
        proxy.extend forwarder_module
        proxy.__getobj__ = object
        proxy.__freeze__
    end
end

#apply_triggers_on_committed_transaction(triggered_matches) ⇒ 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.

Apply the triggers as returned by #compute_triggers_for_committed_transaction



676
677
678
679
680
# File 'lib/roby/transaction.rb', line 676

def apply_triggers_on_committed_transaction(triggered_matches)
    triggered_matches.each do |task, trigger|
        trigger.call(task)
    end
end

#check_valid_transactionvoid

This method returns an undefined value.

Tests if it is safe to commit this transaction

Raises:



569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/roby/transaction.rb', line 569

def check_valid_transaction
    return if valid_transaction?

    unless transactions.empty?
        raise InvalidTransaction, "there is still transactions on top of this one"
    end

    message = invalidation_reasons.map do |reason, trace|
        "#{trace[0]}: #{reason}\n  #{trace[1..-1].join("\n  ")}"
    end.join("\n")
    raise InvalidTransaction, "invalid transaction: #{message}"
end

#clearvoid

This method returns an undefined value.

Clears this transaction

A cleared transaction behaves as a new transaction on the same plan



917
918
919
920
921
922
923
924
925
926
# File 'lib/roby/transaction.rb', line 917

def clear
    unmarked_mission_tasks.clear
    unmarked_permanent_tasks.clear
    unmarked_permanent_events.clear
    proxy_tasks.each_value(&:clear_relations)
    proxy_tasks.clear
    proxy_events.each_value(&:clear_relations)
    proxy_events.clear
    super
end

#commit_transactionObject

Commit all modifications that have been registered in this transaction



840
841
842
843
844
845
846
847
848
849
850
851
852
853
# File 'lib/roby/transaction.rb', line 840

def commit_transaction
    check_valid_transaction
    trigger_matches = compute_triggers_for_committed_transaction
    apply_modifications_to_plan
    apply_triggers_on_committed_transaction(trigger_matches)
    frozen!

    @committed = true
    committed_transaction
    plan.remove_transaction(self)
    @plan = nil

    yield if block_given?
end

#committed_transactionvoid

This method returns an undefined value.

Hook called just after this transaction has been committed



858
# File 'lib/roby/transaction.rb', line 858

def committed_transaction; end

#compute_graph_modifications_for(proxy, new_relations, removed_relations, updated_relations) ⇒ 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.

Compute the graph modifications that are involving the given proxy

It only computes the parent modifications involving objects that are not proxies themselves. It computes the child modifications for every child



605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
# File 'lib/roby/transaction.rb', line 605

def compute_graph_modifications_for(
    proxy, new_relations, removed_relations, updated_relations
)
    real_object = proxy.__getobj__
    proxy.partition_new_old_relations(
        :each_parent_object, include_proxies: false
    ) do |trsc_objects, rel, new, del, existing|
        trsc_graph = proxy.relation_graph_for(rel)
        plan_graph = proxy.__getobj__.relation_graph_for(rel)

        new.each do |task|
            edge_info = trsc_graph.edge_info(trsc_objects[task], proxy)
            new_relations << [plan_graph, task, real_object, edge_info]
        end
        del.each do |task|
            removed_relations << [plan_graph, task, real_object]
        end
        existing.each do |task|
            edge_info = trsc_graph.edge_info(trsc_objects[task], proxy)
            if plan_graph.edge_info(task, real_object) != edge_info
                updated_relations << [plan_graph, task, real_object, edge_info]
            end
        end
    end

    proxy.partition_new_old_relations(
        :each_child_object
    ) do |trsc_objects, rel, new, del, existing|
        trsc_graph = proxy.relation_graph_for(rel)
        plan_graph = proxy.__getobj__.relation_graph_for(rel)

        new.each do |task|
            edge_info = trsc_graph.edge_info(proxy, trsc_objects[task])
            new_relations << [plan_graph, real_object, task, edge_info]
        end
        del.each do |task|
            removed_relations << [plan_graph, real_object, task]
        end
        existing.each do |task|
            edge_info = trsc_graph.edge_info(proxy, trsc_objects[task])
            if plan_graph.edge_info(real_object, task) != edge_info
                updated_relations << [plan_graph, real_object, task, edge_info]
            end
        end
    end
end

#compute_triggers_for_committed_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 compute the triggers that shoul be applied if we commit this transaction



656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
# File 'lib/roby/transaction.rb', line 656

def compute_triggers_for_committed_transaction
    trigger_matches = {}
    plan.triggers.each do |tr|
        tr.each(self) do |t|
            trigger_matches[t] = tr
        end
    end
    proxy_tasks.each do |obj, proxy|
        # Add to matches if it matches and has not triggered yet
        if (tr = trigger_matches.delete(proxy)) && !(tr === obj)
            trigger_matches[obj] = tr
        end
    end
    trigger_matches
end

#controlObject

The decision control object associated with this transaction. It is in general plan.control



425
426
427
# File 'lib/roby/transaction.rb', line 425

def control
    plan.control
end

#copy_object_relations(object, proxy, proxy_map) ⇒ Object

This method copies on proxy all relations of object for which both ends of the relation are already in the transaction.



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/roby/transaction.rb', line 259

def copy_object_relations(object, proxy, proxy_map)
    # Create edges between the neighbours that are really in the transaction
    object.each_relation do |rel|
        plan_graph = object.relation_graph_for(rel)
        trsc_graph = proxy.relation_graph_for(rel)

        plan_graph.each_in_neighbour(object) do |parent|
            if (parent_proxy = proxy_map[parent])
                trsc_graph.add_edge(
                    parent_proxy, proxy,
                    plan_graph.edge_info(parent, object)
                )
            end
        end
        plan_graph.each_out_neighbour(object) do |child|
            if (child_proxy = proxy_map[child])
                trsc_graph.add_edge(
                    proxy, child_proxy,
                    plan_graph.edge_info(object, child)
                )
            end
        end
    end
end

#create_and_register_proxy_event(object) ⇒ Object



100
101
102
103
104
105
106
107
# File 'lib/roby/transaction.rb', line 100

def create_and_register_proxy_event(object)
    validate_transaction_not_frozen

    proxy = object.dup
    setup_and_register_proxy_event(proxy, object)
    copy_object_relations(object, proxy, proxy_events)
    proxy
end

#create_and_register_proxy_plan_service(object) ⇒ Object



109
110
111
112
113
114
115
116
# File 'lib/roby/transaction.rb', line 109

def create_and_register_proxy_plan_service(object)
    validate_transaction_not_frozen

    # Ensure the underlying task is wrapped
    proxy = object.dup
    setup_and_register_proxy_plan_service(proxy, object)
    proxy
end

#create_and_register_proxy_task(object) ⇒ Object



91
92
93
94
95
96
97
98
# File 'lib/roby/transaction.rb', line 91

def create_and_register_proxy_task(object)
    validate_transaction_not_frozen

    proxy = object.dup
    setup_and_register_proxy_task(proxy, object)
    copy_object_relations(object, proxy, proxy_tasks)
    proxy
end

#disable_proxyingObject



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

def disable_proxying
    @disable_proxying = true
    return unless block_given?

    begin
        yield
    ensure
        @disable_proxying = false
    end
end

#discard_modifications(object) ⇒ Object



1026
1027
1028
1029
1030
1031
1032
# File 'lib/roby/transaction.rb', line 1026

def discard_modifications(object)
    if object.respond_to?(:to_task)
        remove_task(object.to_task)
    else
        remove_event(object.to_task)
    end
end

#discard_transactionvoid

This method returns an undefined value.

Discard all the modifications that have been registered in this transaction



891
892
893
894
895
896
897
898
899
900
901
902
# File 'lib/roby/transaction.rb', line 891

def discard_transaction
    unless transactions.empty?
        raise InvalidTransaction,
              "there are still transactions on top of this one"
    end

    frozen!

    discarded_transaction
    plan.remove_transaction(self)
    @plan = nil
end

#discard_transaction!void

This method returns an undefined value.

Discards this transaction and all the transactions it is part of



882
883
884
885
# File 'lib/roby/transaction.rb', line 882

def discard_transaction!
    transactions.dup.each(&:discard_transaction!)
    discard_transaction
end

#discarded_transactionvoid

This method returns an undefined value.

Hook called just after this transaction has been discarded



907
# File 'lib/roby/transaction.rb', line 907

def discarded_transaction; end

#editObject



206
207
208
# File 'lib/roby/transaction.rb', line 206

def edit
    yield if block_given?
end

#enable_proxyingObject



860
861
862
# File 'lib/roby/transaction.rb', line 860

def enable_proxying
    @disable_proxying = false
end

#executable?Boolean

If true, an engine could execute tasks included in this plan. This is alwxays false for transactions

Returns:

  • (Boolean)


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

def executable?
    false
end

#execute(&block) ⇒ Object

Calls the given block in the execution thread of the engine of the underlying plan. If there is no engine attached to this plan, yields immediately.

See Plan#execute and ExecutionEngine#execute



457
458
459
# File 'lib/roby/transaction.rb', line 457

def execute(&block)
    plan.execute(&block)
end

#extend_proxy_object(proxy, object, klass = object.class) ⇒ Object



45
46
47
# File 'lib/roby/transaction.rb', line 45

def extend_proxy_object(proxy, object, klass = object.class)
    proxy.extend Roby::Transaction::Proxying.proxying_module_for(klass)
end

#finalized?Boolean

True if this transaction has either been discarded or committed

Returns:

  • (Boolean)

See Also:

  • #committed?


36
37
38
# File 'lib/roby/transaction.rb', line 36

def finalized?
    !plan
end

#finalized_plan_event(event) ⇒ void

This method returns an undefined value.

Hook called when an event included in self got finalized from #plan

It invalidates the transaction and calls DecisionControl#finalized_plan_event(self, event) for further actions

Parameters:

  • event (EventGenerator)

    the finalized event represented by its proxy in self



951
952
953
954
955
956
957
# File 'lib/roby/transaction.rb', line 951

def finalized_plan_event(event)
    proxied_event = event.__getobj__

    invalidate("event #{event} has been removed from the plan")
    discard_modifications(proxied_event)
    control.finalized_plan_event(self, event)
end

#finalized_plan_task(task) ⇒ void

This method returns an undefined value.

Hook called when a task included in self got finalized from #plan

It invalidates the transaction and calls DecisionControl#finalized_plan_task(self, event) for further actions

Parameters:

  • task (Task)

    the finalized task represented by its proxy in self



935
936
937
938
939
940
941
# File 'lib/roby/transaction.rb', line 935

def finalized_plan_task(task)
    proxied_task = task.__getobj__

    invalidate("task #{task} has been removed from the plan")
    discard_modifications(proxied_task)
    control.finalized_plan_task(self, task)
end

#find_local_object_for_event(object) ⇒ Object



136
137
138
# File 'lib/roby/transaction.rb', line 136

def find_local_object_for_event(object)
    find_local_object_for_plan_object(object, proxy_events)
end

#find_local_object_for_plan_object(object, proxy_map) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/roby/transaction.rb', line 118

def find_local_object_for_plan_object(object, proxy_map)
    if object.plan == self
        object
    elsif (proxy = proxy_map[object])
        proxy
    elsif !object.plan
        raise ArgumentError,
              "#{object} has been removed from plan"
    elsif object.plan.template?
        add(object)
        object
    end
end

#find_local_object_for_plan_service(object) ⇒ Object



140
141
142
143
144
# File 'lib/roby/transaction.rb', line 140

def find_local_object_for_plan_service(object)
    return unless (local_task = find_local_object_for_task(object.task))

    find_plan_service(local_task)
end

#find_local_object_for_task(object) ⇒ Object



132
133
134
# File 'lib/roby/transaction.rb', line 132

def find_local_object_for_task(object)
    find_local_object_for_plan_object(object, proxy_tasks)
end

#frozen!Object



909
910
911
# File 'lib/roby/transaction.rb', line 909

def frozen!
    @frozen = true
end

#has_proxy_for_event?(object) ⇒ Boolean

Tests whether an event has a proxy in self

Unlike #wrap, the provided object must be a plan object from the transaction’s underlying plan

Parameters:

Returns:

  • (Boolean)


304
305
306
307
308
309
310
# File 'lib/roby/transaction.rb', line 304

def has_proxy_for_event?(object)
    if object.plan != plan
        raise ArgumentError,
              "#{object} is not in #{self}.plan (#{plan})"
    end
    proxy_events.key?(object)
end

#has_proxy_for_task?(object) ⇒ Boolean

Tests whether a plan object has a proxy in self

Unlike #wrap, the provided object must be a plan object from the transaction’s underlying plan

Parameters:

Returns:

  • (Boolean)


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

def has_proxy_for_task?(object)
    if object.plan != plan
        raise ArgumentError,
              "#{object} is not in #{self}.plan (#{plan})"
    end
    proxy_tasks.key?(object)
end

#import_subplan_relations(graphs, mappings, proxy_map) ⇒ 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.

Copies relations when importing a new subplan from the main plan

Parameters:



218
219
220
221
222
223
224
225
226
227
228
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
# File 'lib/roby/transaction.rb', line 218

def import_subplan_relations(graphs, mappings, proxy_map)
    graphs.each do |plan_g, self_g|
        plan_g.copy_subgraph_to(self_g, mappings)
        mappings.each do |plan_v, self_v|
            # The method assumes that the plan objects are new. We are
            # therefore done if there is the same number of relations in
            # both plan and transactions
            #
            # It is NOT true in the general case as one can add extra
            # relations in the transaction
            if plan_g.in_degree(plan_v) != self_g.in_degree(self_v)
                plan_g.each_in_neighbour(plan_v) do |plan_parent|
                    next if mappings.key?(plan_parent)

                    if (self_parent = proxy_map[plan_parent])
                        self_g.add_edge(
                            self_parent, self_v,
                            plan_g.edge_info(plan_parent, plan_v)
                        )
                    end
                end
            end

            if plan_g.out_degree(plan_v) != self_g.out_degree(self_v)
                plan_g.each_out_neighbour(plan_v) do |plan_child|
                    next if mappings.key?(plan_child)

                    if (self_child = proxy_map[plan_child])
                        self_g.add_edge(
                            self_v, self_child,
                            plan_g.edge_info(plan_v, plan_child)
                        )
                    end
                end
            end
        end
    end
end

#invalid=(flag) ⇒ Object

Marks this transaction as either invalid or valid. If it is marked as valid, it clears #invalidation_reasons.



534
535
536
537
# File 'lib/roby/transaction.rb', line 534

def invalid=(flag)
    invalidation_reasons.clear unless flag
    @invalid = flag
end

#invalid?Boolean

True if #invalidate has been called, and #invalid= has not been called to clear the invalidation afterwards.

Returns:

  • (Boolean)


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

def invalid?
    @invalid
end

#invalidate(reason = nil) ⇒ Object

Marks this transaction as valid



556
557
558
559
560
561
562
# File 'lib/roby/transaction.rb', line 556

def invalidate(reason = nil)
    self.invalid = true
    invalidation_reasons << [reason, caller(1)] if reason
    Roby.debug do
        "invalidating #{self}: #{reason}"
    end
end

#invalidation_reasonsArray<String>

The set of invalidation reasons registered with #invalidate. It is cleared if the transaction is marked as valid again by calling #invalid=.

Returns:

  • (Array<String>)


530
# File 'lib/roby/transaction.rb', line 530

attribute(:invalidation_reasons) { [] }

#may_unwrap(object) ⇒ Object

If object is in this transaction, may_unwrap will return the underlying plan object. In all other cases, returns object.



391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/roby/transaction.rb', line 391

def may_unwrap(object)
    if object.respond_to?(:plan)
        if object.plan == self && object.respond_to?(:__getobj__)
            object.__getobj__
        elsif object.plan == plan
            object
        else
            object
        end
    else
        object
    end
end

#may_wrap(objects, create: true) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/roby/transaction.rb', line 376

def may_wrap(objects, create: true)
    if objects.respond_to?(:to_ary)
        objects.map { |obj| may_wrap(obj, create: create) }
    elsif objects.respond_to?(:each)
        raise ArgumentError,
              "don't know how to wrap containers of class #{objects.class}"
    elsif objects.kind_of?(PlanObject)
        wrap(objects, create: create)
    else
        objects
    end
end

#proposeObject



204
# File 'lib/roby/transaction.rb', line 204

def propose; end

#proxying?Boolean

Returns:

  • (Boolean)


875
876
877
# File 'lib/roby/transaction.rb', line 875

def proxying?
    !@frozen && !@disable_proxying
end

#query_result_set(matcher) ⇒ 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 delegation from the matchers to the plan object to determine the ‘right’ query algorithm



1022
1023
1024
# File 'lib/roby/transaction.rb', line 1022

def query_result_set(matcher)
    Queries::TransactionQueryResult.from_transaction(self, matcher)
end

#remove_free_event(event, timestamp = Time.now) ⇒ Object



370
371
372
373
374
# File 'lib/roby/transaction.rb', line 370

def remove_free_event(event, timestamp = Time.now)
    unwrapped, proxy = remove_plan_object(event, proxy_events)
    unmarked_permanent_events.delete(unwrapped) if proxy
    super(proxy || event, timestamp)
end

#remove_plan_object(object, proxy_map) ⇒ Object

Removes an object from this transaction

This does not remove the object from the underlying plan. Removing objects directly is (at best) dangerous, and should be handled by garbage collection.



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

def remove_plan_object(object, proxy_map)
    validate_transaction_not_frozen

    object = may_unwrap(object)
    proxy  = proxy_map.delete(object)
    actual_plan = (proxy || object).plan

    if actual_plan != self
        raise InternalError,
              "inconsistency: #{proxy || object} plan is #{actual_plan}, "\
              "was expected to be #{self}"
    end
    [object, proxy]
end

#remove_task(task, timestamp = Time.now) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
# File 'lib/roby/transaction.rb', line 358

def remove_task(task, timestamp = Time.now)
    unwrapped, proxy = remove_plan_object(task, proxy_tasks)
    if proxy
        unmarked_mission_tasks.delete(unwrapped)
        unmarked_permanent_tasks.delete(unwrapped)
        proxy.each_plan_child do |task_event_proxy|
            remove_plan_object(task_event_proxy, proxy_events)
        end
    end
    super(proxy || task, timestamp)
end

#removing_plan_relation(parent, child, relations) ⇒ void

This method returns an undefined value.

Hook called when a relation is removed between plan objects that are present in the transaction

If the removed relation is still present in the transaction as well, it invalidates the transaction and calls DecisionControl#removing_plan_relation(self, parent, child, relations, info)

for further action

Parameters:

  • parent (PlanObject)

    the parent object represented by its proxy in self

  • child (PlanObject)

    the child object represented by its proxy in self

  • relations (Array<Relations::Graph>)

    the graphs in which a relation has been added



1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
# File 'lib/roby/transaction.rb', line 1003

def removing_plan_relation(parent, child, relations)
    present_relations = relations.find_all do |rel|
        parent.child_object?(child, rel)
    end

    unless present_relations.empty?
        invalidate(
            "plan removed a relation #{parent} -> #{child} in #{relations}"
        )
        control.removing_plan_relation(self, parent, child, relations)
    end

    nil
end

#restore_relation(proxy, relation) ⇒ Object



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/roby/transaction.rb', line 312

def restore_relation(proxy, relation)
    object = proxy.__getobj__

    proxy_children = proxy.child_objects(relation)
    object.child_objects(relation).each do |object_child|
        next unless (proxy_child = wrap(object_child, create: false))

        if proxy_children.include?(proxy_child)
            relation.remove_edge(proxy, proxy_child)
        end
    end

    proxy_parents = proxy.parent_objects(relation)
    object.parent_objects(relation).each do |object_parent|
        next unless (proxy_parent = wrap(object_parent, create: false))

        if proxy_parents.include?(proxy_parent)
            relation.remove_edge(parent, proxy_parent)
        end
    end

    added_objects.delete(proxy)
    proxy.discovered_relations.delete(relation)
    proxy.do_discover(relation, false)
end

#root_plan?Boolean

True if this plan is root in the plan hierarchy

Returns:

  • (Boolean)


41
42
43
# File 'lib/roby/transaction.rb', line 41

def root_plan?
    false
end

#setup_and_register_proxy_event(proxy, event) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
# File 'lib/roby/transaction.rb', line 69

def setup_and_register_proxy_event(proxy, event)
    validate_transaction_not_frozen

    proxy_events[event] = proxy
    extend_proxy_object(proxy, event)
    proxy.plan = self
    proxy.setup_proxy(event, self)
    register_event(proxy)
    add_permanent_event(proxy) if plan.permanent_event?(event)
    proxy
end

#setup_and_register_proxy_plan_service(proxy, plan_service) ⇒ Object



81
82
83
84
85
86
87
88
89
# File 'lib/roby/transaction.rb', line 81

def setup_and_register_proxy_plan_service(proxy, plan_service)
    validate_transaction_not_frozen

    extend_proxy_object(proxy, plan_service)
    proxy.setup_proxy(plan_service, self)
    proxy.task = wrap_task(plan_service.to_task)
    add_plan_service(proxy)
    proxy
end

#setup_and_register_proxy_task(proxy, task) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/roby/transaction.rb', line 49

def setup_and_register_proxy_task(proxy, task)
    validate_transaction_not_frozen

    proxy_tasks[task] = proxy
    extend_proxy_object(proxy, task)
    proxy.plan = self
    proxy.setup_proxy(task, self)
    register_task(proxy)
    if plan.mission_task?(task)
        add_mission_task(proxy)
    elsif plan.permanent_task?(task)
        add_permanent_task(proxy)
    end
    plan.find_all_plan_services(task).each do |original_srv|
        create_and_register_proxy_plan_service(original_srv)
    end

    proxy
end

#unmark_mission_task(t) ⇒ Object



513
514
515
516
517
518
519
520
521
522
523
# File 'lib/roby/transaction.rb', line 513

def unmark_mission_task(t)
    validate_transaction_not_frozen

    t = t.as_plan
    if (proxy = find_local_object_for_task(t))
        super(proxy)
    end

    t = may_unwrap(t)
    unmarked_mission_tasks.add(t) if t.plan == plan
end

#unmark_permanent_event(t) ⇒ Object



489
490
491
492
493
494
495
496
497
498
499
# File 'lib/roby/transaction.rb', line 489

def unmark_permanent_event(t)
    validate_transaction_not_frozen

    t = t.as_plan
    if (proxy = find_local_object_for_event(t))
        super(proxy)
    end

    t = may_unwrap(t)
    unmarked_permanent_events.add(t) if t.plan == plan
end

#unmark_permanent_task(t) ⇒ Object



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

def unmark_permanent_task(t)
    validate_transaction_not_frozen

    t = t.as_plan
    if (proxy = find_local_object_for_task(t))
        super(proxy)
    end

    t = may_unwrap(t)
    unmarked_permanent_tasks.add(t) if t.plan == plan
end

#valid_transaction?Boolean

Tests if it is safe to commit this transaction

Returns:

  • (Boolean)

    it returns false if there are other transactions on top of it. They must be committed or discarded before this transaction can be committed or discarded. It also returns safe if if this transaction has been marked as invalid with #invalidate



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

def valid_transaction?
    transactions.empty? && !invalid?
end

#validate_transaction_not_frozenObject

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 called before any mutating operation to verify that the transaction can actually be modified - i.e. that it has not been commited or discarded yet

Raises:

  • FrozenTransaction



1046
1047
1048
1049
1050
1051
1052
1053
1054
# File 'lib/roby/transaction.rb', line 1046

def validate_transaction_not_frozen
    if frozen?
        raise FrozenTransaction,
              "transaction #{self} has been either committed or discarded. "\
              "No modification allowed"
    end

    nil
end

#wrap(object, create: true) ⇒ Object

Get the transaction proxy for object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/roby/transaction.rb', line 181

def wrap(object, create: true)
    if object.kind_of?(PlanService)
        wrap_plan_service(object, create: create)
    elsif object.respond_to?(:to_task)
        wrap_task(object, create: create)
    elsif object.respond_to?(:to_event)
        wrap_event(object, create: create)
    elsif object.respond_to?(:to_ary)
        object.map { |o| wrap(o, create: create) }
    elsif object.respond_to?(:each)
        raise ArgumentError,
              "don't know how to wrap containers of class #{object.class}"
    else
        raise TypeError,
              "don't know how to wrap #{object || 'nil'} of type "\
              "#{object.class.ancestors}"
    end
end

#wrap_event(event, create: true) ⇒ Object



164
165
166
167
168
169
170
# File 'lib/roby/transaction.rb', line 164

def wrap_event(event, create: true)
    if (local_event = find_local_object_for_event(event))
        local_event
    elsif create
        wrap_plan_object(event, proxy_events)
    end
end

#wrap_plan_object(object, proxy_map) ⇒ Object



146
147
148
149
150
151
152
153
154
# File 'lib/roby/transaction.rb', line 146

def wrap_plan_object(object, proxy_map)
    if object.plan != plan
        raise ArgumentError,
              "#{object} is in #{object.plan}, "\
              "this transaction #{self} applies on #{plan}"
    else
        object.create_transaction_proxy(self)
    end
end

#wrap_plan_service(plan_service, create: true) ⇒ Object



172
173
174
175
176
177
178
# File 'lib/roby/transaction.rb', line 172

def wrap_plan_service(plan_service, create: true)
    if (local_plan_service = find_local_object_for_plan_service(plan_service))
        local_plan_service
    elsif create
        plan_service.create_transaction_proxy(self)
    end
end

#wrap_task(task, create: true) ⇒ Object



156
157
158
159
160
161
162
# File 'lib/roby/transaction.rb', line 156

def wrap_task(task, create: true)
    if (local_task = find_local_object_for_task(task))
        local_task
    elsif create
        wrap_plan_object(task, proxy_tasks)
    end
end