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: ReachabilityPlanVisitor, ReachabilityTransactionVisitor, ReachabilityVisitor

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, #mission_tasks, #permanent_events, #permanent_tasks, #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_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_to, #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, #merge, #merge!, #merge_base, #merge_relation_graphs, #merge_transaction, #merge_transaction!, #merged_plan, #merging_plan, #mission?, #mission_task?, #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_task?, #quarantined_tasks, #real_plan, #recreate, #refresh_relations, #register_event, #register_task, #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, #root_in_query?, #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



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

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

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

    super()

    @plan   = plan

    @proxy_tasks      = Hash.new
    @proxy_events     = Hash.new
    @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



386
387
388
# File 'lib/roby/transaction.rb', line 386

def options
  @options
end

#planObject (readonly)

The plan this transaction applies on



380
381
382
# File 'lib/roby/transaction.rb', line 380

def plan
  @plan
end

#proxy_eventsObject (readonly)

The proxy objects built for events this transaction



384
385
386
# File 'lib/roby/transaction.rb', line 384

def proxy_events
  @proxy_events
end

#proxy_tasksObject (readonly)

The proxy objects built for tasks in this transaction



382
383
384
# File 'lib/roby/transaction.rb', line 382

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



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

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



378
379
380
# File 'lib/roby/transaction.rb', line 378

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



375
376
377
# File 'lib/roby/transaction.rb', line 375

def unmarked_permanent_tasks
  @unmarked_permanent_tasks
end

Instance Method Details

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



185
186
187
# File 'lib/roby/transaction.rb', line 185

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

#add(objects) ⇒ Object



450
451
452
453
454
# File 'lib/roby/transaction.rb', line 450

def add(objects)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    super(objects)
    self
end

#add_mission_task(t) ⇒ Object



426
427
428
429
430
431
432
# File 'lib/roby/transaction.rb', line 426

def add_mission_task(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    if t.transaction_proxy?
        unmarked_mission_tasks.delete(t.__getobj__)
    end
    super(t)
end

#add_permanent_event(e) ⇒ Object



442
443
444
445
446
447
448
# File 'lib/roby/transaction.rb', line 442

def add_permanent_event(e)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    if e.transaction_proxy?
        unmarked_permanent_events.delete(e.__getobj__)
    end
    super(e)
end

#add_permanent_task(t) ⇒ Object



434
435
436
437
438
439
440
# File 'lib/roby/transaction.rb', line 434

def add_permanent_task(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    if t.transaction_proxy?
        unmarked_permanent_tasks.delete(t.__getobj__)
    end
    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)



926
927
928
929
930
931
932
933
934
# File 'lib/roby/transaction.rb', line 926

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



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

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.



646
647
648
649
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
677
678
679
680
681
682
683
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
# File 'lib/roby/transaction.rb', line 646

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

    added_relations   = Array.new
    removed_relations = Array.new
    updated_relations = Array.new

    # 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  = Hash.new

    # 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 = Hash.new, Hash.new
    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?
            if permanent_event?(proxy)
                new_permanent_events << object
            end
            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 Exception => 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
                if original.task != task
                    plan.move_plan_service(original, task)
                end
            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 { |e| plan.add_permanent_event(e) }

    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



638
639
640
641
642
# File 'lib/roby/transaction.rb', line 638

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:



537
538
539
540
541
542
543
544
545
546
547
# File 'lib/roby/transaction.rb', line 537

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



871
872
873
874
875
876
877
878
879
880
# File 'lib/roby/transaction.rb', line 871

def clear
    unmarked_mission_tasks.clear
    unmarked_permanent_tasks.clear
    unmarked_permanent_events.clear
    proxy_tasks.each_value { |proxy| proxy.clear_relations }
    proxy_tasks.clear
    proxy_events.each_value { |proxy| proxy.clear_relations }
    proxy_events.clear
    super
end

#commit_transactionObject

Commit all modifications that have been registered in this transaction



799
800
801
802
803
804
805
806
807
808
809
810
811
812
# File 'lib/roby/transaction.rb', line 799

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



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

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



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# File 'lib/roby/transaction.rb', line 572

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



617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/roby/transaction.rb', line 617

def compute_triggers_for_committed_transaction
    trigger_matches = Hash.new
    plan.triggers.each do |tr|
        tr.each(self) do |t|
            trigger_matches[t] = tr
        end
    end
    proxy_tasks.each do |obj, proxy|
        if tr = trigger_matches.delete(proxy)
            if !(tr === obj) # already triggered
                trigger_matches[obj] = tr
            end
        end
    end
    trigger_matches
end

#controlObject

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



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

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.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/roby/transaction.rb', line 236

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



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

def create_and_register_proxy_event(object)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if 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



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

def create_and_register_proxy_plan_service(object)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if 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



85
86
87
88
89
90
91
# File 'lib/roby/transaction.rb', line 85

def create_and_register_proxy_task(object)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    proxy = object.dup
    setup_and_register_proxy_task(proxy, object)
    copy_object_relations(object, proxy, proxy_tasks)
    proxy
end

#disable_proxyingObject



820
821
822
823
824
825
826
827
828
829
# File 'lib/roby/transaction.rb', line 820

def disable_proxying
    @disable_proxying = true
    if block_given?
        begin
            yield
        ensure
            @disable_proxying = false
        end
    end
end

#discard_modifications(object) ⇒ Object



1116
1117
1118
1119
1120
1121
1122
# File 'lib/roby/transaction.rb', line 1116

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



846
847
848
849
850
851
852
853
854
855
856
# File 'lib/roby/transaction.rb', line 846

def discard_transaction
    if !transactions.empty?
        raise InvalidTransaction, "there is 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



835
836
837
838
839
840
# File 'lib/roby/transaction.rb', line 835

def discard_transaction!
    transactions.each do |trsc|
        trsc.discard_transaction!
    end
    discard_transaction
end

#discarded_transactionvoid

This method returns an undefined value.

Hook called just after this transaction has been discarded



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

def discarded_transaction; end

#editObject



191
192
193
# File 'lib/roby/transaction.rb', line 191

def edit
    yield if block_given?
end

#enable_proxyingObject



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

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)


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

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



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

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

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



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

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?


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

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



904
905
906
907
908
909
910
# File 'lib/roby/transaction.rb', line 904

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



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

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



126
127
128
# File 'lib/roby/transaction.rb', line 126

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



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

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

#find_local_object_for_plan_service(object) ⇒ Object



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

def find_local_object_for_plan_service(object)
    if local_task = find_local_object_for_task(object.task)
        find_plan_service(local_task)
    end
end

#find_local_object_for_task(object) ⇒ Object



122
123
124
# File 'lib/roby/transaction.rb', line 122

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

#frozen!Object



863
864
865
# File 'lib/roby/transaction.rb', line 863

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)


274
275
276
277
278
279
# File 'lib/roby/transaction.rb', line 274

def has_proxy_for_event?(object)
    if object.plan != self.plan
        raise ArgumentError, "#{object} is not in #{self}.plan (#{plan})"
    end
    proxy_events.has_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)


261
262
263
264
265
266
# File 'lib/roby/transaction.rb', line 261

def has_proxy_for_task?(object)
    if object.plan != self.plan
        raise ArgumentError, "#{object} is not in #{self}.plan (#{plan})"
    end
    proxy_tasks.has_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:



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/roby/transaction.rb', line 203

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



504
505
506
507
508
509
# File 'lib/roby/transaction.rb', line 504

def invalid=(flag)
    if !flag
        invalidation_reasons.clear
    end
    @invalid = flag
end

#invalid?Boolean

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

Returns:

  • (Boolean)


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

def invalid?; @invalid end

#invalidate(reason = nil) ⇒ Object

Marks this transaction as valid



524
525
526
527
528
529
530
# File 'lib/roby/transaction.rb', line 524

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


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

attribute(:invalidation_reasons) { Array.new }

#may_unwrap(object) ⇒ Object

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



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

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

#may_wrap(objects, create: true) ⇒ Object



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

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



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

def propose; end

#proxying?Boolean

Returns:

  • (Boolean)


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

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

#query_each(result_set) ⇒ Object

Yields tasks in the result set of query. Unlike Query#result_set, all the tasks are included in the transaction

result_set is the value returned by #query_result_set.



983
984
985
986
987
# File 'lib/roby/transaction.rb', line 983

def query_each(result_set) # :nodoc:
    plan_set, trsc_set = result_set
    plan_set.each { |task| yield(wrap_task(task)) }
    trsc_set.each { |task| yield(task) }
end

#query_result_set(matcher) ⇒ Object

Returns [plan_set, transaction_set], where the first is the set of plan tasks matching matcher and the second the set of transaction tasks matching it. The two sets are disjoint.

This will be stored by the Query object as the query result. Note that, at this point, the transaction has not been modified even though it applies on the global scope. New proxies will only be created when Query#each is called.



966
967
968
969
970
971
972
973
974
975
976
977
# File 'lib/roby/transaction.rb', line 966

def query_result_set(matcher) # :nodoc:
    plan_set = Set.new
    if matcher.scope == :global
        plan_result_set = plan.query_result_set(matcher)
        plan.query_each(plan_result_set) do |task|
            plan_set << task if !has_proxy_for_task?(task)
        end
    end
    
    transaction_set = super
    [plan_set, transaction_set]
end

#query_roots(result_set, relation) ⇒ 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.

Given the result set of query, returns the subset of tasks which have no parent in query

This is never called directly, but is used by the Query API



1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
# File 'lib/roby/transaction.rb', line 1094

def query_roots(result_set, relation) # :nodoc:
    plan_set      , trsc_set      = *result_set
    plan_children , trsc_children = Set.new     , Set.new

    trsc_graph = task_relation_graph_for(relation).reverse
    plan_graph = plan.task_relation_graph_for(relation).reverse

    plan_result = plan_set.find_all do |task|
        !reachable_on_applied_transaction?(
            [], trsc_set, trsc_graph,
            [task], plan_set, plan_graph)
    end

    trsc_result = trsc_set.find_all do |task|
        !reachable_on_applied_transaction?(
            [task], trsc_set, trsc_graph,
            [], plan_set, plan_graph)
    end

    [plan_result.to_set, trsc_result.to_set]
end

#reachable_on_applied_transaction?(transaction_seeds, transaction_set, transaction_graph, plan_seeds, plan_set, plan_graph) ⇒ Boolean

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.

Tests whether a task in plan_set or proxy_set would be reachable from ‘task’ if the transaction was applied

Returns:

  • (Boolean)


1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
# File 'lib/roby/transaction.rb', line 1052

def reachable_on_applied_transaction?(transaction_seeds, transaction_set, transaction_graph,
                                      plan_seeds, plan_set, plan_graph)
    transaction_visitor = ReachabilityTransactionVisitor.new(
        transaction_graph, self, plan_seeds, transaction_set)
    if task = transaction_seeds.first
        transaction_visitor.handle_start_vertex(task)
    end
    plan_visitor        = ReachabilityPlanVisitor.new(
        plan_graph, self, transaction_seeds, plan_set)
    if task = plan_seeds.first
        plan_visitor.handle_start_vertex(task)
    end

    catch(:reachable) do
        while !transaction_seeds.empty? || !plan_seeds.empty?
            transaction_seeds.each do |seed|
                seed = transaction_seeds.shift
                if !transaction_visitor.finished_vertex?(seed)
                    transaction_graph.depth_first_visit(seed, transaction_visitor) {}
                end
            end
            transaction_seeds.clear

            plan_seeds.each do |seed|
                seed = plan_seeds.shift
                if !plan_visitor.finished_vertex?(seed)
                    plan_graph.depth_first_visit(seed, plan_visitor) {}
                end
            end
            plan_seeds.clear
        end
        return false
    end
    true
end

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



335
336
337
338
339
340
341
# File 'lib/roby/transaction.rb', line 335

def remove_free_event(event, timestamp = Time.now)
    unwrapped, proxy = remove_plan_object(event, proxy_events)
    if proxy
        unmarked_permanent_events.delete(unwrapped)
    end
    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.



310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/roby/transaction.rb', line 310

def remove_plan_object(object, proxy_map)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if 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
    return object, proxy
end

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



323
324
325
326
327
328
329
330
331
332
333
# File 'lib/roby/transaction.rb', line 323

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



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

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
end

#restore_relation(proxy, relation) ⇒ Object



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/roby/transaction.rb', line 281

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)


32
33
34
# File 'lib/roby/transaction.rb', line 32

def root_plan?
    false
end

#setup_and_register_proxy_event(proxy, event) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/roby/transaction.rb', line 61

def setup_and_register_proxy_event(proxy, event)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?

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

#setup_and_register_proxy_plan_service(proxy, plan_service) ⇒ Object



75
76
77
78
79
80
81
82
83
# File 'lib/roby/transaction.rb', line 75

def setup_and_register_proxy_plan_service(proxy, plan_service)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if 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



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/roby/transaction.rb', line 40

def setup_and_register_proxy_task(proxy, task)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if 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
    if services = plan.find_all_plan_services(task)
        services.each do |original_srv|
            create_and_register_proxy_plan_service(original_srv)
        end
    end
    proxy
end

#unmark_mission_task(t) ⇒ Object



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

def unmark_mission_task(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    t = t.as_plan
    if proxy = find_local_object_for_task(t)
        super(proxy)
    end

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

#unmark_permanent_event(t) ⇒ Object



456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/roby/transaction.rb', line 456

def unmark_permanent_event(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    t = t.as_plan
    if proxy = find_local_object_for_event(t)
        super(proxy)
    end

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

#unmark_permanent_task(t) ⇒ Object



469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/roby/transaction.rb', line 469

def unmark_permanent_task(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
    t = t.as_plan
    if proxy = find_local_object_for_task(t)
        super(proxy)
    end

    t = may_unwrap(t)
    if t.plan == self.plan
        unmarked_permanent_tasks.add(t)
    end
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



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

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

#wrap(object, create: true) ⇒ Object

Get the transaction proxy for object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/roby/transaction.rb', line 169

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



152
153
154
155
156
157
158
# File 'lib/roby/transaction.rb', line 152

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



136
137
138
139
140
141
142
# File 'lib/roby/transaction.rb', line 136

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

#wrap_plan_service(plan_service, create: true) ⇒ Object



160
161
162
163
164
165
166
# File 'lib/roby/transaction.rb', line 160

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



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

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