roby 0.8.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.deep-cover.rb +3 -0
- data/.gitattributes +1 -0
- data/.gitignore +24 -0
- data/.simplecov +10 -0
- data/.travis.yml +17 -0
- data/.yardopts +4 -0
- data/Gemfile +15 -0
- data/README.md +11 -0
- data/Rakefile +47 -177
- data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
- data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
- data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
- data/benchmark/{genom.rb → attic/genom.rb} +0 -0
- data/benchmark/attic/transactions.rb +62 -0
- data/benchmark/plan_basic_operations.rb +28 -0
- data/benchmark/relations/graph.rb +63 -0
- data/benchmark/ruby/identity.rb +18 -0
- data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
- data/benchmark/ruby/yield_vs_block.rb +35 -0
- data/benchmark/run +5 -0
- data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
- data/benchmark/transactions.rb +99 -51
- data/bin/roby +38 -197
- data/bin/roby-display +14 -0
- data/bin/roby-log +3 -176
- data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
- data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
- data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
- data/doc/guide/{src → attic}/abstraction/index.page +1 -1
- data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
- data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
- data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
- data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
- data/doc/guide/attic/cycle/error_handling.page +98 -0
- data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
- data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
- data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
- data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
- data/doc/guide/{src → attic}/cycle/index.page +1 -1
- data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
- data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
- data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
- data/doc/guide/attic/plans/building_plans.page +89 -0
- data/doc/guide/attic/plans/code.page +192 -0
- data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
- data/doc/guide/attic/plans/index.page +7 -0
- data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
- data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
- data/doc/guide/attic/plans/querying_plans.page +5 -0
- data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
- data/doc/guide/config.yaml +7 -4
- data/doc/guide/ext/extended_menu.rb +29 -0
- data/doc/guide/ext/init.rb +6 -0
- data/doc/guide/ext/rdoc_links.rb +7 -6
- data/doc/guide/src/advanced_concepts/history.page +5 -0
- data/doc/guide/src/advanced_concepts/index.page +11 -0
- data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
- data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
- data/doc/guide/src/advanced_concepts/transactions.page +5 -0
- data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
- data/doc/guide/src/base.template +96 -0
- data/doc/guide/src/basics_shell_header.txt +5 -7
- data/doc/guide/src/building/action_coordination.page +96 -0
- data/doc/guide/src/building/actions.page +124 -0
- data/doc/guide/src/building/file_layout.page +71 -0
- data/doc/guide/src/building/index.page +50 -0
- data/doc/guide/src/building/patterns.page +86 -0
- data/doc/guide/src/building/patterns_forwarding.png +0 -0
- data/doc/guide/src/building/patterns_forwarding.svg +277 -0
- data/doc/guide/src/building/runtime.page +95 -0
- data/doc/guide/src/building/task_models.page +94 -0
- data/doc/guide/src/building/tasks.page +284 -0
- data/doc/guide/src/concepts/error_handling.page +100 -0
- data/doc/guide/src/concepts/exception_propagation.png +0 -0
- data/doc/guide/src/concepts/exception_propagation.svg +445 -0
- data/doc/guide/src/concepts/execution.page +85 -0
- data/doc/guide/src/concepts/execution.png +0 -0
- data/doc/guide/src/concepts/execution.svg +573 -0
- data/doc/guide/src/concepts/execution_cycle.png +0 -0
- data/doc/guide/src/concepts/garbage_collection.page +57 -0
- data/doc/guide/src/concepts/index.page +27 -0
- data/doc/guide/src/concepts/plans.page +101 -0
- data/doc/guide/src/concepts/policy.page +31 -0
- data/doc/guide/src/concepts/reactor.page +61 -0
- data/doc/guide/src/concepts/simple_plan_example.png +0 -0
- data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
- data/doc/guide/src/default.template +9 -74
- data/doc/guide/src/event_relations/forward.page +71 -0
- data/doc/guide/src/event_relations/index.page +12 -0
- data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
- data/doc/guide/src/event_relations/signal.page +55 -0
- data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
- data/doc/guide/src/htmldoc.metainfo +21 -8
- data/doc/guide/src/index.page +8 -3
- data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
- data/doc/guide/src/installation/publications.page +14 -0
- data/doc/guide/src/{introduction → installation}/videos.page +14 -7
- data/doc/guide/src/interacting/index.page +16 -0
- data/doc/guide/src/interacting/run.page +33 -0
- data/doc/guide/src/interacting/shell.page +95 -0
- data/doc/guide/src/plugins/creating_plugins.page +72 -0
- data/doc/guide/src/plugins/index.page +27 -5
- data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
- data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
- data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
- data/doc/guide/src/style_screen.css +687 -0
- data/doc/guide/src/task_relations/dependency.page +107 -0
- data/doc/guide/src/task_relations/executed_by.page +77 -0
- data/doc/guide/src/task_relations/index.page +12 -0
- data/doc/guide/src/task_relations/new_relations.page +119 -0
- data/doc/guide/src/task_relations/planned_by.page +46 -0
- data/doc/guide/src/tutorial/app.page +117 -0
- data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
- data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
- data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
- data/doc/guide/src/tutorial/events.page +195 -0
- data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
- data/doc/guide/src/tutorial/index.page +13 -0
- data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
- data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
- data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
- data/doc/guide/src/tutorial/relations_display.page +153 -0
- data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
- data/doc/guide/src/tutorial/shell.page +121 -0
- data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
- data/doc/guide/src/tutorial/tasks.page +374 -0
- data/lib/roby.rb +102 -47
- data/lib/roby/actions.rb +17 -0
- data/lib/roby/actions/action.rb +80 -0
- data/lib/roby/actions/interface.rb +45 -0
- data/lib/roby/actions/library.rb +23 -0
- data/lib/roby/actions/models/action.rb +224 -0
- data/lib/roby/actions/models/coordination_action.rb +58 -0
- data/lib/roby/actions/models/interface.rb +22 -0
- data/lib/roby/actions/models/interface_base.rb +294 -0
- data/lib/roby/actions/models/library.rb +12 -0
- data/lib/roby/actions/models/method_action.rb +90 -0
- data/lib/roby/actions/task.rb +114 -0
- data/lib/roby/and_generator.rb +125 -0
- data/lib/roby/app.rb +2795 -829
- data/lib/roby/app/autotest_console_reporter.rb +138 -0
- data/lib/roby/app/base.rb +21 -0
- data/lib/roby/app/cucumber.rb +2 -0
- data/lib/roby/app/cucumber/controller.rb +439 -0
- data/lib/roby/app/cucumber/helpers.rb +280 -0
- data/lib/roby/app/cucumber/world.rb +32 -0
- data/lib/roby/app/debug.rb +136 -0
- data/lib/roby/app/gen.rb +2 -0
- data/lib/roby/app/rake.rb +178 -38
- data/lib/roby/app/robot_config.rb +9 -0
- data/lib/roby/app/robot_names.rb +115 -0
- data/lib/roby/app/run.rb +3 -2
- data/lib/roby/app/scripts.rb +72 -0
- data/lib/roby/app/scripts/autotest.rb +173 -0
- data/lib/roby/app/scripts/display.rb +2 -0
- data/lib/roby/app/scripts/restart.rb +52 -0
- data/lib/roby/app/scripts/results.rb +17 -8
- data/lib/roby/app/scripts/run.rb +155 -24
- data/lib/roby/app/scripts/shell.rb +147 -62
- data/lib/roby/app/scripts/test.rb +107 -22
- data/lib/roby/app/test_reporter.rb +74 -0
- data/lib/roby/app/test_server.rb +159 -0
- data/lib/roby/app/vagrant.rb +47 -0
- data/lib/roby/backports.rb +16 -0
- data/lib/roby/cli/display.rb +190 -0
- data/lib/roby/cli/exceptions.rb +17 -0
- data/lib/roby/cli/gen/actions/class.rb +5 -0
- data/lib/roby/cli/gen/actions/test.rb +6 -0
- data/lib/roby/cli/gen/app/.yardopts +6 -0
- data/lib/roby/cli/gen/app/README.md +28 -0
- data/lib/roby/cli/gen/app/Rakefile +15 -0
- data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
- data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
- data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
- data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
- data/lib/roby/cli/gen/class/class.rb +6 -0
- data/lib/roby/cli/gen/class/test.rb +7 -0
- data/lib/roby/cli/gen/helpers.rb +203 -0
- data/lib/roby/cli/gen/module/module.rb +5 -0
- data/lib/roby/cli/gen/module/test.rb +6 -0
- data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
- data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
- data/lib/roby/cli/gen/task/class.rb +44 -0
- data/lib/roby/cli/gen/task/test.rb +6 -0
- data/lib/roby/cli/gen_main.rb +120 -0
- data/lib/roby/cli/log.rb +276 -0
- data/lib/roby/cli/log/flamegraph.html +499 -0
- data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
- data/lib/roby/cli/main.rb +153 -0
- data/lib/roby/coordination.rb +60 -0
- data/lib/roby/coordination/action_script.rb +25 -0
- data/lib/roby/coordination/action_state_machine.rb +125 -0
- data/lib/roby/coordination/actions.rb +106 -0
- data/lib/roby/coordination/base.rb +145 -0
- data/lib/roby/coordination/calculus.rb +40 -0
- data/lib/roby/coordination/child.rb +28 -0
- data/lib/roby/coordination/event.rb +29 -0
- data/lib/roby/coordination/fault_handler.rb +25 -0
- data/lib/roby/coordination/fault_handling_task.rb +13 -0
- data/lib/roby/coordination/fault_response_table.rb +110 -0
- data/lib/roby/coordination/models/action_script.rb +64 -0
- data/lib/roby/coordination/models/action_state_machine.rb +224 -0
- data/lib/roby/coordination/models/actions.rb +191 -0
- data/lib/roby/coordination/models/arguments.rb +55 -0
- data/lib/roby/coordination/models/base.rb +176 -0
- data/lib/roby/coordination/models/capture.rb +86 -0
- data/lib/roby/coordination/models/child.rb +35 -0
- data/lib/roby/coordination/models/event.rb +41 -0
- data/lib/roby/coordination/models/exceptions.rb +42 -0
- data/lib/roby/coordination/models/fault_handler.rb +219 -0
- data/lib/roby/coordination/models/fault_response_table.rb +77 -0
- data/lib/roby/coordination/models/root.rb +22 -0
- data/lib/roby/coordination/models/script.rb +283 -0
- data/lib/roby/coordination/models/task.rb +184 -0
- data/lib/roby/coordination/models/task_from_action.rb +50 -0
- data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
- data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
- data/lib/roby/coordination/models/task_from_variable.rb +27 -0
- data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
- data/lib/roby/coordination/models/variable.rb +32 -0
- data/lib/roby/coordination/script.rb +200 -0
- data/lib/roby/coordination/script_instruction.rb +12 -0
- data/lib/roby/coordination/task.rb +45 -0
- data/lib/roby/coordination/task_base.rb +69 -0
- data/lib/roby/coordination/task_script.rb +293 -0
- data/lib/roby/coordination/task_state_machine.rb +308 -0
- data/lib/roby/decision_control.rb +33 -21
- data/lib/roby/distributed_object.rb +76 -0
- data/lib/roby/droby.rb +17 -0
- data/lib/roby/droby/droby_id.rb +6 -0
- data/lib/roby/droby/enable.rb +153 -0
- data/lib/roby/droby/event_logger.rb +189 -0
- data/lib/roby/droby/event_logging.rb +57 -0
- data/lib/roby/droby/exceptions.rb +14 -0
- data/lib/roby/droby/identifiable.rb +22 -0
- data/lib/roby/droby/logfile.rb +141 -0
- data/lib/roby/droby/logfile/client.rb +176 -0
- data/lib/roby/droby/logfile/file_format.md +97 -0
- data/lib/roby/droby/logfile/index.rb +117 -0
- data/lib/roby/droby/logfile/reader.rb +139 -0
- data/lib/roby/droby/logfile/server.rb +199 -0
- data/lib/roby/droby/logfile/writer.rb +114 -0
- data/lib/roby/droby/marshal.rb +264 -0
- data/lib/roby/droby/marshallable.rb +12 -0
- data/lib/roby/droby/null_event_logger.rb +25 -0
- data/lib/roby/droby/object_manager.rb +205 -0
- data/lib/roby/droby/peer_id.rb +6 -0
- data/lib/roby/droby/plan_rebuilder.rb +373 -0
- data/lib/roby/droby/rebuilt_plan.rb +160 -0
- data/lib/roby/droby/remote_droby_id.rb +6 -0
- data/lib/roby/droby/timepoints.rb +205 -0
- data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
- data/lib/roby/droby/timepoints_ctf.rb +125 -0
- data/lib/roby/droby/v5.rb +14 -0
- data/lib/roby/droby/v5/builtin.rb +120 -0
- data/lib/roby/droby/v5/droby_class.rb +45 -0
- data/lib/roby/droby/v5/droby_constant.rb +81 -0
- data/lib/roby/droby/v5/droby_dump.rb +1026 -0
- data/lib/roby/droby/v5/droby_id.rb +44 -0
- data/lib/roby/droby/v5/droby_model.rb +82 -0
- data/lib/roby/droby/v5/peer_id.rb +10 -0
- data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
- data/lib/roby/event.rb +79 -957
- data/lib/roby/event_constraints.rb +835 -0
- data/lib/roby/event_generator.rb +1047 -0
- data/lib/roby/event_structure/causal_link.rb +6 -0
- data/lib/roby/event_structure/forwarding.rb +6 -0
- data/lib/roby/event_structure/precedence.rb +7 -0
- data/lib/roby/event_structure/signal.rb +8 -0
- data/lib/roby/event_structure/temporal_constraints.rb +640 -0
- data/lib/roby/exceptions.rb +446 -152
- data/lib/roby/executable_plan.rb +549 -0
- data/lib/roby/execution_engine.rb +1997 -950
- data/lib/roby/filter_generator.rb +26 -0
- data/lib/roby/gui/chronicle_view.rb +225 -0
- data/lib/roby/gui/chronicle_widget.rb +925 -0
- data/lib/roby/gui/dot_id.rb +11 -0
- data/lib/roby/gui/exception_view.rb +44 -0
- data/lib/roby/gui/log_display.rb +273 -0
- data/lib/roby/gui/model_views.rb +2 -0
- data/lib/roby/gui/model_views/action_interface.rb +53 -0
- data/lib/roby/gui/model_views/task.rb +47 -0
- data/lib/roby/gui/model_views/task.rhtml +41 -0
- data/lib/roby/gui/object_info_view.rb +89 -0
- data/lib/roby/gui/plan_dot_layout.rb +427 -0
- data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
- data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
- data/lib/roby/gui/relations_view.rb +278 -0
- data/lib/roby/gui/relations_view/relations.ui +139 -0
- data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
- data/lib/roby/gui/relations_view/relations_config.rb +292 -0
- data/lib/roby/gui/relations_view/relations_view.ui +53 -0
- data/lib/roby/gui/scheduler_view.css +24 -0
- data/lib/roby/gui/scheduler_view.rb +46 -0
- data/lib/roby/gui/scheduler_view.rhtml +53 -0
- data/lib/roby/gui/stepping.rb +93 -0
- data/lib/roby/gui/stepping.ui +181 -0
- data/lib/roby/gui/styles.rb +81 -0
- data/lib/roby/gui/task_display_configuration.rb +42 -0
- data/lib/roby/gui/task_state_at.rb +38 -0
- data/lib/roby/hooks.rb +26 -0
- data/lib/roby/interface.rb +136 -469
- data/lib/roby/interface/async.rb +20 -0
- data/lib/roby/interface/async/action_monitor.rb +188 -0
- data/lib/roby/interface/async/interface.rb +498 -0
- data/lib/roby/interface/async/job_monitor.rb +213 -0
- data/lib/roby/interface/async/log.rb +238 -0
- data/lib/roby/interface/async/new_job_listener.rb +79 -0
- data/lib/roby/interface/async/ui_connector.rb +183 -0
- data/lib/roby/interface/client.rb +553 -0
- data/lib/roby/interface/command.rb +24 -0
- data/lib/roby/interface/command_argument.rb +16 -0
- data/lib/roby/interface/command_library.rb +92 -0
- data/lib/roby/interface/droby_channel.rb +174 -0
- data/lib/roby/interface/exceptions.rb +22 -0
- data/lib/roby/interface/interface.rb +655 -0
- data/lib/roby/interface/job.rb +47 -0
- data/lib/roby/interface/rest.rb +10 -0
- data/lib/roby/interface/rest/api.rb +29 -0
- data/lib/roby/interface/rest/helpers.rb +24 -0
- data/lib/roby/interface/rest/server.rb +212 -0
- data/lib/roby/interface/server.rb +154 -0
- data/lib/roby/interface/shell_client.rb +468 -0
- data/lib/roby/interface/shell_subcommand.rb +24 -0
- data/lib/roby/interface/subcommand_client.rb +35 -0
- data/lib/roby/interface/tcp.rb +168 -0
- data/lib/roby/models/arguments.rb +112 -0
- data/lib/roby/models/plan_object.rb +83 -0
- data/lib/roby/models/task.rb +835 -0
- data/lib/roby/models/task_event.rb +62 -0
- data/lib/roby/models/task_service.rb +78 -0
- data/lib/roby/or_generator.rb +88 -0
- data/lib/roby/plan.rb +1751 -864
- data/lib/roby/plan_object.rb +611 -0
- data/lib/roby/plan_service.rb +200 -0
- data/lib/roby/promise.rb +332 -0
- data/lib/roby/queries.rb +23 -0
- data/lib/roby/queries/and_matcher.rb +32 -0
- data/lib/roby/queries/any.rb +27 -0
- data/lib/roby/queries/code_error_matcher.rb +58 -0
- data/lib/roby/queries/event_generator_matcher.rb +9 -0
- data/lib/roby/queries/execution_exception_matcher.rb +165 -0
- data/lib/roby/queries/index.rb +165 -0
- data/lib/roby/queries/localized_error_matcher.rb +149 -0
- data/lib/roby/queries/matcher_base.rb +107 -0
- data/lib/roby/queries/none.rb +27 -0
- data/lib/roby/queries/not_matcher.rb +30 -0
- data/lib/roby/queries/op_matcher.rb +8 -0
- data/lib/roby/queries/or_matcher.rb +30 -0
- data/lib/roby/queries/plan_object_matcher.rb +363 -0
- data/lib/roby/queries/query.rb +188 -0
- data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
- data/lib/roby/queries/task_matcher.rb +344 -0
- data/lib/roby/relations.rb +42 -678
- data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
- data/lib/roby/relations/directed_relation_support.rb +268 -0
- data/lib/roby/relations/event_relation_graph.rb +19 -0
- data/lib/roby/relations/fork_merge_visitor.rb +154 -0
- data/lib/roby/relations/graph.rb +533 -0
- data/lib/roby/relations/models/directed_relation_support.rb +11 -0
- data/lib/roby/relations/models/graph.rb +75 -0
- data/lib/roby/relations/models/task_relation_graph.rb +18 -0
- data/lib/roby/relations/space.rb +380 -0
- data/lib/roby/relations/task_relation_graph.rb +20 -0
- data/lib/roby/robot.rb +85 -38
- data/lib/roby/schedulers/basic.rb +155 -25
- data/lib/roby/schedulers/null.rb +20 -0
- data/lib/roby/schedulers/reporting.rb +31 -0
- data/lib/roby/schedulers/state.rb +129 -0
- data/lib/roby/schedulers/temporal.rb +91 -0
- data/lib/roby/singletons.rb +87 -0
- data/lib/roby/standalone.rb +4 -2
- data/lib/roby/standard_errors.rb +405 -82
- data/lib/roby/state.rb +6 -3
- data/lib/roby/state/conf_model.rb +5 -0
- data/lib/roby/state/events.rb +181 -95
- data/lib/roby/state/goal_model.rb +77 -0
- data/lib/roby/state/open_struct.rb +591 -0
- data/lib/roby/state/open_struct_model.rb +68 -0
- data/lib/roby/state/pos.rb +45 -45
- data/lib/roby/state/shapes.rb +11 -11
- data/lib/roby/state/state_model.rb +303 -0
- data/lib/roby/state/task.rb +43 -0
- data/lib/roby/support.rb +88 -148
- data/lib/roby/task.rb +1361 -1750
- data/lib/roby/task_arguments.rb +428 -0
- data/lib/roby/task_event.rb +127 -0
- data/lib/roby/task_event_generator.rb +337 -0
- data/lib/roby/task_service.rb +6 -0
- data/lib/roby/task_structure/conflicts.rb +104 -0
- data/lib/roby/task_structure/dependency.rb +932 -0
- data/lib/roby/task_structure/error_handling.rb +118 -0
- data/lib/roby/task_structure/executed_by.rb +234 -0
- data/lib/roby/task_structure/planned_by.rb +90 -0
- data/lib/roby/tasks/aggregator.rb +37 -0
- data/lib/roby/tasks/external_process.rb +275 -0
- data/lib/roby/tasks/group.rb +27 -0
- data/lib/roby/tasks/null.rb +19 -0
- data/lib/roby/tasks/parallel.rb +43 -0
- data/lib/roby/tasks/sequence.rb +88 -0
- data/lib/roby/tasks/simple.rb +21 -0
- data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
- data/lib/roby/tasks/timeout.rb +17 -0
- data/lib/roby/tasks/virtual.rb +55 -0
- data/lib/roby/template_plan.rb +7 -0
- data/lib/roby/test/aruba_minitest.rb +74 -0
- data/lib/roby/test/assertion.rb +16 -0
- data/lib/roby/test/assertions.rb +490 -0
- data/lib/roby/test/common.rb +368 -591
- data/lib/roby/test/dsl.rb +149 -0
- data/lib/roby/test/error.rb +18 -0
- data/lib/roby/test/event_reporter.rb +83 -0
- data/lib/roby/test/execution_expectations.rb +1134 -0
- data/lib/roby/test/expect_execution.rb +151 -0
- data/lib/roby/test/minitest_helpers.rb +166 -0
- data/lib/roby/test/roby_app_helpers.rb +200 -0
- data/lib/roby/test/run_planners.rb +155 -0
- data/lib/roby/test/self.rb +112 -0
- data/lib/roby/test/spec.rb +198 -0
- data/lib/roby/test/tasks/empty_task.rb +4 -4
- data/lib/roby/test/tasks/goto.rb +28 -27
- data/lib/roby/test/teardown_plans.rb +100 -0
- data/lib/roby/test/testcase.rb +239 -307
- data/lib/roby/test/tools.rb +159 -155
- data/lib/roby/test/validate_state_machine.rb +75 -0
- data/lib/roby/transaction.rb +1125 -0
- data/lib/roby/transaction/event_generator_proxy.rb +63 -0
- data/lib/roby/transaction/plan_object_proxy.rb +99 -0
- data/lib/roby/transaction/plan_service_proxy.rb +43 -0
- data/lib/roby/transaction/proxying.rb +120 -0
- data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
- data/lib/roby/transaction/task_proxy.rb +135 -0
- data/lib/roby/until_generator.rb +30 -0
- data/lib/roby/version.rb +5 -0
- data/lib/roby/yard.rb +169 -0
- data/lib/yard-roby.rb +1 -0
- data/manifest.xml +32 -6
- data/roby.gemspec +59 -0
- metadata +788 -587
- data/Manifest.txt +0 -321
- data/NOTES +0 -4
- data/README.txt +0 -166
- data/TODO.txt +0 -146
- data/app/README.txt +0 -24
- data/app/Rakefile +0 -8
- data/app/config/ROBOT.rb +0 -5
- data/app/config/init.rb +0 -33
- data/app/config/roby.yml +0 -3
- data/app/controllers/ROBOT.rb +0 -2
- data/app/planners/ROBOT/main.rb +0 -6
- data/app/planners/main.rb +0 -5
- data/app/scripts/distributed +0 -3
- data/app/scripts/generate/bookmarks +0 -3
- data/app/scripts/replay +0 -3
- data/app/scripts/results +0 -3
- data/app/scripts/run +0 -3
- data/app/scripts/server +0 -3
- data/app/scripts/shell +0 -3
- data/app/scripts/test +0 -3
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby-shell +0 -25
- data/doc/guide/src/basics/app.page +0 -139
- data/doc/guide/src/basics/index.page +0 -11
- data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/basics/relations_display.page +0 -203
- data/doc/guide/src/basics/shell.page +0 -102
- data/doc/guide/src/default.css +0 -319
- data/doc/guide/src/introduction/index.page +0 -29
- data/doc/guide/src/introduction/publications.page +0 -14
- data/doc/guide/src/relations/dependency.page +0 -89
- data/doc/guide/src/relations/index.page +0 -12
- data/ext/droby/dump.cc +0 -175
- data/ext/droby/extconf.rb +0 -3
- data/ext/graph/algorithm.cc +0 -746
- data/ext/graph/extconf.rb +0 -7
- data/ext/graph/graph.cc +0 -575
- data/ext/graph/graph.hh +0 -183
- data/ext/graph/iterator_sequence.hh +0 -102
- data/ext/graph/undirected_dfs.hh +0 -226
- data/ext/graph/undirected_graph.hh +0 -421
- data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
- data/lib/roby/app/scripts/replay.rb +0 -31
- data/lib/roby/app/scripts/server.rb +0 -18
- data/lib/roby/basic_object.rb +0 -151
- data/lib/roby/config.rb +0 -14
- data/lib/roby/distributed.rb +0 -36
- data/lib/roby/distributed/base.rb +0 -448
- data/lib/roby/distributed/communication.rb +0 -875
- data/lib/roby/distributed/connection_space.rb +0 -616
- data/lib/roby/distributed/distributed_object.rb +0 -206
- data/lib/roby/distributed/drb.rb +0 -62
- data/lib/roby/distributed/notifications.rb +0 -531
- data/lib/roby/distributed/peer.rb +0 -555
- data/lib/roby/distributed/protocol.rb +0 -529
- data/lib/roby/distributed/proxy.rb +0 -343
- data/lib/roby/distributed/subscription.rb +0 -311
- data/lib/roby/distributed/transaction.rb +0 -498
- data/lib/roby/external_process_task.rb +0 -225
- data/lib/roby/graph.rb +0 -160
- data/lib/roby/log.rb +0 -3
- data/lib/roby/log/chronicle.rb +0 -303
- data/lib/roby/log/console.rb +0 -74
- data/lib/roby/log/data_stream.rb +0 -275
- data/lib/roby/log/dot.rb +0 -279
- data/lib/roby/log/event_stream.rb +0 -161
- data/lib/roby/log/file.rb +0 -396
- data/lib/roby/log/gui/basic_display.ui +0 -83
- data/lib/roby/log/gui/basic_display_ui.rb +0 -89
- data/lib/roby/log/gui/chronicle.rb +0 -26
- data/lib/roby/log/gui/chronicle_view.rb +0 -40
- data/lib/roby/log/gui/chronicle_view.ui +0 -70
- data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
- data/lib/roby/log/gui/data_displays.rb +0 -171
- data/lib/roby/log/gui/data_displays.ui +0 -155
- data/lib/roby/log/gui/data_displays_ui.rb +0 -146
- data/lib/roby/log/gui/notifications.rb +0 -26
- data/lib/roby/log/gui/relations.rb +0 -269
- data/lib/roby/log/gui/relations.ui +0 -123
- data/lib/roby/log/gui/relations_ui.rb +0 -120
- data/lib/roby/log/gui/relations_view.rb +0 -185
- data/lib/roby/log/gui/relations_view.ui +0 -149
- data/lib/roby/log/gui/relations_view_ui.rb +0 -144
- data/lib/roby/log/gui/replay.rb +0 -366
- data/lib/roby/log/gui/replay_controls.rb +0 -206
- data/lib/roby/log/gui/replay_controls.ui +0 -282
- data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
- data/lib/roby/log/gui/runtime.rb +0 -130
- data/lib/roby/log/hooks.rb +0 -186
- data/lib/roby/log/logger.rb +0 -203
- data/lib/roby/log/notifications.rb +0 -244
- data/lib/roby/log/plan_rebuilder.rb +0 -468
- data/lib/roby/log/relations.rb +0 -1084
- data/lib/roby/log/server.rb +0 -547
- data/lib/roby/log/sqlite.rb +0 -47
- data/lib/roby/log/timings.rb +0 -233
- data/lib/roby/plan-object.rb +0 -371
- data/lib/roby/planning.rb +0 -13
- data/lib/roby/planning/loops.rb +0 -309
- data/lib/roby/planning/model.rb +0 -1012
- data/lib/roby/planning/task.rb +0 -180
- data/lib/roby/query.rb +0 -655
- data/lib/roby/relations/conflicts.rb +0 -67
- data/lib/roby/relations/dependency.rb +0 -358
- data/lib/roby/relations/ensured.rb +0 -19
- data/lib/roby/relations/error_handling.rb +0 -22
- data/lib/roby/relations/events.rb +0 -7
- data/lib/roby/relations/executed_by.rb +0 -208
- data/lib/roby/relations/influence.rb +0 -10
- data/lib/roby/relations/planned_by.rb +0 -63
- data/lib/roby/state/information.rb +0 -55
- data/lib/roby/state/state.rb +0 -367
- data/lib/roby/task-operations.rb +0 -186
- data/lib/roby/task_index.rb +0 -80
- data/lib/roby/test/distributed.rb +0 -230
- data/lib/roby/test/tasks/simple_task.rb +0 -23
- data/lib/roby/transactions.rb +0 -507
- data/lib/roby/transactions/proxy.rb +0 -325
- data/plugins/fault_injection/History.txt +0 -4
- data/plugins/fault_injection/README.txt +0 -34
- data/plugins/fault_injection/Rakefile +0 -12
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +0 -52
- data/plugins/fault_injection/fault_injection.rb +0 -89
- data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
- data/plugins/subsystems/README.txt +0 -37
- data/plugins/subsystems/Rakefile +0 -13
- data/plugins/subsystems/app.rb +0 -182
- data/plugins/subsystems/test/app/README +0 -24
- data/plugins/subsystems/test/app/Rakefile +0 -8
- data/plugins/subsystems/test/app/config/app.yml +0 -71
- data/plugins/subsystems/test/app/config/init.rb +0 -12
- data/plugins/subsystems/test/app/config/roby.yml +0 -3
- data/plugins/subsystems/test/app/planners/main.rb +0 -20
- data/plugins/subsystems/test/app/scripts/distributed +0 -3
- data/plugins/subsystems/test/app/scripts/replay +0 -3
- data/plugins/subsystems/test/app/scripts/results +0 -3
- data/plugins/subsystems/test/app/scripts/run +0 -3
- data/plugins/subsystems/test/app/scripts/server +0 -3
- data/plugins/subsystems/test/app/scripts/shell +0 -3
- data/plugins/subsystems/test/app/scripts/test +0 -3
- data/plugins/subsystems/test/app/tasks/services.rb +0 -15
- data/plugins/subsystems/test/test_subsystems.rb +0 -78
- data/test/distributed/test_communication.rb +0 -195
- data/test/distributed/test_connection.rb +0 -284
- data/test/distributed/test_execution.rb +0 -378
- data/test/distributed/test_mixed_plan.rb +0 -341
- data/test/distributed/test_plan_notifications.rb +0 -238
- data/test/distributed/test_protocol.rb +0 -525
- data/test/distributed/test_query.rb +0 -106
- data/test/distributed/test_remote_plan.rb +0 -491
- data/test/distributed/test_transaction.rb +0 -466
- data/test/mockups/external_process +0 -28
- data/test/mockups/tasks.rb +0 -27
- data/test/planning/test_loops.rb +0 -432
- data/test/planning/test_model.rb +0 -427
- data/test/planning/test_task.rb +0 -126
- data/test/relations/test_conflicts.rb +0 -42
- data/test/relations/test_dependency.rb +0 -324
- data/test/relations/test_ensured.rb +0 -38
- data/test/relations/test_executed_by.rb +0 -224
- data/test/relations/test_planned_by.rb +0 -56
- data/test/suite_core.rb +0 -29
- data/test/suite_distributed.rb +0 -10
- data/test/suite_planning.rb +0 -4
- data/test/suite_relations.rb +0 -8
- data/test/tasks/test_external_process.rb +0 -126
- data/test/tasks/test_thread_task.rb +0 -70
- data/test/test_bgl.rb +0 -528
- data/test/test_event.rb +0 -969
- data/test/test_exceptions.rb +0 -591
- data/test/test_execution_engine.rb +0 -987
- data/test/test_gui.rb +0 -20
- data/test/test_interface.rb +0 -43
- data/test/test_log.rb +0 -125
- data/test/test_log_server.rb +0 -133
- data/test/test_plan.rb +0 -418
- data/test/test_query.rb +0 -424
- data/test/test_relations.rb +0 -260
- data/test/test_state.rb +0 -432
- data/test/test_support.rb +0 -16
- data/test/test_task.rb +0 -1181
- data/test/test_testcase.rb +0 -138
- data/test/test_transactions.rb +0 -610
- data/test/test_transactions_proxy.rb +0 -216
|
@@ -1,264 +1,425 @@
|
|
|
1
1
|
module Roby
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# Instead of
|
|
39
|
-
# Roby.engine.once { ... }
|
|
40
|
-
#
|
|
41
|
-
# Or
|
|
42
|
-
# engine.once { ... }
|
|
43
|
-
#
|
|
44
|
-
# Nonetheless, note that it breaks the object-orientation of the system and
|
|
45
|
-
# therefore won't work in cases where you want multiple execution engine to
|
|
46
|
-
# run in parallel.
|
|
47
|
-
#
|
|
48
|
-
# == Execution cycle
|
|
49
|
-
#
|
|
50
|
-
# link:../../images/roby_cycle_overview.png
|
|
51
|
-
#
|
|
52
|
-
# === Event propagation
|
|
53
|
-
# Event propagation is based on three main event relations:
|
|
54
|
-
#
|
|
55
|
-
# * Signal describes the commands that must be called when an event occurs. The
|
|
56
|
-
# signalled event command is called when the signalling events are emitted. If
|
|
57
|
-
# more than one event are signalling the same event in the same execution
|
|
58
|
-
# cycle, the command will be called only once
|
|
59
|
-
# * Forwarding describes the events that must be emitted whenever a source
|
|
60
|
-
# event is. It is to be used as a way to define event aliases (for instance
|
|
61
|
-
# 'stop' is an alias for 'success'), because a task is stopped when it has
|
|
62
|
-
# finished with success. Unlike with signals, if more than one event is
|
|
63
|
-
# forwarded to the same event in the same cycle, the target event will be
|
|
64
|
-
# emitted as many times as the incoming events.
|
|
65
|
-
# * the Precedence relation is a subset of the two preceding relations. It
|
|
66
|
-
# represents a partial ordering of the events that must be maintained during
|
|
67
|
-
# the propagation stage (i.e. a notion of causality).
|
|
68
|
-
#
|
|
69
|
-
# In the code, the followin procedure is followed: when a code fragment calls
|
|
70
|
-
# EventGenerator#emit or EventGenerator#call, the event is not emitted right
|
|
71
|
-
# away. Instead, it is queued in the set of "pending" events through the use of
|
|
72
|
-
# #add_event_propagation. The execution engine will then consider
|
|
73
|
-
# the pending set of events, choose the appropriate one by following the
|
|
74
|
-
# information contained in the Precedence relation and emit or call it. The
|
|
75
|
-
# actual call/emission is done through EventGenerator#call_without_propagation
|
|
76
|
-
# and EventGenerator#emit_without_propagation. The error checking (i.e. wether
|
|
77
|
-
# or not the emission/call is allowed) is done at both steps of propagation,
|
|
78
|
-
# because doing it late in the *_without_propagation versions would make the
|
|
79
|
-
# system more difficult to debug/test.
|
|
80
|
-
#
|
|
81
|
-
# === Error handling
|
|
82
|
-
# Each user-provided code fragment (i.e. event handlers, event commands,
|
|
83
|
-
# polling blocks, ...) are called into a specific error-gathering context.
|
|
84
|
-
# Once an exception is caught, it is added to the set of detected errors
|
|
85
|
-
# through #add_error. Those errors are handled after the
|
|
86
|
-
# event propagation cycle by the #propagate_exceptions
|
|
87
|
-
# method. It follows the following steps:
|
|
88
|
-
#
|
|
89
|
-
# * it removes all exceptions for which a running repair exists
|
|
90
|
-
# (#remove_inhibited_exceptions)
|
|
91
|
-
#
|
|
92
|
-
# * it checks for repairs declared through the
|
|
93
|
-
# Roby::TaskStructure::ErrorHandling relation. If one exists, the
|
|
94
|
-
# corresponding task is started, adds it to the set of running repairs
|
|
95
|
-
# (Plan#add_repair)
|
|
96
|
-
#
|
|
97
|
-
# For example, the following code fragment declares that +repair_task+
|
|
98
|
-
# is a plan repair for all errors involving the +low_battery+ event of the
|
|
99
|
-
# +moving+ task
|
|
100
|
-
#
|
|
101
|
-
# task.event(:moving).handle_with repair_task
|
|
102
|
-
#
|
|
103
|
-
# * it executes the exception handlers that have been declared for this
|
|
104
|
-
# exception by a call to Roby::Task.on_exception. The following code
|
|
105
|
-
# fragment defines an exception handler for LowBattery exceptions:
|
|
106
|
-
#
|
|
107
|
-
# class Moving
|
|
108
|
-
# on_exception(LowBattery) { |error| do_something_to_handle_that }
|
|
109
|
-
# end
|
|
2
|
+
# Exception wrapper used to report that multiple errors have been raised
|
|
3
|
+
# during a synchronous event processing call.
|
|
4
|
+
#
|
|
5
|
+
# See ExecutionEngine#process_events_synchronous for more information
|
|
6
|
+
class SynchronousEventProcessingMultipleErrors < RuntimeError
|
|
7
|
+
# Exceptions as gathered during propagation with {ExecutionEngine#task_m}
|
|
8
|
+
#
|
|
9
|
+
# @return [Array<ExecutionEngine::PropagationInfo>]
|
|
10
|
+
attr_reader :errors
|
|
11
|
+
|
|
12
|
+
# The set of underlying "real" (i.e. non-Roby) exceptions
|
|
13
|
+
#
|
|
14
|
+
# @return [Array<Exception>]
|
|
15
|
+
def original_exceptions
|
|
16
|
+
errors
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(errors)
|
|
20
|
+
@errors = errors
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def pretty_print(pp)
|
|
24
|
+
pp.text "Got #{errors.size} exceptions and #{original_exceptions.size} sub-exceptions"
|
|
25
|
+
pp.breakable
|
|
26
|
+
pp.seplist(errors.each_with_index) do |e, i|
|
|
27
|
+
Roby.flatten_exception(e).each_with_index do |sub_e, sub_i|
|
|
28
|
+
pp.breakable
|
|
29
|
+
pp.text "[#{i}.#{sub_i}] "
|
|
30
|
+
sub_e.pretty_print(pp)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @api private
|
|
110
37
|
#
|
|
111
|
-
#
|
|
112
|
-
# call #pass_exception to notify that it cannot handle the given
|
|
113
|
-
# exception.
|
|
38
|
+
# The core execution algorithm
|
|
114
39
|
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
# corresponding Roby::Plan instance. Plan-level exception handlers are
|
|
118
|
-
# defined by Plan#on_exception. Alternatively, for the main plan,
|
|
119
|
-
# Roby.on_exception can be also used.
|
|
40
|
+
# It is in charge of handling event and exception propagation, as well as
|
|
41
|
+
# running cleanup processes (e.g. garbage collection).
|
|
120
42
|
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
# #garbage_collect, so that they get killed and removed from the plan.
|
|
43
|
+
# The main method is {#process_events}. When executing a Roby application,
|
|
44
|
+
# it is called periodically by {#event_loop}.
|
|
124
45
|
#
|
|
46
|
+
# In addition, there is a special "synchronous" propagation mode that is
|
|
47
|
+
# used by {EventGenerator#call} and {EventGenerator#emit}. This mode is used
|
|
48
|
+
# when the event code is not executed within an engine, but from an
|
|
49
|
+
# imperative script, as in unit tests.
|
|
125
50
|
class ExecutionEngine
|
|
126
51
|
extend Logger::Hierarchy
|
|
127
|
-
|
|
52
|
+
include Logger::Hierarchy
|
|
53
|
+
include DRoby::EventLogging
|
|
54
|
+
|
|
55
|
+
# Whether this engine should use the OOB GC from the gctools gem
|
|
56
|
+
attr_predicate :use_oob_gc?, true
|
|
57
|
+
|
|
58
|
+
# Whether this engine should trace and log GC-related information
|
|
59
|
+
attr_predicate :profile_gc?, true
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
# Whether the engines should use the OOB GC from the gctools gem by
|
|
63
|
+
# default
|
|
64
|
+
#
|
|
65
|
+
# It is enabled in lib/roby.rb if the gctools are installed
|
|
66
|
+
attr_predicate :use_oob_gc?, true
|
|
67
|
+
end
|
|
128
68
|
|
|
129
69
|
# Create an execution engine acting on +plan+, using +control+ as the
|
|
130
70
|
# decision control object
|
|
131
71
|
#
|
|
132
|
-
#
|
|
133
|
-
|
|
72
|
+
# @param [ExecutablePlan] plan the plan on which this engine acts
|
|
73
|
+
# @param [DecisionControl] control the policy object, i.e. the object
|
|
74
|
+
# that embeds policies in cases where multiple reactions would be
|
|
75
|
+
# possible
|
|
76
|
+
# @param [DRoby::EventLogger] event_logger the logger that should be
|
|
77
|
+
# used to trace execution events. It is by default the same than the
|
|
78
|
+
# {#plan}'s. Pass a {DRoby::NullEventLogger} instance to disable event
|
|
79
|
+
# logging for this engine.
|
|
80
|
+
def initialize(plan, control: Roby::DecisionControl.new, event_logger: plan.event_logger)
|
|
134
81
|
@plan = plan
|
|
135
|
-
|
|
82
|
+
@event_logger = event_logger
|
|
83
|
+
|
|
84
|
+
@use_oob_gc = ExecutionEngine.use_oob_gc?
|
|
85
|
+
|
|
136
86
|
@control = control
|
|
87
|
+
@scheduler = Schedulers::Null.new(plan)
|
|
88
|
+
reset_thread_pool
|
|
89
|
+
@thread = Thread.current
|
|
137
90
|
|
|
91
|
+
@propagation = nil
|
|
138
92
|
@propagation_id = 0
|
|
93
|
+
@propagation_exceptions = nil
|
|
94
|
+
@application_exceptions = nil
|
|
139
95
|
@delayed_events = []
|
|
140
|
-
@process_once = Queue.new
|
|
141
96
|
@event_ordering = Array.new
|
|
142
97
|
@event_priorities = Hash.new
|
|
143
98
|
@propagation_handlers = []
|
|
99
|
+
@external_events_handlers = []
|
|
144
100
|
@at_cycle_end_handlers = Array.new
|
|
145
101
|
@process_every = Array.new
|
|
146
|
-
@
|
|
102
|
+
@waiting_work = Concurrent::Array.new
|
|
103
|
+
@emitted_events = Array.new
|
|
104
|
+
@disabled_handlers = Set.new
|
|
105
|
+
@exception_listeners = Array.new
|
|
106
|
+
|
|
107
|
+
@worker_threads_mtx = Mutex.new
|
|
108
|
+
@worker_threads = Array.new
|
|
109
|
+
@once_blocks = Queue.new
|
|
110
|
+
|
|
111
|
+
@pending_exceptions = Hash.new
|
|
147
112
|
|
|
148
|
-
|
|
113
|
+
each_cycle(&ExecutionEngine.method(:call_every))
|
|
149
114
|
|
|
150
|
-
|
|
115
|
+
@quit = 0
|
|
151
116
|
@allow_propagation = true
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
@last_stop_count = 0
|
|
117
|
+
@cycle_index = 0
|
|
118
|
+
@cycle_start = Time.now
|
|
119
|
+
@cycle_length = 0.1
|
|
120
|
+
@last_stop_count = 0
|
|
157
121
|
@finalizers = []
|
|
158
122
|
@gc_warning = true
|
|
159
|
-
|
|
123
|
+
|
|
124
|
+
refresh_relations
|
|
125
|
+
|
|
126
|
+
self.display_exceptions = true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Refresh the value of cached relations
|
|
130
|
+
#
|
|
131
|
+
# Some often-used relations are cached at {#initialize}, such as
|
|
132
|
+
# {#dependency_graph} and {#precedence_graph}. Call this when
|
|
133
|
+
# the actual graph objects have changed on the plan
|
|
134
|
+
def refresh_relations
|
|
135
|
+
@dependency_graph = plan.task_relation_graph_for(TaskStructure::Dependency)
|
|
136
|
+
@precedence_graph = plan.event_relation_graph_for(EventStructure::Precedence)
|
|
137
|
+
@signal_graph = plan.event_relation_graph_for(EventStructure::Signal)
|
|
138
|
+
@forward_graph = plan.event_relation_graph_for(EventStructure::Forwarding)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# A thread pool on which async work should be executed
|
|
142
|
+
#
|
|
143
|
+
# @see {#promise}
|
|
144
|
+
# @return [Concurrent::CachedThreadPool]
|
|
145
|
+
attr_reader :thread_pool
|
|
146
|
+
|
|
147
|
+
# Cached graph object for {EventStructure::Precedence}
|
|
148
|
+
#
|
|
149
|
+
# This is here for performance reasons, to avoid resolving the same
|
|
150
|
+
# graph over and over
|
|
151
|
+
attr_reader :precedence_graph
|
|
152
|
+
|
|
153
|
+
# Cached graph object for {EventStructure::Signal}
|
|
154
|
+
#
|
|
155
|
+
# This is here for performance reasons, to avoid resolving the same
|
|
156
|
+
# graph over and over
|
|
157
|
+
attr_reader :signal_graph
|
|
158
|
+
|
|
159
|
+
# Cached graph object for {EventStructure::Forward}
|
|
160
|
+
#
|
|
161
|
+
# This is here for performance reasons, to avoid resolving the same
|
|
162
|
+
# graph over and over
|
|
163
|
+
attr_reader :forward_graph
|
|
164
|
+
|
|
165
|
+
# Cached graph object for {TaskStructure::Dependency}
|
|
166
|
+
#
|
|
167
|
+
# This is here for performance reasons, to avoid resolving the same
|
|
168
|
+
# graph over and over
|
|
169
|
+
attr_reader :dependency_graph
|
|
160
170
|
|
|
161
171
|
# The Plan this engine is acting on
|
|
162
172
|
attr_accessor :plan
|
|
173
|
+
# The underlying {DRoby::EventLogger}
|
|
174
|
+
#
|
|
175
|
+
# It is usually the same than the {#plan}'s. Pass a
|
|
176
|
+
# {DRoby::NullEventLogger} at construction time to disable logging of
|
|
177
|
+
# execution events.
|
|
178
|
+
attr_accessor :event_logger
|
|
163
179
|
# The DecisionControl object associated with this engine
|
|
164
180
|
attr_accessor :control
|
|
165
181
|
# A numeric ID giving the count of the current propagation cycle
|
|
166
182
|
attr_reader :propagation_id
|
|
183
|
+
# The set of events that have been emitted within the last call to
|
|
184
|
+
# {#process_events} (i.e. the last execution of the event loop)
|
|
185
|
+
#
|
|
186
|
+
# @return [Array<Event>]
|
|
187
|
+
attr_reader :emitted_events
|
|
188
|
+
# The blocks that are currently listening to exceptions
|
|
189
|
+
# @return [Array<#call>]
|
|
190
|
+
attr_reader :exception_listeners
|
|
191
|
+
# Thread-safe queue to push work to the execution engine
|
|
192
|
+
#
|
|
193
|
+
# Do not access directly, use {#once} instead
|
|
194
|
+
#
|
|
195
|
+
# @return [Queue] blocks that should be executed at the beginning of the
|
|
196
|
+
# next execution cycle. It is the only thread safe way to queue work
|
|
197
|
+
# to be executed by the engine
|
|
198
|
+
attr_reader :once_blocks
|
|
199
|
+
|
|
200
|
+
# @api private
|
|
201
|
+
#
|
|
202
|
+
# Internal structure used to store a poll block definition provided to
|
|
203
|
+
# #every or #add_propagation_handler
|
|
204
|
+
#
|
|
205
|
+
# @!macro poll_options
|
|
206
|
+
# @option [Symbol] on_error if :raise (the default), pass exceptions
|
|
207
|
+
# to the caller. If :ignore, do nothing. If :disable, remove the
|
|
208
|
+
# poll block
|
|
209
|
+
class PollBlockDefinition
|
|
210
|
+
ON_ERROR = [:raise, :ignore, :disable]
|
|
211
|
+
|
|
212
|
+
attr_reader :description
|
|
213
|
+
attr_reader :handler
|
|
214
|
+
attr_reader :on_error
|
|
215
|
+
attr_predicate :late?, true
|
|
216
|
+
attr_predicate :once?, true
|
|
217
|
+
attr_predicate :disabled?, true
|
|
218
|
+
|
|
219
|
+
def id; handler.object_id end
|
|
220
|
+
|
|
221
|
+
def initialize(description, handler, on_error: :raise, late: false, once: false)
|
|
222
|
+
if !PollBlockDefinition::ON_ERROR.include?(on_error.to_sym)
|
|
223
|
+
raise ArgumentError, "invalid value '#{on_error} for the :on_error option. Accepted values are #{ON_ERROR.map(&:to_s).join(", ")}"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
@description, @handler, @on_error, @late, @once =
|
|
227
|
+
description, handler, on_error, late, once
|
|
228
|
+
@disabled = false
|
|
229
|
+
end
|
|
167
230
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
231
|
+
def to_s; "#<PollBlockDefinition: #{description} #{handler} on_error:#{on_error}>" end
|
|
232
|
+
|
|
233
|
+
def call(engine, *args)
|
|
234
|
+
handler.call(*args)
|
|
235
|
+
true
|
|
236
|
+
|
|
237
|
+
rescue Exception => e
|
|
238
|
+
if on_error == :raise
|
|
239
|
+
engine.add_framework_error(e, description)
|
|
240
|
+
return false
|
|
241
|
+
elsif on_error == :disable
|
|
242
|
+
engine.warn "propagation handler #{description} disabled because of the following error"
|
|
243
|
+
Roby.log_exception_with_backtrace(e, engine, :warn)
|
|
244
|
+
return false
|
|
245
|
+
elsif on_error == :ignore
|
|
246
|
+
engine.warn "ignored error from propagation handler #{description}"
|
|
247
|
+
Roby.log_exception_with_backtrace(e, engine, :warn)
|
|
248
|
+
return true
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Add/remove propagation handler methods that are shared between the
|
|
254
|
+
# instance and the class
|
|
255
|
+
module PropagationHandlerMethods
|
|
256
|
+
# Code blocks that get called at the beginning of each cycle
|
|
257
|
+
#
|
|
258
|
+
# @return [Array<PollBlockDefinition>]
|
|
259
|
+
attr_reader :external_events_handlers
|
|
260
|
+
# Code blocks that get called during propagation to handle some
|
|
261
|
+
# internal propagation mechanism
|
|
262
|
+
#
|
|
263
|
+
# @return [Array<PollBlockDefinition>]
|
|
172
264
|
attr_reader :propagation_handlers
|
|
173
265
|
|
|
174
|
-
#
|
|
175
|
-
# ExecutionEngine.add_propagation_handler { |plan| ... }
|
|
266
|
+
# @api private
|
|
176
267
|
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
# events they would call or emit are injected in the propagation
|
|
181
|
-
# process itself.
|
|
268
|
+
# Helper method that gets the arguments necessary top create a
|
|
269
|
+
# propagation handler, sanitizes and normalizes them, and returns
|
|
270
|
+
# both the propagation type and the {PollBlockDefinition} object
|
|
182
271
|
#
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
272
|
+
# @param [:external_events,:propagation] type whether the block should be registered as an
|
|
273
|
+
# :external_events block, processed at the beginning of the cycle,
|
|
274
|
+
# or a :propagation block, processed at each propagation loop.
|
|
275
|
+
# @param [String] description a string describing the block. It will
|
|
276
|
+
# be used when adding timepoints to the event log
|
|
277
|
+
# @param poll_options (see PollBlockDefinition#initialize)
|
|
278
|
+
def create_propagation_handler(type: :external_events, description: 'propagation handler', **poll_options, &block)
|
|
279
|
+
check_arity block, 1
|
|
280
|
+
handler = PollBlockDefinition.new(description, block, **poll_options)
|
|
281
|
+
|
|
282
|
+
if type == :external_events
|
|
283
|
+
if handler.late?
|
|
284
|
+
raise ArgumentError, "only :propagation handlers can be marked as 'late', the external event handlers cannot"
|
|
285
|
+
end
|
|
286
|
+
elsif type != :propagation
|
|
287
|
+
raise ArgumentError, "invalid value for the :type option. Expected :propagation or :external_events, got #{type}"
|
|
288
|
+
end
|
|
289
|
+
return type, handler
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# The propagation handlers are blocks that should be called at
|
|
293
|
+
# various places during propagation for all plans. These objects
|
|
294
|
+
# are called in propagation context, which means that the events
|
|
295
|
+
# they would call or emit are injected in the propagation process
|
|
296
|
+
# itself.
|
|
189
297
|
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
298
|
+
# @param [:propagation,:external_events] type defines when this block should be called. If
|
|
299
|
+
# :external_events, it is called only once at the beginning of each
|
|
300
|
+
# execution cycle. If :propagation, it is called once at the
|
|
301
|
+
# beginning of each cycle, as well as after each propagation step.
|
|
302
|
+
# The :late option also gives some control over when the handler is
|
|
303
|
+
# called when in propagation mode
|
|
304
|
+
# @option options [Boolean] once (false) if true, this handler will
|
|
305
|
+
# be removed just after its first execution
|
|
306
|
+
# @option options [Boolean] late (false) if true, the handler is
|
|
307
|
+
# called only when there are no events to propagate anymore.
|
|
308
|
+
# @option options [:raise,:ignore,:disable] on_error (:raise)
|
|
309
|
+
# controls what happens when the block raises an exception. If
|
|
310
|
+
# :raise, the error is registered as a framework error. If
|
|
311
|
+
# :ignore, it is completely ignored. If :disable, the handler
|
|
312
|
+
# will be disabled, i.e. not called anymore until #disabled?
|
|
193
313
|
#
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
314
|
+
# @return [Object] an ID object that can be passed to
|
|
315
|
+
# {#remove_propagation_handler}
|
|
316
|
+
def add_propagation_handler(type: :external_events, description: 'propagation handler', **poll_options, &block)
|
|
317
|
+
type, handler = create_propagation_handler(type: type, description: description, **poll_options, &block)
|
|
318
|
+
if type == :propagation
|
|
319
|
+
propagation_handlers << handler
|
|
320
|
+
elsif type == :external_events
|
|
321
|
+
external_events_handlers << handler
|
|
322
|
+
end
|
|
323
|
+
handler.id
|
|
200
324
|
end
|
|
201
325
|
|
|
202
326
|
# This method removes a propagation handler which has been added by
|
|
203
|
-
#
|
|
204
|
-
#
|
|
327
|
+
# {#add_propagation_handler}.
|
|
328
|
+
#
|
|
329
|
+
# @param [Object] id the block ID as returned by
|
|
330
|
+
# {#add_propagation_handler}
|
|
205
331
|
def remove_propagation_handler(id)
|
|
206
|
-
propagation_handlers.delete_if { |p| p.
|
|
332
|
+
propagation_handlers.delete_if { |p| p.id == id }
|
|
333
|
+
external_events_handlers.delete_if { |p| p.id == id }
|
|
207
334
|
nil
|
|
208
335
|
end
|
|
209
|
-
end
|
|
210
336
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
337
|
+
# Add a handler that is called at the beginning of the execution cycle
|
|
338
|
+
def at_cycle_begin(description: 'at_cycle_begin', **options, &block)
|
|
339
|
+
add_propagation_handler(description: description, type: :external_events, **options, &block)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Execute the given block at the beginning of each cycle, in propagation
|
|
343
|
+
# context.
|
|
344
|
+
#
|
|
345
|
+
# @return [Object] an ID that can be used to remove the handler using
|
|
346
|
+
# {#remove_propagation_handler}
|
|
347
|
+
def each_cycle(description: 'each_cycle', &block)
|
|
348
|
+
add_propagation_handler(description: description, &block)
|
|
349
|
+
end
|
|
350
|
+
end
|
|
216
351
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
# argument is the proc object to be added. In the second form, the
|
|
228
|
-
# block is taken the handler. In both cases, the method returns a value
|
|
229
|
-
# which can be used to remove the propagation handler later.
|
|
230
|
-
#
|
|
231
|
-
# See also #remove_propagation_handler
|
|
232
|
-
def add_propagation_handler(proc_obj = nil, &block)
|
|
233
|
-
proc_obj ||= block
|
|
234
|
-
check_arity proc_obj, 1
|
|
235
|
-
propagation_handlers << proc_obj
|
|
236
|
-
proc_obj.object_id
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# This method removes a propagation handler which has been added by
|
|
240
|
-
# #add_propagation_handler. THe +id+ value is the value returned by
|
|
241
|
-
# #add_propagation_handler. In its first form, the argument is the proc
|
|
242
|
-
# object to be added. In the second form, the block is taken the
|
|
243
|
-
# handler. In both cases, the method returns a value which can be used
|
|
244
|
-
# to remove the propagation handler later.
|
|
245
|
-
#
|
|
246
|
-
# See also #add_propagation_handler
|
|
352
|
+
@propagation_handlers = Array.new
|
|
353
|
+
@external_events_handlers = Array.new
|
|
354
|
+
extend PropagationHandlerMethods
|
|
355
|
+
include PropagationHandlerMethods
|
|
356
|
+
|
|
357
|
+
# Poll blocks that have been disabled because they raised an exception
|
|
358
|
+
#
|
|
359
|
+
# @return [Array<PollBlockDefinition>]
|
|
360
|
+
attr_reader :disabled_handlers
|
|
361
|
+
|
|
247
362
|
def remove_propagation_handler(id)
|
|
248
|
-
|
|
363
|
+
disabled_handlers.delete_if { |p| p.id == id }
|
|
364
|
+
super
|
|
249
365
|
nil
|
|
250
366
|
end
|
|
251
367
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
368
|
+
class JoinAllWaitingWorkTimeout < RuntimeError
|
|
369
|
+
attr_reader :waiting_work
|
|
370
|
+
def initialize(waiting_work)
|
|
371
|
+
@waiting_work = waiting_work.dup
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def pretty_print(pp)
|
|
375
|
+
pp.text "timed out in #join_all_waiting_work, #{waiting_work.size} promises waiting"
|
|
376
|
+
waiting_work.each do |w|
|
|
377
|
+
pp.breakable
|
|
378
|
+
pp.nest(2) do
|
|
379
|
+
if w.respond_to?(:state)
|
|
380
|
+
pp.text "[state=#{w.state}] "
|
|
381
|
+
end
|
|
382
|
+
w.pretty_print(pp)
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Waits for all obligations in {#waiting_work} to finish
|
|
389
|
+
def join_all_waiting_work(timeout: nil)
|
|
390
|
+
return [], PropagationInfo.new if waiting_work.empty?
|
|
391
|
+
deadline = if timeout
|
|
392
|
+
Time.now + timeout
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
finished = Array.new
|
|
396
|
+
propagation_info = PropagationInfo.new
|
|
397
|
+
begin
|
|
398
|
+
framework_errors = gather_framework_errors("#join_all_waiting_work", raise_caught_exceptions: false) do
|
|
399
|
+
next_steps = nil
|
|
400
|
+
event_errors = gather_errors do
|
|
401
|
+
next_steps = gather_propagation do
|
|
402
|
+
finished.concat(process_waiting_work)
|
|
403
|
+
blocks = Array.new
|
|
404
|
+
while !once_blocks.empty?
|
|
405
|
+
blocks << once_blocks.pop.last
|
|
406
|
+
end
|
|
407
|
+
call_poll_blocks(blocks)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
this_propagation = propagate_events_and_errors(next_steps, event_errors, garbage_collect_pass: false)
|
|
412
|
+
propagation_info.merge(this_propagation)
|
|
413
|
+
end
|
|
414
|
+
propagation_info.add_framework_errors(framework_errors)
|
|
415
|
+
|
|
416
|
+
Thread.pass
|
|
417
|
+
has_scheduled_promises = has_waiting_work?
|
|
418
|
+
if deadline && (Time.now > deadline) && has_scheduled_promises
|
|
419
|
+
raise JoinAllWaitingWorkTimeout.new(waiting_work)
|
|
420
|
+
end
|
|
421
|
+
end while has_waiting_work?
|
|
422
|
+
return finished, propagation_info
|
|
262
423
|
end
|
|
263
424
|
|
|
264
425
|
# The scheduler is the object which handles non-generic parts of the
|
|
@@ -267,10 +428,25 @@ def each_cycle(&block)
|
|
|
267
428
|
# events.
|
|
268
429
|
#
|
|
269
430
|
# See Schedulers::Basic
|
|
270
|
-
|
|
431
|
+
attr_reader :scheduler
|
|
432
|
+
|
|
433
|
+
def scheduler=(scheduler)
|
|
434
|
+
if !scheduler
|
|
435
|
+
raise ArgumentError, "cannot set the scheduler to nil. You can disable the current scheduler with .enabled = false instead, or set it to Schedulers::Null.new"
|
|
436
|
+
end
|
|
437
|
+
@scheduler = scheduler
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def gathering?
|
|
441
|
+
Roby.warn_deprecated "#gathering? is deprecated, use #in_propagation_context? instead"
|
|
442
|
+
in_propagation_context?
|
|
443
|
+
end
|
|
271
444
|
|
|
272
|
-
# True if we are
|
|
273
|
-
|
|
445
|
+
# True if we are within a propagation context (i.e. within event
|
|
446
|
+
# processing)
|
|
447
|
+
def in_propagation_context?
|
|
448
|
+
!!@propagation
|
|
449
|
+
end
|
|
274
450
|
|
|
275
451
|
attr_predicate :allow_propagation
|
|
276
452
|
|
|
@@ -279,7 +455,7 @@ def gathering?; !!@propagation end
|
|
|
279
455
|
attr_reader :propagation_sources
|
|
280
456
|
# The set of events extracted from #sources
|
|
281
457
|
def propagation_source_events
|
|
282
|
-
result =
|
|
458
|
+
result = Set.new
|
|
283
459
|
for ev in @propagation_sources
|
|
284
460
|
if ev.respond_to?(:generator)
|
|
285
461
|
result << ev
|
|
@@ -290,7 +466,7 @@ def propagation_source_events
|
|
|
290
466
|
|
|
291
467
|
# The set of generators extracted from #sources
|
|
292
468
|
def propagation_source_generators
|
|
293
|
-
result =
|
|
469
|
+
result = Set.new
|
|
294
470
|
for ev in @propagation_sources
|
|
295
471
|
result << if ev.respond_to?(:generator)
|
|
296
472
|
ev.generator
|
|
@@ -334,10 +510,28 @@ def execute_delayed_events
|
|
|
334
510
|
end
|
|
335
511
|
end
|
|
336
512
|
|
|
513
|
+
# Called by EventGenerator when an event became unreachable
|
|
514
|
+
def unreachable_event(event)
|
|
515
|
+
delayed_events.delete_if { |_, _, _, signalled, _| signalled == event }
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Called by #plan when a task has been finalized
|
|
519
|
+
def finalized_task(task)
|
|
520
|
+
@pending_exceptions.delete(task)
|
|
521
|
+
end
|
|
522
|
+
|
|
337
523
|
# Called by #plan when an event has been finalized
|
|
338
524
|
def finalized_event(event)
|
|
339
|
-
|
|
340
|
-
|
|
525
|
+
if @propagation
|
|
526
|
+
@propagation.delete(event)
|
|
527
|
+
end
|
|
528
|
+
event.unreachable!("finalized", plan)
|
|
529
|
+
# since the event is already finalized,
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# Returns true if some events are queued
|
|
533
|
+
def has_queued_events?
|
|
534
|
+
!@propagation.empty?
|
|
341
535
|
end
|
|
342
536
|
|
|
343
537
|
# Sets up a propagation context, yielding the block in it. During this
|
|
@@ -348,88 +542,152 @@ def finalized_event(event)
|
|
|
348
542
|
# where the two +_sources+ are arrays of the form
|
|
349
543
|
# [[source, context], ...]
|
|
350
544
|
#
|
|
351
|
-
# The method returns the resulting hash. Use #
|
|
545
|
+
# The method returns the resulting hash. Use #in_propagation_context? to know if the
|
|
352
546
|
# current engine is in a propagation context, and #add_event_propagation
|
|
353
547
|
# to add a new entry to this set.
|
|
354
548
|
def gather_propagation(initial_set = Hash.new)
|
|
355
|
-
raise InternalError, "nested call to #gather_propagation" if
|
|
356
|
-
@propagation = initial_set
|
|
549
|
+
raise InternalError, "nested call to #gather_propagation" if in_propagation_context?
|
|
357
550
|
|
|
358
|
-
|
|
551
|
+
old_allow_propagation, @allow_propagation = @allow_propagation, true
|
|
359
552
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
553
|
+
# The ensure clause must NOT apply to the recursive check above.
|
|
554
|
+
# Otherwise, we end up resetting @propagation_exceptions to nil,
|
|
555
|
+
# which wreaks havoc
|
|
556
|
+
begin
|
|
557
|
+
@propagation = initial_set
|
|
558
|
+
@propagation_sources = nil
|
|
559
|
+
@propagation_step_id = 0
|
|
364
560
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
561
|
+
before = @propagation
|
|
562
|
+
propagation_context([]) do
|
|
563
|
+
yield
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
result, @propagation = @propagation, nil
|
|
567
|
+
return result
|
|
568
|
+
ensure
|
|
569
|
+
@propagation = nil
|
|
570
|
+
@allow_propagation = old_allow_propagation
|
|
371
571
|
end
|
|
372
572
|
end
|
|
373
573
|
|
|
374
|
-
#
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
|
|
574
|
+
# Register a LocalizedError for future propagation
|
|
575
|
+
#
|
|
576
|
+
# This method must be called in a error-gathering context (i.e.
|
|
577
|
+
# {#gather_error}.
|
|
578
|
+
#
|
|
579
|
+
# @param [#to_execution_exception] e the exception
|
|
580
|
+
# @raise [NotPropagationContext] raised if called outside
|
|
581
|
+
# {#gather_error}
|
|
582
|
+
def add_error(e, propagate_through: nil)
|
|
583
|
+
plan_exception = e.to_execution_exception
|
|
378
584
|
if @propagation_exceptions
|
|
379
|
-
plan_exception
|
|
380
|
-
@propagation_exceptions << plan_exception
|
|
585
|
+
@propagation_exceptions << [plan_exception, propagate_through]
|
|
381
586
|
else
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
else
|
|
385
|
-
add_framework_error(e, "error outside error handling")
|
|
386
|
-
end
|
|
587
|
+
Roby.log_exception_with_backtrace(e, self, :fatal)
|
|
588
|
+
raise NotPropagationContext, "#add_error called outside an error-gathering context (#add_error)"
|
|
387
589
|
end
|
|
388
590
|
end
|
|
389
591
|
|
|
390
|
-
# Yields to the block
|
|
391
|
-
#
|
|
392
|
-
|
|
592
|
+
# Yields to the block and registers any raised exception using
|
|
593
|
+
# {#add_framework_error}
|
|
594
|
+
#
|
|
595
|
+
# If the method is called within an exception-gathering context (either
|
|
596
|
+
# {#process_events} or {#gather_framework_errors} itself), nothing else
|
|
597
|
+
# is done. Otherwise, {#process_pending_application_exceptions} is
|
|
598
|
+
# called to re-raise any caught exception
|
|
599
|
+
def gather_framework_errors(source, raise_caught_exceptions: true)
|
|
600
|
+
if @application_exceptions
|
|
601
|
+
recursive_error_gathering_context = true
|
|
602
|
+
else
|
|
603
|
+
@application_exceptions = []
|
|
604
|
+
end
|
|
605
|
+
|
|
393
606
|
yield
|
|
607
|
+
|
|
608
|
+
if !recursive_error_gathering_context && !raise_caught_exceptions
|
|
609
|
+
clear_application_exceptions
|
|
610
|
+
end
|
|
394
611
|
rescue Exception => e
|
|
395
612
|
add_framework_error(e, source)
|
|
613
|
+
if !recursive_error_gathering_context && !raise_caught_exceptions
|
|
614
|
+
clear_application_exceptions
|
|
615
|
+
end
|
|
616
|
+
ensure
|
|
617
|
+
if !recursive_error_gathering_context && raise_caught_exceptions
|
|
618
|
+
process_pending_application_exceptions
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def process_pending_application_exceptions(application_errors = clear_application_exceptions,
|
|
623
|
+
raise_framework_errors: Roby.app.abort_on_application_exception?)
|
|
624
|
+
|
|
625
|
+
# We don't aggregate exceptions, so report them all and raise one
|
|
626
|
+
if display_exceptions?
|
|
627
|
+
application_errors.each do |error, source|
|
|
628
|
+
if !error.kind_of?(Interrupt)
|
|
629
|
+
fatal "Application error in #{source}"
|
|
630
|
+
Roby.log_exception_with_backtrace(error, self, :fatal)
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
error, source = application_errors.find do |error, _|
|
|
636
|
+
raise_framework_errors || error.kind_of?(SignalException)
|
|
637
|
+
end
|
|
638
|
+
if error
|
|
639
|
+
raise error, "in #{source}: #{error.message}", error.backtrace
|
|
640
|
+
end
|
|
396
641
|
end
|
|
397
642
|
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
#
|
|
643
|
+
# Registers the given error and a description of its source in the list
|
|
644
|
+
# of application/framework errors
|
|
645
|
+
#
|
|
646
|
+
# It must be called within an exception-gathering context, that is
|
|
647
|
+
# either within {#process_events}, or within {#gather_framework_errors}
|
|
648
|
+
#
|
|
649
|
+
# These errors will terminate the event loop
|
|
650
|
+
#
|
|
651
|
+
# @param [Exception] error
|
|
652
|
+
# @param [Object] source
|
|
402
653
|
def add_framework_error(error, source)
|
|
403
654
|
if @application_exceptions
|
|
404
655
|
@application_exceptions << [error, source]
|
|
405
|
-
elsif Roby.app.abort_on_application_exception? || error.kind_of?(SignalException)
|
|
406
|
-
raise error, "in #{source}: #{error.message}", error.backtrace
|
|
407
656
|
else
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
Roby.warn line
|
|
411
|
-
end
|
|
657
|
+
Roby.log_exception_with_backtrace(error, self, :fatal)
|
|
658
|
+
raise NotPropagationContext, "#add_framework_error called outside an exception-gathering context"
|
|
412
659
|
end
|
|
413
660
|
end
|
|
414
661
|
|
|
415
662
|
# Sets the source_event and source_generator variables according
|
|
416
663
|
# to +source+. +source+ is the +from+ argument of #add_event_propagation
|
|
417
664
|
def propagation_context(sources)
|
|
418
|
-
|
|
665
|
+
current_sources = @propagation_sources
|
|
666
|
+
raise InternalError, "not in a gathering context in #propagation_context" unless in_propagation_context?
|
|
419
667
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
668
|
+
@propagation_sources = sources
|
|
669
|
+
yield
|
|
670
|
+
ensure
|
|
671
|
+
@propagation_sources = current_sources
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def has_propagation_for?(target)
|
|
675
|
+
@propagation && @propagation.has_key?(target)
|
|
676
|
+
end
|
|
426
677
|
|
|
427
|
-
|
|
678
|
+
# Queue a signal to be propagated
|
|
679
|
+
def queue_signal(sources, target, context, timespec)
|
|
680
|
+
add_event_propagation(false, sources, target, context, timespec)
|
|
681
|
+
end
|
|
428
682
|
|
|
429
|
-
|
|
430
|
-
|
|
683
|
+
# Queue a forwarding to be propagated
|
|
684
|
+
def queue_forward(sources, target, context, timespec)
|
|
685
|
+
add_event_propagation(true, sources, target, context, timespec)
|
|
431
686
|
end
|
|
432
687
|
|
|
688
|
+
PENDING_PROPAGATION_FORWARD = 1
|
|
689
|
+
PENDING_PROPAGATION_SIGNAL = 2
|
|
690
|
+
|
|
433
691
|
# Adds a propagation to the next propagation step: it registers a
|
|
434
692
|
# propagation step to be performed between +source+ and +target+ with
|
|
435
693
|
# the given +context+. If +is_forward+ is true, the propagation will be
|
|
@@ -439,20 +697,122 @@ def propagation_context(sources)
|
|
|
439
697
|
# calling the target event.
|
|
440
698
|
#
|
|
441
699
|
# See #gather_propagation
|
|
442
|
-
def add_event_propagation(is_forward,
|
|
700
|
+
def add_event_propagation(is_forward, sources, target, context, timespec)
|
|
443
701
|
if target.plan != plan
|
|
444
702
|
raise Roby::EventNotExecutable.new(target), "#{target} not in executed plan"
|
|
445
703
|
end
|
|
446
704
|
|
|
447
|
-
|
|
448
|
-
|
|
705
|
+
target.pending(sources.find_all { |ev| ev.kind_of?(Event) })
|
|
706
|
+
|
|
707
|
+
@propagation_step_id += 1
|
|
708
|
+
target_info = (@propagation[target] ||= [@propagation_step_id, [], []])
|
|
709
|
+
step = target_info[is_forward ? PENDING_PROPAGATION_FORWARD : PENDING_PROPAGATION_SIGNAL]
|
|
710
|
+
if sources.empty?
|
|
711
|
+
step << nil << context << timespec
|
|
712
|
+
else
|
|
713
|
+
sources.each do |ev|
|
|
714
|
+
step << ev << context << timespec
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# Whether a forward matching this signature is currently pending
|
|
720
|
+
def has_pending_forward?(from, to, expected_context)
|
|
721
|
+
if pending = @propagation[to]
|
|
722
|
+
pending[PENDING_PROPAGATION_FORWARD].each_slice(3).any? do |event, context, timespec|
|
|
723
|
+
(from === event.generator) && (expected_context === context)
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# Whether a signal matching this signature is currently pending
|
|
729
|
+
def has_pending_signal?(from, to, expected_context)
|
|
730
|
+
if pending = @propagation[to]
|
|
731
|
+
pending[PENDING_PROPAGATION_SIGNAL].each_slice(3).any? do |event, context, timespec|
|
|
732
|
+
(from === event.generator) && (expected_context === context)
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
# Helper that calls the propagation handlers in +propagation_handlers+
|
|
738
|
+
# (which are expected to be instances of PollBlockDefinition) and
|
|
739
|
+
# handles the errors according of each handler's policy
|
|
740
|
+
def call_poll_blocks(blocks, late = false)
|
|
741
|
+
blocks.delete_if do |handler|
|
|
742
|
+
if handler.disabled? || (handler.late? ^ late)
|
|
743
|
+
next
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
log_timepoint_group handler.description do
|
|
747
|
+
if !handler.call(self, plan)
|
|
748
|
+
handler.disabled = true
|
|
749
|
+
end
|
|
750
|
+
end
|
|
751
|
+
handler.once?
|
|
752
|
+
end
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
# Dispatch {#once_blocks} to the other handler sets for further
|
|
756
|
+
# processing
|
|
757
|
+
def process_once_blocks
|
|
758
|
+
while !once_blocks.empty?
|
|
759
|
+
type, block = once_blocks.pop
|
|
760
|
+
if type == :external_events
|
|
761
|
+
external_events_handlers << block
|
|
762
|
+
else
|
|
763
|
+
propagation_handlers << block
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# Gather the events that come out of this plan manager
|
|
769
|
+
def gather_external_events
|
|
770
|
+
process_once_blocks
|
|
771
|
+
gather_framework_errors('delayed events') { execute_delayed_events }
|
|
772
|
+
call_poll_blocks(self.class.external_events_handlers)
|
|
773
|
+
call_poll_blocks(self.external_events_handlers)
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def call_propagation_handlers
|
|
777
|
+
process_once_blocks
|
|
778
|
+
if scheduler.enabled?
|
|
779
|
+
gather_framework_errors('scheduler') do
|
|
780
|
+
scheduler.initial_events
|
|
781
|
+
log_timepoint 'scheduler'
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
call_poll_blocks(self.class.propagation_handlers, false)
|
|
785
|
+
call_poll_blocks(self.propagation_handlers, false)
|
|
786
|
+
|
|
787
|
+
if !has_queued_events?
|
|
788
|
+
call_poll_blocks(self.class.propagation_handlers, true)
|
|
789
|
+
call_poll_blocks(self.propagation_handlers, true)
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def gathering_errors?
|
|
794
|
+
!!@propagation_exceptions
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
# Executes the given block while gathering errors, and returns the
|
|
798
|
+
# errors that have been declared with #add_error
|
|
799
|
+
#
|
|
800
|
+
# @return [Array<ExecutionException>]
|
|
801
|
+
def gather_errors
|
|
802
|
+
if @propagation_exceptions
|
|
803
|
+
raise InternalError, "recursive call to #gather_errors"
|
|
804
|
+
end
|
|
449
805
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
806
|
+
# The ensure clause must NOT apply to the recursive check above.
|
|
807
|
+
# Otherwise, we end up resetting @propagation_exceptions to nil,
|
|
808
|
+
# which wreaks havoc
|
|
809
|
+
begin
|
|
810
|
+
@propagation_exceptions = []
|
|
811
|
+
yield
|
|
812
|
+
@propagation_exceptions
|
|
453
813
|
|
|
454
|
-
|
|
455
|
-
|
|
814
|
+
ensure
|
|
815
|
+
@propagation_exceptions = nil
|
|
456
816
|
end
|
|
457
817
|
end
|
|
458
818
|
|
|
@@ -463,54 +823,99 @@ def add_event_propagation(is_forward, from, target, context, timespec)
|
|
|
463
823
|
# events we should consider as already emitted in the following propagation.
|
|
464
824
|
# +seeds+ si a list of procs which should be called to initiate the propagation
|
|
465
825
|
# (i.e. build an initial set of events)
|
|
466
|
-
def
|
|
467
|
-
|
|
468
|
-
|
|
826
|
+
def event_propagation_phase(initial_events, propagation_info)
|
|
827
|
+
@propagation_id += 1
|
|
828
|
+
|
|
829
|
+
gather_errors do
|
|
830
|
+
next_steps = initial_events
|
|
831
|
+
while !next_steps.empty?
|
|
832
|
+
while !next_steps.empty?
|
|
833
|
+
next_steps = event_propagation_step(next_steps, propagation_info)
|
|
834
|
+
end
|
|
835
|
+
next_steps = gather_propagation { call_propagation_handlers }
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
# Compute errors in plan and handle the results
|
|
841
|
+
def error_handling_phase(events_errors)
|
|
842
|
+
# Do the exception handling phase
|
|
843
|
+
errors = compute_errors(events_errors)
|
|
844
|
+
notify_about_error_handling_results(errors)
|
|
845
|
+
|
|
846
|
+
# nonfatal errors are only notified. Fatal errors (kill_tasks) are
|
|
847
|
+
# handled in the propagation loop during garbage collection. Only
|
|
848
|
+
# the free events errors have to be handled here.
|
|
849
|
+
errors.free_events_errors.each do |exception, generators|
|
|
850
|
+
generators.each { |g| g.unreachable!(exception.exception) }
|
|
851
|
+
end
|
|
852
|
+
return errors
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
# Compute the set of unhandled fatal exceptions
|
|
856
|
+
def compute_kill_tasks_for_unhandled_fatal_errors(fatal_errors)
|
|
857
|
+
kill_tasks = fatal_errors.inject(Set.new) do |tasks, (exception, affected_tasks)|
|
|
858
|
+
tasks.merge(affected_tasks)
|
|
469
859
|
end
|
|
860
|
+
# Tasks might have been finalized during exception handling, filter
|
|
861
|
+
# those out
|
|
862
|
+
kill_tasks.find_all(&:plan)
|
|
863
|
+
end
|
|
470
864
|
|
|
471
|
-
|
|
472
|
-
|
|
865
|
+
# Issue the warning message and log notifications related to tasks being
|
|
866
|
+
# killed because of unhandled fatal exceptions
|
|
867
|
+
def notify_about_error_handling_results(errors)
|
|
868
|
+
kill_tasks, fatal_errors, nonfatal_errors, free_events_errors, handled_errors =
|
|
869
|
+
errors.kill_tasks, errors.fatal_errors, errors.nonfatal_errors, errors.free_events_errors, errors.handled_errors
|
|
473
870
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
gather_framework_errors('distributed events') { Roby::Distributed.process_pending }
|
|
478
|
-
gather_framework_errors('delayed events') { execute_delayed_events }
|
|
479
|
-
while !process_once.empty?
|
|
480
|
-
p = process_once.pop
|
|
481
|
-
gather_framework_errors("'once' block #{p}") { p.call }
|
|
482
|
-
end
|
|
483
|
-
if seeds
|
|
484
|
-
for s in seeds
|
|
485
|
-
gather_framework_errors("seed #{s}") { s.call }
|
|
486
|
-
end
|
|
871
|
+
if !nonfatal_errors.empty?
|
|
872
|
+
if display_exceptions?
|
|
873
|
+
warn "#{nonfatal_errors.size} unhandled non-fatal exceptions"
|
|
487
874
|
end
|
|
488
|
-
|
|
489
|
-
|
|
875
|
+
nonfatal_errors.each do |exception, tasks|
|
|
876
|
+
notify_exception(EXCEPTION_NONFATAL, exception, tasks)
|
|
490
877
|
end
|
|
491
|
-
|
|
492
|
-
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
if !handled_errors.empty?
|
|
881
|
+
if display_exceptions?
|
|
882
|
+
warn "#{handled_errors.size} handled errors"
|
|
493
883
|
end
|
|
494
|
-
|
|
495
|
-
|
|
884
|
+
handled_errors.each do |exception, tasks|
|
|
885
|
+
notify_exception(EXCEPTION_HANDLED, exception, tasks)
|
|
496
886
|
end
|
|
497
887
|
end
|
|
498
888
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
889
|
+
if !free_events_errors.empty?
|
|
890
|
+
if display_exceptions?
|
|
891
|
+
warn "#{free_events_errors.size} free event exceptions"
|
|
892
|
+
end
|
|
893
|
+
free_events_errors.each do |exception, events|
|
|
894
|
+
notify_exception(EXCEPTION_FREE_EVENT, exception, events)
|
|
895
|
+
end
|
|
896
|
+
end
|
|
503
897
|
|
|
504
|
-
|
|
505
|
-
|
|
898
|
+
if !fatal_errors.empty?
|
|
899
|
+
if display_exceptions?
|
|
900
|
+
warn "#{fatal_errors.size} unhandled fatal exceptions, involving #{kill_tasks.size} tasks that will be forcefully killed"
|
|
901
|
+
end
|
|
902
|
+
fatal_errors.each do |exception, tasks|
|
|
903
|
+
notify_exception(EXCEPTION_FATAL, exception, tasks)
|
|
904
|
+
end
|
|
905
|
+
if display_exceptions?
|
|
906
|
+
kill_tasks.each do |task|
|
|
907
|
+
log_pp :warn, task
|
|
908
|
+
end
|
|
909
|
+
end
|
|
910
|
+
end
|
|
506
911
|
end
|
|
507
912
|
|
|
508
913
|
# Validates +timespec+ as a delay specification. A valid delay
|
|
509
914
|
# specification is either +nil+ or a hash, in which case two forms are
|
|
510
915
|
# possible:
|
|
511
916
|
#
|
|
512
|
-
# :
|
|
513
|
-
# :
|
|
917
|
+
# at: absolute_time
|
|
918
|
+
# delay: number
|
|
514
919
|
#
|
|
515
920
|
def self.validate_timespec(timespec)
|
|
516
921
|
if timespec
|
|
@@ -549,22 +954,28 @@ def self.make_delay(timeref, source, target, timespec)
|
|
|
549
954
|
def next_event(pending)
|
|
550
955
|
# this variable is 2 if selected_event is being forwarded, 1 if it
|
|
551
956
|
# is both forwarded and signalled and 0 if it is only signalled
|
|
552
|
-
priority, selected_event = nil
|
|
957
|
+
priority, step_id, selected_event = nil
|
|
553
958
|
for propagation_step in pending
|
|
554
959
|
target_event = propagation_step[0]
|
|
555
|
-
forwards, signals = *propagation_step[1]
|
|
556
|
-
target_priority = if forwards && signals then
|
|
557
|
-
elsif
|
|
558
|
-
else
|
|
960
|
+
target_step_id, forwards, signals = *propagation_step[1]
|
|
961
|
+
target_priority = if forwards.empty? && signals.empty? then 2
|
|
962
|
+
elsif forwards.empty? then 0
|
|
963
|
+
else 1
|
|
559
964
|
end
|
|
560
965
|
|
|
561
966
|
do_select = if selected_event
|
|
562
|
-
if
|
|
967
|
+
if precedence_graph.reachable?(selected_event, target_event)
|
|
563
968
|
false
|
|
564
|
-
elsif
|
|
969
|
+
elsif precedence_graph.reachable?(target_event, selected_event)
|
|
970
|
+
true
|
|
971
|
+
elsif priority < target_priority
|
|
565
972
|
true
|
|
973
|
+
elsif priority == target_priority
|
|
974
|
+
# If they are of the same priority, handle
|
|
975
|
+
# earlier events first
|
|
976
|
+
step_id > target_step_id
|
|
566
977
|
else
|
|
567
|
-
|
|
978
|
+
false
|
|
568
979
|
end
|
|
569
980
|
else
|
|
570
981
|
true
|
|
@@ -573,6 +984,7 @@ def next_event(pending)
|
|
|
573
984
|
if do_select
|
|
574
985
|
selected_event = target_event
|
|
575
986
|
priority = target_priority
|
|
987
|
+
step_id = target_step_id
|
|
576
988
|
end
|
|
577
989
|
end
|
|
578
990
|
[selected_event, *pending.delete(selected_event)]
|
|
@@ -595,7 +1007,7 @@ def next_event(pending)
|
|
|
595
1007
|
def prepare_propagation(target, is_forward, info)
|
|
596
1008
|
timeref = Time.now
|
|
597
1009
|
|
|
598
|
-
source_events, source_generators, context =
|
|
1010
|
+
source_events, source_generators, context = Set.new, Set.new, []
|
|
599
1011
|
|
|
600
1012
|
delayed = true
|
|
601
1013
|
info.each_slice(3) do |src, ctxt, time|
|
|
@@ -622,7 +1034,7 @@ def prepare_propagation(target, is_forward, info)
|
|
|
622
1034
|
end
|
|
623
1035
|
|
|
624
1036
|
unless delayed
|
|
625
|
-
[source_events, source_generators,
|
|
1037
|
+
[source_events, source_generators, context]
|
|
626
1038
|
end
|
|
627
1039
|
end
|
|
628
1040
|
|
|
@@ -638,26 +1050,34 @@ def prepare_propagation(target, is_forward, info)
|
|
|
638
1050
|
# The method returns the next set of pending emissions and calls, adding
|
|
639
1051
|
# the forwardings and signals that the propagation of the considered event
|
|
640
1052
|
# have added.
|
|
641
|
-
def event_propagation_step(current_step)
|
|
642
|
-
signalled, forward_info, call_info = next_event(current_step)
|
|
1053
|
+
def event_propagation_step(current_step, propagation_info)
|
|
1054
|
+
signalled, step_id, forward_info, call_info = next_event(current_step)
|
|
643
1055
|
|
|
644
1056
|
next_step = nil
|
|
645
|
-
if call_info
|
|
646
|
-
source_events, source_generators, context =
|
|
1057
|
+
if !call_info.empty?
|
|
1058
|
+
source_events, source_generators, context =
|
|
1059
|
+
prepare_propagation(signalled, false, call_info)
|
|
647
1060
|
if source_events
|
|
648
|
-
|
|
649
|
-
source_ev.generator.signalling(source_ev, signalled)
|
|
650
|
-
end
|
|
1061
|
+
log(:generator_propagate_events, false, source_events, signalled)
|
|
651
1062
|
|
|
652
1063
|
if signalled.self_owned?
|
|
653
1064
|
next_step = gather_propagation(current_step) do
|
|
654
|
-
propagation_context(source_events | source_generators) do
|
|
1065
|
+
propagation_context(source_events | source_generators) do
|
|
655
1066
|
begin
|
|
1067
|
+
propagation_info.add_generator_call(signalled)
|
|
656
1068
|
signalled.call_without_propagation(context)
|
|
657
1069
|
rescue Roby::LocalizedError => e
|
|
658
|
-
signalled.
|
|
1070
|
+
if signalled.command_emitted?
|
|
1071
|
+
add_error(e)
|
|
1072
|
+
else
|
|
1073
|
+
signalled.emit_failed(e)
|
|
1074
|
+
end
|
|
659
1075
|
rescue Exception => e
|
|
660
|
-
signalled.
|
|
1076
|
+
if signalled.command_emitted?
|
|
1077
|
+
add_error(Roby::CommandFailed.new(e, signalled))
|
|
1078
|
+
else
|
|
1079
|
+
signalled.emit_failed(Roby::CommandFailed.new(e, signalled))
|
|
1080
|
+
end
|
|
661
1081
|
end
|
|
662
1082
|
end
|
|
663
1083
|
end
|
|
@@ -666,28 +1086,33 @@ def event_propagation_step(current_step)
|
|
|
666
1086
|
|
|
667
1087
|
if forward_info
|
|
668
1088
|
next_step ||= Hash.new
|
|
669
|
-
next_step[signalled] ||= []
|
|
670
|
-
|
|
671
|
-
next_step[signalled][0].concat forward_info
|
|
1089
|
+
target_info = (next_step[signalled] ||= [@propagation_step_id += 1, [], []])
|
|
1090
|
+
target_info[PENDING_PROPAGATION_FORWARD].concat(forward_info)
|
|
672
1091
|
end
|
|
673
1092
|
|
|
674
|
-
elsif forward_info
|
|
675
|
-
source_events, source_generators, context =
|
|
1093
|
+
elsif !forward_info.empty?
|
|
1094
|
+
source_events, source_generators, context =
|
|
1095
|
+
prepare_propagation(signalled, true, forward_info)
|
|
676
1096
|
if source_events
|
|
677
|
-
|
|
678
|
-
source_ev.generator.forwarding(source_ev, signalled)
|
|
679
|
-
end
|
|
1097
|
+
log(:generator_propagate_events, true, source_events, signalled)
|
|
680
1098
|
|
|
681
1099
|
# If the destination event is not owned, but if the peer is not
|
|
682
1100
|
# connected, the event is our responsibility now.
|
|
683
|
-
if signalled.self_owned? || !signalled.owners.any? { |peer| peer !=
|
|
1101
|
+
if signalled.self_owned? || !signalled.owners.any? { |peer| peer != plan.local_owner && peer.connected? }
|
|
684
1102
|
next_step = gather_propagation(current_step) do
|
|
685
|
-
propagation_context(source_events | source_generators) do
|
|
1103
|
+
propagation_context(source_events | source_generators) do
|
|
686
1104
|
begin
|
|
687
|
-
signalled.emit_without_propagation(context)
|
|
1105
|
+
if event = signalled.emit_without_propagation(context)
|
|
1106
|
+
propagation_info.add_event_emission(event)
|
|
1107
|
+
emitted_events << event
|
|
1108
|
+
end
|
|
688
1109
|
rescue Roby::LocalizedError => e
|
|
1110
|
+
Roby.warn "Internal Error: #emit_without_propagation emitted a LocalizedError exception. This is unsupported and will become a fatal error in the future. You should usually replace raise with engine.add_error"
|
|
1111
|
+
Roby.display_exception(Roby.logger.io(:warn), e, false)
|
|
689
1112
|
add_error(e)
|
|
690
1113
|
rescue Exception => e
|
|
1114
|
+
Roby.warn "Internal Error: #emit_without_propagation emitted an exception. This is unsupported and will become a fatal error in the future. You should create a proper localized error and replace raise with engine.add_error"
|
|
1115
|
+
Roby.display_exception(Roby.logger.io(:warn), e, false)
|
|
691
1116
|
add_error(Roby::EmissionFailed.new(e, signalled))
|
|
692
1117
|
end
|
|
693
1118
|
end
|
|
@@ -700,165 +1125,250 @@ def event_propagation_step(current_step)
|
|
|
700
1125
|
current_step
|
|
701
1126
|
end
|
|
702
1127
|
|
|
703
|
-
#
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
end
|
|
1128
|
+
# Graph visitor that propagates exceptions in the dependency graph
|
|
1129
|
+
class ExceptionPropagationVisitor < Relations::ForkMergeVisitor
|
|
1130
|
+
attr_reader :exception_handler
|
|
1131
|
+
attr_reader :handled_exceptions
|
|
1132
|
+
attr_reader :unhandled_exceptions
|
|
1133
|
+
|
|
1134
|
+
def initialize(graph, object, origin, origin_neighbours = graph.out_neighbours(origin), &exception_handler)
|
|
1135
|
+
super(graph, object, origin, origin_neighbours)
|
|
1136
|
+
@exception_handler = exception_handler
|
|
1137
|
+
@handled_exceptions = Array.new
|
|
1138
|
+
@unhandled_exceptions = Array.new
|
|
715
1139
|
end
|
|
716
|
-
end
|
|
717
1140
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1141
|
+
def propagate_object(u, v, obj)
|
|
1142
|
+
raise if u == v
|
|
1143
|
+
if !obj.handled?
|
|
1144
|
+
obj.propagate(u, v)
|
|
1145
|
+
obj
|
|
1146
|
+
end
|
|
724
1147
|
end
|
|
725
1148
|
|
|
726
|
-
|
|
727
|
-
|
|
1149
|
+
def fork_object(obj)
|
|
1150
|
+
obj.fork
|
|
1151
|
+
end
|
|
728
1152
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
fatal = [] # the list of exceptions for which no handler has been found
|
|
733
|
-
|
|
734
|
-
# Remove finished repairs. Those are still considered during this cycle,
|
|
735
|
-
# as it is possible that some actions have been scheduled for the
|
|
736
|
-
# beginning of the next cycle through #once
|
|
737
|
-
finished_repairs = remove_useless_repairs
|
|
738
|
-
# Remove remove exceptions for which a repair exists
|
|
739
|
-
exceptions = remove_inhibited_exceptions(exceptions)
|
|
740
|
-
|
|
741
|
-
# Install new repairs based on the HandledBy task relation. If a repair
|
|
742
|
-
# is installed, remove the exception from the set of errors to handle
|
|
743
|
-
exceptions.delete_if do |e, _|
|
|
744
|
-
# Check for handled_by relations which would be able to handle +e+
|
|
745
|
-
error = e.exception
|
|
746
|
-
next unless (failed_event = error.failed_event)
|
|
747
|
-
next unless (failed_task = error.failed_task)
|
|
748
|
-
next if finished_repairs.has_key?(failed_event)
|
|
749
|
-
|
|
750
|
-
failed_generator = error.failed_generator
|
|
751
|
-
|
|
752
|
-
repair = failed_task.find_error_handler do |repairing_task, event_set|
|
|
753
|
-
event_set.find do |repaired_generator|
|
|
754
|
-
repaired_generator = failed_task.event(repaired_generator)
|
|
755
|
-
|
|
756
|
-
!repairing_task.finished? &&
|
|
757
|
-
(repaired_generator == failed_generator ||
|
|
758
|
-
Roby::EventStructure::Forwarding.reachable?(failed_generator, repaired_generator))
|
|
759
|
-
end
|
|
760
|
-
end
|
|
1153
|
+
def handle_examine_vertex(u)
|
|
1154
|
+
e = vertex_to_object.fetch(u)
|
|
1155
|
+
return if !e
|
|
761
1156
|
|
|
762
|
-
if
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
end
|
|
767
|
-
true
|
|
768
|
-
else
|
|
769
|
-
false
|
|
1157
|
+
if e.handled = exception_handler[e, u]
|
|
1158
|
+
handled_exceptions << e
|
|
1159
|
+
elsif out_degree[u] == 0
|
|
1160
|
+
unhandled_exceptions << e
|
|
770
1161
|
end
|
|
771
1162
|
end
|
|
1163
|
+
end
|
|
772
1164
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1165
|
+
# The core exception propagation algorithm
|
|
1166
|
+
#
|
|
1167
|
+
# @param [Array<(ExecutionException,Array<Task>)>] exceptions the set of
|
|
1168
|
+
# exceptions to propagate, as well as the parents that towards which
|
|
1169
|
+
# we should propagate them (if empty, all parents)
|
|
1170
|
+
#
|
|
1171
|
+
# @yieldparam [ExecutionException] exception the exception that is being
|
|
1172
|
+
# propagated
|
|
1173
|
+
# @yieldparam [Task,Plan] handling_object the object we want to test
|
|
1174
|
+
# whether it handles the exception or not
|
|
1175
|
+
# @yieldreturn [Boolean] true if the exception is handled, false
|
|
1176
|
+
# otherwise
|
|
1177
|
+
#
|
|
1178
|
+
# @return [Array<(ExecutionException,Array<Task>)>] the set of unhandled
|
|
1179
|
+
# exceptions, as a mapping from an exception description to the set of
|
|
1180
|
+
# tasks that are affected by it
|
|
1181
|
+
def propagate_exception_in_plan(exceptions)
|
|
1182
|
+
propagation_graph = dependency_graph.reverse
|
|
1183
|
+
|
|
1184
|
+
# Propagate the exceptions in the hierarchy
|
|
1185
|
+
handled_unhandled = Array.new
|
|
1186
|
+
exceptions.each do |exception, parents|
|
|
1187
|
+
origin = exception.origin
|
|
1188
|
+
if parents
|
|
1189
|
+
filtered_parents = parents.find_all { |t| t.depends_on?(origin) }
|
|
1190
|
+
if filtered_parents != parents
|
|
1191
|
+
warn "some parents specified for #{exception.exception}(#{exception.exception.class}) are actually not parents of #{origin}, they got filtered out"
|
|
1192
|
+
(parents - filtered_parents).each do |task|
|
|
1193
|
+
warn " #{task}"
|
|
788
1194
|
end
|
|
789
1195
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1196
|
+
if filtered_parents.empty?
|
|
1197
|
+
parents = propagation_graph.out_neighbours(origin)
|
|
1198
|
+
else
|
|
1199
|
+
parents = filtered_parents
|
|
794
1200
|
end
|
|
795
|
-
|
|
796
|
-
has_parent = true
|
|
797
1201
|
end
|
|
1202
|
+
else
|
|
1203
|
+
parents = propagation_graph.out_neighbours(origin)
|
|
1204
|
+
end
|
|
798
1205
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1206
|
+
debug do
|
|
1207
|
+
debug "propagating exception "
|
|
1208
|
+
log_pp :debug, exception
|
|
1209
|
+
if !parents.empty?
|
|
1210
|
+
debug " constrained to parents"
|
|
1211
|
+
log_nest(2) do
|
|
1212
|
+
parents.each do |p|
|
|
1213
|
+
log_pp :debug, p
|
|
1214
|
+
end
|
|
805
1215
|
end
|
|
806
1216
|
end
|
|
807
|
-
|
|
808
|
-
by_task
|
|
1217
|
+
break
|
|
809
1218
|
end
|
|
810
1219
|
|
|
811
|
-
|
|
812
|
-
|
|
1220
|
+
visitor = ExceptionPropagationVisitor.new(propagation_graph, exception, origin, parents) do |e, task|
|
|
1221
|
+
yield(e, task)
|
|
813
1222
|
end
|
|
1223
|
+
visitor.visit
|
|
814
1224
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
task_exceptions.each { |e| new_exceptions << [e, [task]] }
|
|
820
|
-
next
|
|
821
|
-
end
|
|
1225
|
+
unhandled = visitor.unhandled_exceptions.inject { |a, b| a.merge(b) }
|
|
1226
|
+
handled = visitor.handled_exceptions.inject { |a, b| a.merge(b) }
|
|
1227
|
+
handled_unhandled << [handled, unhandled]
|
|
1228
|
+
end
|
|
822
1229
|
|
|
823
|
-
task_exceptions.each do |e|
|
|
824
|
-
next if e.handled?
|
|
825
|
-
handled = task.handle_exception(e)
|
|
826
1230
|
|
|
1231
|
+
exceptions_handled_by = Array.new
|
|
1232
|
+
unhandled_exceptions = Array.new
|
|
1233
|
+
handled_unhandled.each do |handled, e|
|
|
1234
|
+
if e
|
|
1235
|
+
if e.handled = yield(e, plan)
|
|
827
1236
|
if handled
|
|
828
|
-
|
|
829
|
-
e
|
|
1237
|
+
handled_by = (handled.propagation_leafs.to_set << plan)
|
|
1238
|
+
exceptions_handled_by << [handled.merge(e), handled_by]
|
|
830
1239
|
else
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1240
|
+
handled = e
|
|
1241
|
+
exceptions_handled_by << [e, [plan].to_set]
|
|
1242
|
+
end
|
|
1243
|
+
else
|
|
1244
|
+
affected_tasks = e.trace.vertices.to_set
|
|
1245
|
+
if handled
|
|
1246
|
+
affected_tasks -= handled.trace.vertices
|
|
1247
|
+
exceptions_handled_by << [handled, handled.propagation_leafs.to_set]
|
|
835
1248
|
end
|
|
1249
|
+
unhandled_exceptions << [e, affected_tasks]
|
|
836
1250
|
end
|
|
1251
|
+
else
|
|
1252
|
+
exceptions_handled_by << [handled, handled.propagation_leafs.to_set]
|
|
837
1253
|
end
|
|
1254
|
+
end
|
|
838
1255
|
|
|
839
|
-
|
|
1256
|
+
debug do
|
|
1257
|
+
debug "#{unhandled_exceptions.size} unhandled exceptions remain"
|
|
1258
|
+
log_nest(2) do
|
|
1259
|
+
unhandled_exceptions.each do |e, affected_tasks|
|
|
1260
|
+
log_pp :debug, e
|
|
1261
|
+
debug "Affects #{affected_tasks.size} tasks"
|
|
1262
|
+
log_nest(2) do
|
|
1263
|
+
affected_tasks.each do |t|
|
|
1264
|
+
log_pp :debug, t
|
|
1265
|
+
end
|
|
1266
|
+
end
|
|
1267
|
+
end
|
|
1268
|
+
end
|
|
1269
|
+
break
|
|
840
1270
|
end
|
|
1271
|
+
return unhandled_exceptions, exceptions_handled_by
|
|
1272
|
+
end
|
|
1273
|
+
|
|
1274
|
+
# Propagation exception phase, checking if tasks and/or the main plan
|
|
1275
|
+
# are handling the exceptions
|
|
1276
|
+
#
|
|
1277
|
+
# @param [Array<(ExecutionException,Array<Task>)>] exceptions the set of
|
|
1278
|
+
# exceptions to propagate, as well as the parents that towards which
|
|
1279
|
+
# we should propagate them (if empty, all parents)
|
|
1280
|
+
# @return (see propagate_exception_in_plan)
|
|
1281
|
+
def propagate_exceptions(exceptions)
|
|
1282
|
+
if exceptions.empty?
|
|
1283
|
+
return Array.new, Array.new, Array.new
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
# Remove all exception that are not associated with a task
|
|
1287
|
+
exceptions, free_events_exceptions = exceptions.partition do |e, _|
|
|
1288
|
+
e.origin
|
|
1289
|
+
end
|
|
1290
|
+
# Normalize the free events exceptions
|
|
1291
|
+
free_events_exceptions = free_events_exceptions.map do |e, _|
|
|
1292
|
+
if e.exception.failed_generator.plan
|
|
1293
|
+
[e, Set[e.exception.failed_generator]]
|
|
1294
|
+
end
|
|
1295
|
+
end.compact
|
|
1296
|
+
|
|
1297
|
+
debug "Filtering inhibited exceptions"
|
|
1298
|
+
exceptions = log_nest(2) do
|
|
1299
|
+
non_inhibited, _ = remove_inhibited_exceptions(exceptions)
|
|
1300
|
+
# Reset the trace for the real propagation
|
|
1301
|
+
non_inhibited.map do |e, _|
|
|
1302
|
+
_, propagate_through = exceptions.find { |original_e, _| original_e.exception == e.exception }
|
|
1303
|
+
e.reset_trace
|
|
1304
|
+
[e, propagate_through]
|
|
1305
|
+
end
|
|
1306
|
+
end
|
|
1307
|
+
|
|
1308
|
+
debug "Propagating #{exceptions.size} non-inhibited exceptions"
|
|
1309
|
+
log_nest(2) do
|
|
1310
|
+
# Note that the first half of the method filtered the free
|
|
1311
|
+
# events exceptions out of 'exceptions'
|
|
1312
|
+
unhandled, handled = propagate_exception_in_plan(exceptions) do |e, object|
|
|
1313
|
+
object.handle_exception(e)
|
|
1314
|
+
end
|
|
841
1315
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1316
|
+
return unhandled, free_events_exceptions, handled
|
|
1317
|
+
end
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
# Process the given exceptions to remove the ones that are currently
|
|
1321
|
+
# filtered by the plan repairs
|
|
1322
|
+
#
|
|
1323
|
+
# The returned exceptions are propagated, i.e. their #trace method
|
|
1324
|
+
# contains all the tasks that are affected by the absence of a handling
|
|
1325
|
+
# mechanism
|
|
1326
|
+
#
|
|
1327
|
+
# @param [(ExecutionException,Array<Roby::Task>)] exceptions pairs of
|
|
1328
|
+
# exceptions as well as the "root tasks", i.e. the parents of
|
|
1329
|
+
# origin.task towards which they should be propagated
|
|
1330
|
+
# @return [Array<ExecutionException>] the unhandled exceptions
|
|
1331
|
+
def remove_inhibited_exceptions(exceptions)
|
|
1332
|
+
exceptions = exceptions.find_all do |execution_exception, _|
|
|
1333
|
+
execution_exception.origin.plan
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
propagate_exception_in_plan(exceptions) do |e, object|
|
|
1337
|
+
if has_pending_exception_matching?(e, object)
|
|
1338
|
+
true
|
|
1339
|
+
elsif object.respond_to?(:handles_error?)
|
|
1340
|
+
object.handles_error?(e)
|
|
845
1341
|
end
|
|
846
1342
|
end
|
|
847
|
-
# Call global exception handlers for exceptions in +fatal+. Return the
|
|
848
|
-
# set of still unhandled exceptions
|
|
849
|
-
fatal.
|
|
850
|
-
find_all { |e| !e.handled? }.
|
|
851
|
-
reject { |e| plan.handle_exception(e) }
|
|
852
1343
|
end
|
|
853
1344
|
|
|
854
|
-
#
|
|
855
|
-
|
|
856
|
-
|
|
1345
|
+
# Query whether the given exception is inhibited in this plan
|
|
1346
|
+
def inhibited_exception?(exception)
|
|
1347
|
+
unhandled, _ = remove_inhibited_exceptions([exception.to_execution_exception])
|
|
1348
|
+
unhandled.empty?
|
|
1349
|
+
end
|
|
857
1350
|
|
|
858
1351
|
# Schedules +block+ to be called at the beginning of the next execution
|
|
859
1352
|
# cycle, in propagation context.
|
|
860
|
-
|
|
861
|
-
|
|
1353
|
+
#
|
|
1354
|
+
# @param [#fail] sync a synchronization object that is used to
|
|
1355
|
+
# communicate between the once block and the calling thread. The main
|
|
1356
|
+
# use of this parameter is to make sure that #fail is called if the
|
|
1357
|
+
# execution engine quits
|
|
1358
|
+
# @param (see PropagationHandlerMethods#create_propagation_handler)
|
|
1359
|
+
def once(sync: nil, description: 'once block', type: :external_events, **options, &block)
|
|
1360
|
+
waiting_work << sync if sync
|
|
1361
|
+
once_blocks << create_propagation_handler(description: description, type: type, once: true, **options, &block)
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
# Schedules +block+ to be called once after +delay+ seconds passed, in
|
|
1365
|
+
# the propagation context
|
|
1366
|
+
def delayed(delay, description: 'delayed block', **options, &block)
|
|
1367
|
+
handler = PollBlockDefinition.new(description, block, once: true, **options)
|
|
1368
|
+
once do
|
|
1369
|
+
process_every << [handler, cycle_start, delay]
|
|
1370
|
+
end
|
|
1371
|
+
handler.id
|
|
862
1372
|
end
|
|
863
1373
|
|
|
864
1374
|
# The set of errors which have been generated outside of the plan's
|
|
@@ -866,124 +1376,509 @@ def once(&block)
|
|
|
866
1376
|
# down.
|
|
867
1377
|
attr_reader :application_exceptions
|
|
868
1378
|
def clear_application_exceptions
|
|
1379
|
+
if !@application_exceptions
|
|
1380
|
+
raise RecursivePropagationContext, "unbalanced call to #clear_application_exceptions"
|
|
1381
|
+
end
|
|
1382
|
+
|
|
869
1383
|
result, @application_exceptions = @application_exceptions, nil
|
|
870
1384
|
result
|
|
871
1385
|
end
|
|
872
1386
|
|
|
873
|
-
#
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
e = e.exception
|
|
879
|
-
end
|
|
880
|
-
raise e, e.message, e.backtrace
|
|
881
|
-
else
|
|
882
|
-
raise Aborting.new(exceptions)
|
|
883
|
-
end
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
# Process the pending events. The time at each event loop step
|
|
887
|
-
# is saved into +stats+.
|
|
888
|
-
def process_events(stats = {:start => Time.now})
|
|
889
|
-
@application_exceptions = []
|
|
890
|
-
|
|
891
|
-
add_timepoint(stats, :real_start)
|
|
892
|
-
|
|
893
|
-
# Gather new events and propagate them
|
|
894
|
-
events_errors = begin
|
|
895
|
-
old_allow_propagation, @allow_propagation = @allow_propagation, true
|
|
896
|
-
propagate_events
|
|
897
|
-
ensure @allow_propagation = old_allow_propagation
|
|
898
|
-
end
|
|
899
|
-
add_timepoint(stats, :events)
|
|
900
|
-
|
|
901
|
-
# HACK: events_errors is sometime nil here. It shouldn't
|
|
902
|
-
events_errors ||= []
|
|
1387
|
+
# Used during exception propagation to inject new errors in the process
|
|
1388
|
+
#
|
|
1389
|
+
# It shall not be accessed directly. Instead, Plan#add_error should be
|
|
1390
|
+
# called
|
|
1391
|
+
attr_reader :additional_errors
|
|
903
1392
|
|
|
1393
|
+
# Compute the set of fatal errors in the current execution state
|
|
1394
|
+
#
|
|
1395
|
+
# @param [Array] events_errors the set of errors gathered during event
|
|
1396
|
+
# propagation
|
|
1397
|
+
# @return [PropagationInfo]
|
|
1398
|
+
def compute_errors(events_errors)
|
|
904
1399
|
# Generate exceptions from task structure
|
|
905
1400
|
structure_errors = plan.check_structure
|
|
906
|
-
|
|
1401
|
+
log_timepoint 'structure_check'
|
|
907
1402
|
|
|
908
1403
|
# Propagate the errors. Note that the plan repairs are taken into
|
|
909
|
-
# account in ExecutionEngine.propagate_exceptions
|
|
1404
|
+
# account in ExecutionEngine.propagate_exceptions directly. We keep
|
|
910
1405
|
# event and structure errors separate since in the first case there
|
|
911
1406
|
# is not two-stage handling (all errors that have not been handled
|
|
912
1407
|
# are fatal), and in the second case we call #check_structure
|
|
913
|
-
# again to
|
|
914
|
-
|
|
915
|
-
propagate_exceptions(
|
|
916
|
-
|
|
1408
|
+
# again to errors that are remaining after the call to the exception
|
|
1409
|
+
# handlers
|
|
1410
|
+
events_errors, free_events_errors, events_handled = propagate_exceptions(events_errors)
|
|
1411
|
+
_, structure_handled = propagate_exceptions(structure_errors)
|
|
1412
|
+
log_timepoint 'exception_propagation'
|
|
917
1413
|
|
|
918
1414
|
# Get the remaining problems in the plan structure, and act on it
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1415
|
+
structure_errors, structure_inhibited = remove_inhibited_exceptions(plan.check_structure)
|
|
1416
|
+
|
|
1417
|
+
# Partition them by fatal/nonfatal
|
|
1418
|
+
fatal_errors, nonfatal_errors = Array.new, Array.new
|
|
1419
|
+
(structure_errors + events_errors).each do |e, involved_tasks|
|
|
1420
|
+
if e.fatal?
|
|
1421
|
+
fatal_errors << [e, involved_tasks]
|
|
1422
|
+
else
|
|
1423
|
+
nonfatal_errors << [e, involved_tasks]
|
|
1424
|
+
end
|
|
1425
|
+
end
|
|
1426
|
+
kill_tasks = compute_kill_tasks_for_unhandled_fatal_errors(fatal_errors).to_set
|
|
1427
|
+
handled_errors = structure_handled + events_handled
|
|
1428
|
+
|
|
1429
|
+
debug "#{fatal_errors.size} fatal errors found and #{free_events_errors.size} errors involving free events"
|
|
1430
|
+
debug "the fatal errors involve #{kill_tasks.size} non-finalized tasks"
|
|
1431
|
+
return PropagationInfo.new(Set.new, Set.new, kill_tasks, fatal_errors, nonfatal_errors, free_events_errors, handled_errors, structure_inhibited)
|
|
1432
|
+
end
|
|
1433
|
+
|
|
1434
|
+
# Whether this EE has asynchronous waiting work waiting to be processed
|
|
1435
|
+
def has_waiting_work?
|
|
1436
|
+
# Filter out unscheduled promises (promises on which #execute was
|
|
1437
|
+
# not called). If they are unscheduled, we're not waiting on them
|
|
1438
|
+
waiting_work.any? { |w| !w.unscheduled? }
|
|
1439
|
+
end
|
|
1440
|
+
|
|
1441
|
+
# Process asynchronous work registered in {#waiting_work} to clear
|
|
1442
|
+
# completed work and/or handle errors that were not handled by the async
|
|
1443
|
+
# object itself (e.g. a {Promise} without a {Promise#on_error} handler)
|
|
1444
|
+
def process_waiting_work
|
|
1445
|
+
finished, not_finished = waiting_work.partition do |work|
|
|
1446
|
+
work.complete?
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
finished.find_all do |work|
|
|
1450
|
+
work.rejected? && (work.respond_to?(:has_error_handler?) && !work.has_error_handler?)
|
|
1451
|
+
end.each do |work|
|
|
1452
|
+
e = work.reason
|
|
1453
|
+
e.set_backtrace(e.backtrace + caller)
|
|
1454
|
+
add_framework_error(e, work.to_s)
|
|
1455
|
+
end
|
|
1456
|
+
|
|
1457
|
+
@waiting_work = not_finished
|
|
1458
|
+
finished
|
|
1459
|
+
end
|
|
1460
|
+
|
|
1461
|
+
# Gathering of all the errors that happened during an event processing
|
|
1462
|
+
# loop and were not handled
|
|
1463
|
+
PropagationInfo = Struct.new :called_generators, :emitted_events, :kill_tasks, :fatal_errors, :nonfatal_errors, :free_events_errors, :handled_errors, :inhibited_errors, :framework_errors do
|
|
1464
|
+
def initialize(called_generators = Set.new,
|
|
1465
|
+
emitted_events = Set.new,
|
|
1466
|
+
kill_tasks = Set.new,
|
|
1467
|
+
fatal_errors = Array.new,
|
|
1468
|
+
nonfatal_errors = Array.new,
|
|
1469
|
+
free_events_errors = Array.new,
|
|
1470
|
+
handled_errors = Array.new,
|
|
1471
|
+
inhibited_errors = Array.new,
|
|
1472
|
+
framework_errors = Array.new)
|
|
1473
|
+
|
|
1474
|
+
self.called_generators = called_generators .to_set
|
|
1475
|
+
self.emitted_events = emitted_events.to_set
|
|
1476
|
+
self.kill_tasks = kill_tasks.to_set
|
|
1477
|
+
self.fatal_errors = fatal_errors
|
|
1478
|
+
self.nonfatal_errors = nonfatal_errors
|
|
1479
|
+
self.free_events_errors = free_events_errors
|
|
1480
|
+
self.handled_errors = handled_errors
|
|
1481
|
+
self.inhibited_errors = inhibited_errors
|
|
1482
|
+
self.framework_errors = framework_errors
|
|
1483
|
+
end
|
|
1484
|
+
|
|
1485
|
+
def merge(results)
|
|
1486
|
+
self.called_generators.merge(results.called_generators)
|
|
1487
|
+
self.emitted_events.merge(results.emitted_events)
|
|
1488
|
+
self.kill_tasks.merge(results.kill_tasks)
|
|
1489
|
+
self.fatal_errors.concat(results.fatal_errors)
|
|
1490
|
+
self.nonfatal_errors.concat(results.nonfatal_errors)
|
|
1491
|
+
self.free_events_errors.concat(results.free_events_errors)
|
|
1492
|
+
self.handled_errors.concat(results.handled_errors)
|
|
1493
|
+
self.inhibited_errors.concat(results.inhibited_errors)
|
|
1494
|
+
self.framework_errors.concat(results.framework_errors)
|
|
1495
|
+
end
|
|
1496
|
+
|
|
1497
|
+
def add_generator_call(generator)
|
|
1498
|
+
called_generators << generator
|
|
1499
|
+
end
|
|
1500
|
+
|
|
1501
|
+
def add_event_emission(event)
|
|
1502
|
+
emitted_events << event
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
def each_called_generator(&block)
|
|
1506
|
+
called_generators.each(&block)
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1509
|
+
def has_called_generators?
|
|
1510
|
+
!called_generators.empty?
|
|
1511
|
+
end
|
|
1512
|
+
|
|
1513
|
+
def each_emitted_event(&block)
|
|
1514
|
+
emitted_events.each(&block)
|
|
1515
|
+
end
|
|
1516
|
+
|
|
1517
|
+
def has_emitted_events?
|
|
1518
|
+
!emitted_events.empty?
|
|
1519
|
+
end
|
|
1520
|
+
|
|
1521
|
+
# Return the exception objects registered in this result object
|
|
1522
|
+
def exceptions
|
|
1523
|
+
fatal_errors.map(&:first) + nonfatal_errors.map(&:first) + free_events_errors.map(&:first)
|
|
1524
|
+
end
|
|
1525
|
+
|
|
1526
|
+
def each_fatal_error(&block)
|
|
1527
|
+
fatal_errors.each(&block)
|
|
1528
|
+
end
|
|
1529
|
+
|
|
1530
|
+
def has_fatal_errors?
|
|
1531
|
+
!fatal_errors.empty?
|
|
1532
|
+
end
|
|
1533
|
+
|
|
1534
|
+
def each_nonfatal_error(&block)
|
|
1535
|
+
nonfatal_errors.each(&block)
|
|
1536
|
+
end
|
|
1537
|
+
|
|
1538
|
+
def has_nonfatal_errors?
|
|
1539
|
+
!nonfatal_errors.empty?
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
def each_free_events_errors(&block)
|
|
1543
|
+
free_events_errors.each(&block)
|
|
1544
|
+
end
|
|
1545
|
+
|
|
1546
|
+
def has_free_events_errors?
|
|
1547
|
+
!free_events_errors.empty?
|
|
1548
|
+
end
|
|
1549
|
+
|
|
1550
|
+
def each_handled_error(&block)
|
|
1551
|
+
handled_errors.each(&block)
|
|
1552
|
+
end
|
|
1553
|
+
|
|
1554
|
+
def has_handled_errors?
|
|
1555
|
+
!handled_errors.empty?
|
|
1556
|
+
end
|
|
1557
|
+
|
|
1558
|
+
def each_inhibited_error(&block)
|
|
1559
|
+
inhibited_errors.each(&block)
|
|
1560
|
+
end
|
|
1561
|
+
|
|
1562
|
+
def has_inhibited_errors?
|
|
1563
|
+
!inhibited_errors.empty?
|
|
1564
|
+
end
|
|
1565
|
+
|
|
1566
|
+
def each_framework_error(&block)
|
|
1567
|
+
framework_errors.each(&block)
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1570
|
+
def has_framework_errors?
|
|
1571
|
+
!framework_errors.empty?
|
|
1572
|
+
end
|
|
1573
|
+
|
|
1574
|
+
def add_framework_errors(errors)
|
|
1575
|
+
framework_errors.concat(errors)
|
|
1576
|
+
end
|
|
1577
|
+
|
|
1578
|
+
def pretty_print(pp)
|
|
1579
|
+
if !emitted_events.empty?
|
|
1580
|
+
pp.text "received #{emitted_events.size} events:"
|
|
1581
|
+
pp.nest(2) do
|
|
1582
|
+
emitted_events.each do |ev|
|
|
1583
|
+
pp.breakable
|
|
1584
|
+
ev.pretty_print(pp)
|
|
1585
|
+
end
|
|
1586
|
+
end
|
|
1587
|
+
end
|
|
1588
|
+
exceptions = self.exceptions
|
|
1589
|
+
if !exceptions.empty?
|
|
1590
|
+
pp.breakable if !emitted_events.empty?
|
|
1591
|
+
pp.text "#{exceptions.size} unhandled exceptions:"
|
|
1592
|
+
pp.nest(2) do
|
|
1593
|
+
exceptions.map do |e|
|
|
1594
|
+
pp.breakable
|
|
1595
|
+
e.pretty_print(pp)
|
|
1596
|
+
end
|
|
1597
|
+
end
|
|
1598
|
+
end
|
|
1599
|
+
if !handled_errors.empty?
|
|
1600
|
+
pp.breakable if !emitted_events.empty? || !exceptions.empty?
|
|
1601
|
+
pp.text "#{handled_errors.size} handled exceptions:"
|
|
1602
|
+
pp.nest(2) do
|
|
1603
|
+
handled_errors.each do |e, handled_by|
|
|
1604
|
+
pp.breakable
|
|
1605
|
+
e.pretty_print(pp)
|
|
1606
|
+
pp.breakable
|
|
1607
|
+
pp.text "Handled by:"
|
|
1608
|
+
pp.nest(2) do
|
|
1609
|
+
handled_by.each do |t|
|
|
1610
|
+
pp.breakable
|
|
1611
|
+
t.pretty_print(pp)
|
|
1612
|
+
end
|
|
1613
|
+
end
|
|
929
1614
|
end
|
|
930
|
-
kill_tasks.merge(new_tasks)
|
|
931
1615
|
end
|
|
932
|
-
kill_tasks
|
|
933
1616
|
end
|
|
934
|
-
if !
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1617
|
+
if !framework_errors.empty?
|
|
1618
|
+
pp.breakable if !emitted_events.empty? || !exceptions.empty? || !handled_errors.empty?
|
|
1619
|
+
pp.text "#{framework_errors.size} framework errors"
|
|
1620
|
+
pp.nest(2) do
|
|
1621
|
+
framework_errors.each do |e, source|
|
|
1622
|
+
pp.breakable
|
|
1623
|
+
pp.text "from #{source}: "
|
|
1624
|
+
e.pretty_print(pp)
|
|
939
1625
|
end
|
|
940
|
-
""
|
|
941
1626
|
end
|
|
942
1627
|
end
|
|
943
1628
|
end
|
|
944
|
-
|
|
1629
|
+
end
|
|
1630
|
+
|
|
1631
|
+
# The methods that setup propagation context in ExecutionEngine must not
|
|
1632
|
+
# be called recursively.
|
|
1633
|
+
#
|
|
1634
|
+
# This exception is thrown if such a recursive call is detected
|
|
1635
|
+
class RecursivePropagationContext < RuntimeError; end
|
|
1636
|
+
|
|
1637
|
+
# Some methods require to be called within a gather_* block. This
|
|
1638
|
+
# exception is raised when they're called outside of it
|
|
1639
|
+
class NotPropagationContext < RuntimeError; end
|
|
1640
|
+
|
|
1641
|
+
# The inside part of the event loop
|
|
1642
|
+
#
|
|
1643
|
+
# It gathers initial events and errors and propagate them
|
|
1644
|
+
#
|
|
1645
|
+
# @return [PropagationInfo] what happened during the propagation
|
|
1646
|
+
# @raise RecursivePropagationContext if called recursively
|
|
1647
|
+
def process_events(raise_framework_errors: Roby.app.abort_on_application_exception?, garbage_collect_pass: true, &caller_block)
|
|
1648
|
+
if @application_exceptions
|
|
1649
|
+
raise RecursivePropagationContext, "recursive call to process_events"
|
|
1650
|
+
end
|
|
1651
|
+
passed_recursive_check = true # to avoid having a almost-method-global ensure block
|
|
1652
|
+
@application_exceptions = []
|
|
1653
|
+
@emitted_events = Array.new
|
|
1654
|
+
|
|
1655
|
+
@thread_pool.send :synchronize do
|
|
1656
|
+
@thread_pool.send(:ns_prune_pool)
|
|
1657
|
+
end
|
|
945
1658
|
|
|
946
|
-
|
|
947
|
-
|
|
1659
|
+
# Gather new events and propagate them
|
|
1660
|
+
events_errors = nil
|
|
1661
|
+
next_steps = gather_propagation do
|
|
1662
|
+
events_errors = gather_errors do
|
|
1663
|
+
if caller_block
|
|
1664
|
+
yield
|
|
1665
|
+
caller_block = nil
|
|
1666
|
+
end
|
|
948
1667
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1668
|
+
if !quitting? || !garbage_collect([])
|
|
1669
|
+
process_waiting_work
|
|
1670
|
+
log_timepoint 'workers'
|
|
1671
|
+
gather_external_events
|
|
1672
|
+
log_timepoint 'external_events'
|
|
1673
|
+
call_propagation_handlers
|
|
1674
|
+
log_timepoint 'propagation_handlers'
|
|
1675
|
+
end
|
|
1676
|
+
end
|
|
953
1677
|
end
|
|
954
1678
|
|
|
955
|
-
|
|
956
|
-
|
|
1679
|
+
propagation_info = propagate_events_and_errors(next_steps, events_errors, garbage_collect_pass: garbage_collect_pass)
|
|
1680
|
+
if Roby.app.abort_on_exception? && !all_errors.fatal_errors.empty?
|
|
1681
|
+
raise Aborting.new(propagation_info.each_fatal_error.map(&:exception))
|
|
957
1682
|
end
|
|
1683
|
+
propagation_info.framework_errors.concat(@application_exceptions)
|
|
1684
|
+
propagation_info
|
|
958
1685
|
|
|
959
1686
|
ensure
|
|
960
|
-
|
|
1687
|
+
if passed_recursive_check
|
|
1688
|
+
process_pending_application_exceptions(raise_framework_errors: raise_framework_errors)
|
|
1689
|
+
end
|
|
961
1690
|
end
|
|
962
1691
|
|
|
963
|
-
#
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1692
|
+
# Tests are using a special mode for propagation, in which everything is
|
|
1693
|
+
# resolved when #emit or #call is called, including error handling. This
|
|
1694
|
+
# mode is implemented using this method
|
|
1695
|
+
#
|
|
1696
|
+
# When errors occur in this mode, the exceptions are raised directly.
|
|
1697
|
+
# This is useful in tests as, this way, we are sure that the exception
|
|
1698
|
+
# will not get overlooked
|
|
1699
|
+
#
|
|
1700
|
+
# If multiple errors are raised in a single call (this is possible due
|
|
1701
|
+
# to Roby's error handling mechanisms), the method will raise
|
|
1702
|
+
# SynchronousEventProcessingMultipleErrors to wrap all the exceptions
|
|
1703
|
+
# into one.
|
|
1704
|
+
def process_events_synchronous(seeds = Hash.new, initial_errors = Array.new, enable_scheduler: false, raise_errors: true)
|
|
1705
|
+
Roby.warn_deprecated "#process_events_synchronous is deprecated, use the expect_execution harness instead"
|
|
1706
|
+
|
|
1707
|
+
if @application_exceptions
|
|
1708
|
+
raise RecursivePropagationContext, "recursive call to process_events"
|
|
1709
|
+
end
|
|
1710
|
+
passed_recursive_check = true # to avoid having a almost-method-global ensure block
|
|
1711
|
+
@application_exceptions = []
|
|
1712
|
+
|
|
1713
|
+
# Save early for the benefit of the 'ensure' block
|
|
1714
|
+
current_scheduler_enabled = scheduler.enabled?
|
|
1715
|
+
|
|
1716
|
+
if (!seeds.empty? || !initial_errors.empty?) && block_given?
|
|
1717
|
+
raise ArgumentError, "cannot provide both seeds/inital errors and a block"
|
|
1718
|
+
elsif block_given?
|
|
1719
|
+
seeds = gather_propagation do
|
|
1720
|
+
initial_errors = gather_errors do
|
|
1721
|
+
yield
|
|
1722
|
+
end
|
|
1723
|
+
end
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
scheduler.enabled = enable_scheduler
|
|
1727
|
+
|
|
1728
|
+
propagation_info = propagate_events_and_errors(seeds, initial_errors, garbage_collect_pass: false)
|
|
1729
|
+
if !propagation_info.kill_tasks.empty?
|
|
1730
|
+
gc_initial_errors = nil
|
|
1731
|
+
gc_seeds = gather_propagation do
|
|
1732
|
+
gc_initial_errors = gather_errors do
|
|
1733
|
+
garbage_collect(propagation_info.kill_tasks)
|
|
1734
|
+
end
|
|
1735
|
+
end
|
|
1736
|
+
gc_errors = propagate_events_and_errors(gc_seeds, gc_initial_errors, garbage_collect_pass: false)
|
|
1737
|
+
propagation_info.merge(gc_errors)
|
|
1738
|
+
end
|
|
1739
|
+
|
|
1740
|
+
if raise_errors
|
|
1741
|
+
propagation_info = propagation_info.exceptions
|
|
1742
|
+
if propagation_info.size == 1
|
|
1743
|
+
raise propagation_info.first
|
|
1744
|
+
elsif !propagation_info.empty?
|
|
1745
|
+
raise SynchronousEventProcessingMultipleErrors.new(propagation_info.map(&:exception))
|
|
1746
|
+
end
|
|
1747
|
+
else
|
|
1748
|
+
propagation_info
|
|
1749
|
+
end
|
|
1750
|
+
|
|
1751
|
+
rescue SynchronousEventProcessingMultipleErrors => e
|
|
1752
|
+
raise SynchronousEventProcessingMultipleErrors.new(e.errors + clear_application_exceptions)
|
|
1753
|
+
|
|
1754
|
+
rescue Exception => e
|
|
1755
|
+
if passed_recursive_check
|
|
1756
|
+
application_exceptions = clear_application_exceptions
|
|
1757
|
+
if !application_exceptions.empty?
|
|
1758
|
+
raise SynchronousEventProcessingMultipleErrors.new(application_exceptions.map(&:first) + [e])
|
|
1759
|
+
else raise e
|
|
1760
|
+
end
|
|
1761
|
+
else
|
|
1762
|
+
raise e
|
|
1763
|
+
end
|
|
1764
|
+
|
|
1765
|
+
ensure
|
|
1766
|
+
if passed_recursive_check && @application_exceptions
|
|
1767
|
+
process_pending_application_exceptions
|
|
968
1768
|
end
|
|
1769
|
+
scheduler.enabled = current_scheduler_enabled
|
|
1770
|
+
end
|
|
1771
|
+
|
|
1772
|
+
|
|
1773
|
+
# Propagate an initial set of event propagations and errors
|
|
1774
|
+
#
|
|
1775
|
+
# @param [Array] next_steps the next propagations, as returned by
|
|
1776
|
+
# {#gather_propagation}
|
|
1777
|
+
# @param [Array] initial_errors a set of errors that should be
|
|
1778
|
+
# propagated
|
|
1779
|
+
# @param [Boolean] garbage_collect_pass whether the garbage collection
|
|
1780
|
+
# pass should be performed or not. It is used in the tests' codepath
|
|
1781
|
+
# for {EventGenerator#call} and {EventGenerator#emit}.
|
|
1782
|
+
# @return [PropagationInfo] what happened during the propagation
|
|
1783
|
+
# and propagated
|
|
1784
|
+
def propagate_events_and_errors(next_steps, initial_errors, garbage_collect_pass: true)
|
|
1785
|
+
propagation_info = PropagationInfo.new
|
|
1786
|
+
events_errors = initial_errors.dup
|
|
1787
|
+
begin
|
|
1788
|
+
log_timepoint_group 'event_propagation_phase' do
|
|
1789
|
+
events_errors.concat(event_propagation_phase(next_steps, propagation_info))
|
|
1790
|
+
end
|
|
1791
|
+
|
|
1792
|
+
next_steps = gather_propagation do
|
|
1793
|
+
exception_propagation_errors, error_phase_results = nil
|
|
1794
|
+
log_timepoint_group 'error_handling_phase' do
|
|
1795
|
+
exception_propagation_errors = gather_errors do
|
|
1796
|
+
error_phase_results = error_handling_phase(events_errors)
|
|
1797
|
+
end
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
add_exceptions_for_inhibition(error_phase_results.each_fatal_error)
|
|
1801
|
+
propagation_info.merge(error_phase_results)
|
|
1802
|
+
garbage_collection_errors = gather_errors do
|
|
1803
|
+
plan.generate_induced_errors(error_phase_results)
|
|
1804
|
+
if garbage_collect_pass
|
|
1805
|
+
garbage_collect(error_phase_results.kill_tasks)
|
|
1806
|
+
else []
|
|
1807
|
+
end
|
|
1808
|
+
end
|
|
1809
|
+
events_errors = (exception_propagation_errors + garbage_collection_errors)
|
|
1810
|
+
log_timepoint 'garbage_collect'
|
|
1811
|
+
end
|
|
1812
|
+
end while !next_steps.empty? || !events_errors.empty?
|
|
1813
|
+
propagation_info
|
|
969
1814
|
end
|
|
970
1815
|
|
|
971
|
-
#
|
|
972
|
-
|
|
1816
|
+
# Tests whether there is an exception registered by
|
|
1817
|
+
# {#add_fatal_exceptions_for_inhibition} for a given error and object
|
|
1818
|
+
#
|
|
1819
|
+
# @param [ExecutionException] e
|
|
1820
|
+
# @param [Task,Plan] the handling object
|
|
1821
|
+
def has_pending_exception_matching?(e, object)
|
|
1822
|
+
@pending_exceptions[object] && @pending_exceptions[object].include?([e.exception.class, e.origin])
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1825
|
+
# Register a set of fatal exceptions to ensure that they will be
|
|
1826
|
+
# inhibited in the next exception propagation cycles
|
|
1827
|
+
def add_exceptions_for_inhibition(fatal_errors)
|
|
1828
|
+
fatal_errors.each do |exception, involved_tasks|
|
|
1829
|
+
involved_tasks.each do |t|
|
|
1830
|
+
(@pending_exceptions[t] ||= Set.new) <<
|
|
1831
|
+
[exception.exception.class, exception.origin]
|
|
1832
|
+
end
|
|
1833
|
+
end
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1836
|
+
def unmark_finished_missions_and_permanent_tasks
|
|
1837
|
+
to_unmark = plan.task_index.by_predicate[:finished?] | plan.task_index.by_predicate[:failed?]
|
|
1838
|
+
|
|
1839
|
+
finished_missions = (plan.mission_tasks & to_unmark)
|
|
1840
|
+
# Remove all missions that are finished
|
|
1841
|
+
for finished_mission in finished_missions
|
|
1842
|
+
if !finished_mission.being_repaired?
|
|
1843
|
+
plan.unmark_mission_task(finished_mission)
|
|
1844
|
+
end
|
|
1845
|
+
end
|
|
1846
|
+
finished_permanent = (plan.permanent_tasks & to_unmark)
|
|
1847
|
+
for finished_permanent in (plan.permanent_tasks & to_unmark)
|
|
1848
|
+
if !finished_permanent.being_repaired?
|
|
1849
|
+
plan.unmark_permanent_task(finished_permanent)
|
|
1850
|
+
end
|
|
1851
|
+
end
|
|
1852
|
+
end
|
|
973
1853
|
|
|
974
1854
|
# Kills and removes all unneeded tasks. +force_on+ is a set of task
|
|
975
1855
|
# whose garbage-collection must be performed, even though those tasks
|
|
976
1856
|
# are actually useful for the system. This is used to properly kill
|
|
977
1857
|
# tasks for which errors have been detected.
|
|
1858
|
+
#
|
|
1859
|
+
# @return [Boolean] true if events have been called (thus requiring
|
|
1860
|
+
# some propagation) and false otherwise
|
|
978
1861
|
def garbage_collect(force_on = nil)
|
|
979
1862
|
if force_on && !force_on.empty?
|
|
980
|
-
|
|
981
|
-
|
|
1863
|
+
info "GC: adding #{force_on.size} tasks in the force_gc set"
|
|
1864
|
+
mismatching_plan = force_on.find_all do |t|
|
|
1865
|
+
if t.plan == self.plan
|
|
1866
|
+
plan.force_gc << t
|
|
1867
|
+
false
|
|
1868
|
+
else
|
|
1869
|
+
true
|
|
1870
|
+
end
|
|
1871
|
+
end
|
|
1872
|
+
if !mismatching_plan.empty?
|
|
1873
|
+
raise ArgumentError, "#{mismatching_plan.map { |t| "#{t}(plan=#{t.plan})" }.join(", ")} have been given to #{self}.garbage_collect, but they are not tasks in #{plan}"
|
|
1874
|
+
end
|
|
982
1875
|
end
|
|
983
1876
|
|
|
1877
|
+
unmark_finished_missions_and_permanent_tasks
|
|
1878
|
+
|
|
984
1879
|
# The set of tasks for which we queued stop! at this cycle
|
|
985
1880
|
# #finishing? is false until the next event propagation cycle
|
|
986
|
-
finishing =
|
|
1881
|
+
finishing = Set.new
|
|
987
1882
|
did_something = true
|
|
988
1883
|
while did_something
|
|
989
1884
|
did_something = false
|
|
@@ -994,138 +1889,163 @@ def garbage_collect(force_on = nil)
|
|
|
994
1889
|
|
|
995
1890
|
# Remote tasks are simply removed, regardless of other concerns
|
|
996
1891
|
for t in remote_tasks
|
|
997
|
-
|
|
998
|
-
plan.
|
|
1892
|
+
debug { "GC: removing the remote task #{t}" }
|
|
1893
|
+
plan.garbage_task(t)
|
|
999
1894
|
end
|
|
1000
1895
|
|
|
1001
1896
|
break if local_tasks.empty?
|
|
1002
1897
|
|
|
1003
|
-
|
|
1898
|
+
debug do
|
|
1899
|
+
debug "#{local_tasks.size} tasks are unneeded in this plan"
|
|
1004
1900
|
local_tasks.each do |t|
|
|
1005
|
-
|
|
1006
|
-
plan.garbage(t)
|
|
1007
|
-
plan.remove_object(t)
|
|
1901
|
+
debug " #{t} mission=#{plan.mission_task?(t)} permanent=#{plan.permanent_task?(t)}"
|
|
1008
1902
|
end
|
|
1009
1903
|
break
|
|
1010
1904
|
end
|
|
1011
1905
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
plan.garbage(t)
|
|
1018
|
-
true
|
|
1019
|
-
else
|
|
1020
|
-
ExecutionEngine.debug { "GC: ignoring #{t}, it is not root" }
|
|
1021
|
-
false
|
|
1906
|
+
if local_tasks.all? { |t| t.pending? || t.finished? }
|
|
1907
|
+
local_tasks.each do |t|
|
|
1908
|
+
debug { "GC: #{t} is not running, removed" }
|
|
1909
|
+
if plan.garbage_task(t)
|
|
1910
|
+
did_something = true
|
|
1022
1911
|
end
|
|
1023
1912
|
end
|
|
1913
|
+
break
|
|
1914
|
+
end
|
|
1024
1915
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
local_tasks.each do |t|
|
|
1032
|
-
t.each_graph do |rel|
|
|
1033
|
-
rel.remove(t) if rel.weak?
|
|
1034
|
-
end
|
|
1916
|
+
# Mark all root local_tasks as garbage.
|
|
1917
|
+
roots = local_tasks.dup
|
|
1918
|
+
plan.each_task_relation_graph do |g|
|
|
1919
|
+
next if !g.root_relation? || g.weak?
|
|
1920
|
+
roots.delete_if do |t|
|
|
1921
|
+
g.each_in_neighbour(t).any? { |p| !p.finished? }
|
|
1035
1922
|
end
|
|
1923
|
+
break if roots.empty?
|
|
1036
1924
|
end
|
|
1037
1925
|
|
|
1038
|
-
(roots.
|
|
1039
|
-
if local_task.pending?
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1926
|
+
(roots.to_set - finishing).each do |local_task|
|
|
1927
|
+
if local_task.pending?
|
|
1928
|
+
info "GC: removing pending task #{local_task}"
|
|
1929
|
+
|
|
1930
|
+
if plan.garbage_task(local_task)
|
|
1931
|
+
did_something = true
|
|
1932
|
+
end
|
|
1933
|
+
elsif local_task.failed_to_start?
|
|
1934
|
+
info "GC: removing task that failed to start #{local_task}"
|
|
1935
|
+
if plan.garbage_task(local_task)
|
|
1936
|
+
did_something = true
|
|
1937
|
+
end
|
|
1043
1938
|
elsif local_task.starting?
|
|
1044
1939
|
# wait for task to be started before killing it
|
|
1045
|
-
|
|
1046
|
-
elsif
|
|
1047
|
-
|
|
1048
|
-
plan.
|
|
1049
|
-
|
|
1940
|
+
debug { "GC: #{local_task} is starting" }
|
|
1941
|
+
elsif local_task.finished?
|
|
1942
|
+
debug { "GC: #{local_task} is not running, removed" }
|
|
1943
|
+
if plan.garbage_task(local_task)
|
|
1944
|
+
did_something = true
|
|
1945
|
+
end
|
|
1050
1946
|
elsif !local_task.finishing?
|
|
1051
|
-
if local_task.
|
|
1052
|
-
|
|
1947
|
+
if local_task.quarantined?
|
|
1948
|
+
warn "GC: #{local_task} is running but in quarantine"
|
|
1949
|
+
elsif local_task.event(:stop).controlable?
|
|
1950
|
+
debug { "GC: attempting to stop #{local_task}" }
|
|
1053
1951
|
if !local_task.respond_to?(:stop!)
|
|
1054
|
-
|
|
1055
|
-
plan.
|
|
1952
|
+
warn "something fishy: #{local_task}/stop is controlable but there is no #stop! method, putting in quarantine"
|
|
1953
|
+
plan.quarantine_task(local_task)
|
|
1056
1954
|
else
|
|
1057
1955
|
finishing << local_task
|
|
1058
|
-
once do
|
|
1059
|
-
ExecutionEngine.info { "GC: stopping #{local_task}" }
|
|
1060
|
-
local_task.stop!(nil)
|
|
1061
|
-
end
|
|
1062
1956
|
end
|
|
1063
1957
|
else
|
|
1064
|
-
|
|
1065
|
-
plan.
|
|
1958
|
+
warn "GC: #{local_task} cannot be stopped, putting in quarantine"
|
|
1959
|
+
plan.quarantine_task(local_task)
|
|
1066
1960
|
end
|
|
1067
1961
|
elsif local_task.finishing?
|
|
1068
|
-
|
|
1962
|
+
debug do
|
|
1963
|
+
debug "GC: waiting for #{local_task} to finish"
|
|
1964
|
+
local_task.history.each do |ev|
|
|
1965
|
+
debug "GC: #{ev}"
|
|
1966
|
+
end
|
|
1967
|
+
break
|
|
1968
|
+
end
|
|
1069
1969
|
else
|
|
1070
|
-
|
|
1970
|
+
warn "GC: ignored #{local_task}"
|
|
1071
1971
|
end
|
|
1072
1972
|
end
|
|
1073
1973
|
end
|
|
1074
1974
|
|
|
1975
|
+
finishing.each do |task|
|
|
1976
|
+
task.stop!
|
|
1977
|
+
end
|
|
1978
|
+
|
|
1075
1979
|
plan.unneeded_events.each do |event|
|
|
1076
|
-
plan.
|
|
1980
|
+
plan.garbage_event(event)
|
|
1077
1981
|
end
|
|
1078
|
-
end
|
|
1079
1982
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
SLEEP_MIN_TIME = 0.01
|
|
1983
|
+
!finishing.empty?
|
|
1984
|
+
end
|
|
1083
1985
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1986
|
+
# Do not sleep or call Thread#pass if there is less that
|
|
1987
|
+
# this much time left in the cycle
|
|
1988
|
+
SLEEP_MIN_TIME = 0.01
|
|
1086
1989
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1990
|
+
# Blocks until at least once execution cycle has been done
|
|
1991
|
+
def wait_one_cycle
|
|
1992
|
+
current_cycle = execute { cycle_index }
|
|
1993
|
+
while current_cycle == execute { cycle_index }
|
|
1994
|
+
raise ExecutionQuitError if !running?
|
|
1995
|
+
sleep(cycle_length)
|
|
1996
|
+
end
|
|
1997
|
+
end
|
|
1095
1998
|
|
|
1096
1999
|
# Calls the periodic blocks which should be called
|
|
1097
2000
|
def self.call_every(plan) # :nodoc:
|
|
1098
|
-
engine = plan.
|
|
2001
|
+
engine = plan.execution_engine
|
|
1099
2002
|
now = engine.cycle_start
|
|
1100
2003
|
length = engine.cycle_length
|
|
1101
2004
|
engine.process_every.map! do |block, last_call, duration|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if !
|
|
1106
|
-
|
|
1107
|
-
last_call = now
|
|
2005
|
+
# Check if the nearest timepoint is the beginning of
|
|
2006
|
+
# this cycle or of the next cycle
|
|
2007
|
+
if !last_call || (duration - (now - last_call)) < length / 2
|
|
2008
|
+
if !block.call(engine, engine.plan)
|
|
2009
|
+
next
|
|
1108
2010
|
end
|
|
1109
|
-
|
|
1110
|
-
|
|
2011
|
+
|
|
2012
|
+
last_call = now
|
|
1111
2013
|
end
|
|
1112
2014
|
[block, last_call, duration]
|
|
1113
|
-
end
|
|
2015
|
+
end.compact!
|
|
1114
2016
|
end
|
|
1115
2017
|
|
|
1116
|
-
# A list of
|
|
1117
|
-
#
|
|
2018
|
+
# A list of threaded objects waiting for the control thread
|
|
2019
|
+
#
|
|
2020
|
+
# Objects registered here will be notified them by calling {#fail} when
|
|
2021
|
+
# it quits. In addition, {#join_all_waiting_work} will wait for all
|
|
2022
|
+
# pending jobs to finish.
|
|
2023
|
+
#
|
|
2024
|
+
# Note that all {Concurrent::Obligation} subclasses fit the bill
|
|
1118
2025
|
#
|
|
1119
|
-
#
|
|
1120
|
-
|
|
1121
|
-
attr_reader :waiting_threads
|
|
2026
|
+
# @return [Array<#fail,#complete?>]
|
|
2027
|
+
attr_reader :waiting_work
|
|
1122
2028
|
|
|
1123
2029
|
# A set of blocks that are called at each cycle end
|
|
1124
2030
|
attr_reader :at_cycle_end_handlers
|
|
1125
2031
|
|
|
1126
|
-
#
|
|
1127
|
-
|
|
1128
|
-
|
|
2032
|
+
# Adds a block to be called at the end of each execution cycle
|
|
2033
|
+
#
|
|
2034
|
+
# @return [Object] an object that allows to identify the block so that
|
|
2035
|
+
# it can be removed with {#remove_at_cycle_end}
|
|
2036
|
+
#
|
|
2037
|
+
# @yieldparam [Plan] plan the plan on which this engine runs
|
|
2038
|
+
def at_cycle_end(description: 'at_cycle_end', **options, &block)
|
|
2039
|
+
handler = PollBlockDefinition.new(description, block, **options)
|
|
2040
|
+
at_cycle_end_handlers << handler
|
|
2041
|
+
handler.object_id
|
|
2042
|
+
end
|
|
2043
|
+
|
|
2044
|
+
# Removes a handler added by {#at_cycle_end}
|
|
2045
|
+
#
|
|
2046
|
+
# @param [Object] handler_id the value returned by {#at_cycle_end}
|
|
2047
|
+
def remove_at_cycle_end(handler_id)
|
|
2048
|
+
at_cycle_end_handlers.delete_if { |h| h.object_id == handler_id }
|
|
1129
2049
|
end
|
|
1130
2050
|
|
|
1131
2051
|
# A set of blocks which are called every cycle
|
|
@@ -1136,110 +2056,120 @@ def at_cycle_end(&block)
|
|
|
1136
2056
|
#
|
|
1137
2057
|
# The returned value is the periodic handler ID. It can be passed to
|
|
1138
2058
|
# #remove_periodic_handler to undefine it.
|
|
1139
|
-
def every(duration, &block)
|
|
2059
|
+
def every(duration, description: 'periodic handler', **options, &block)
|
|
2060
|
+
handler = PollBlockDefinition.new(description, block, **options)
|
|
2061
|
+
|
|
1140
2062
|
once do
|
|
1141
|
-
|
|
1142
|
-
|
|
2063
|
+
if handler.call(self, plan)
|
|
2064
|
+
process_every << [handler, cycle_start, duration]
|
|
2065
|
+
end
|
|
1143
2066
|
end
|
|
1144
|
-
|
|
2067
|
+
handler.id
|
|
1145
2068
|
end
|
|
1146
2069
|
|
|
1147
2070
|
# Removes a periodic handler defined by #every. +id+ is the value
|
|
1148
2071
|
# returned by #every.
|
|
1149
2072
|
def remove_periodic_handler(id)
|
|
1150
2073
|
execute do
|
|
1151
|
-
process_every.delete_if { |spec| spec[0].
|
|
2074
|
+
process_every.delete_if { |spec| spec[0].id == id }
|
|
1152
2075
|
end
|
|
1153
2076
|
end
|
|
1154
2077
|
|
|
1155
2078
|
# The execution thread if there is one running
|
|
1156
|
-
|
|
2079
|
+
attr_accessor :thread
|
|
1157
2080
|
# True if an execution thread is running
|
|
1158
|
-
|
|
2081
|
+
attr_predicate :running?, true
|
|
1159
2082
|
|
|
1160
|
-
|
|
1161
|
-
|
|
2083
|
+
# The cycle length in seconds
|
|
2084
|
+
attr_reader :cycle_length
|
|
1162
2085
|
|
|
1163
|
-
|
|
1164
|
-
|
|
2086
|
+
# The starting Time of this cycle
|
|
2087
|
+
attr_reader :cycle_start
|
|
1165
2088
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
2089
|
+
# The number of this cycle since the beginning
|
|
2090
|
+
attr_reader :cycle_index
|
|
2091
|
+
|
|
2092
|
+
# True if the current thread is the execution thread of this engine
|
|
2093
|
+
#
|
|
2094
|
+
# See #outside_control? for a discussion of the use of #inside_control?
|
|
2095
|
+
# and #outside_control? when testing the threading context
|
|
2096
|
+
def inside_control?
|
|
2097
|
+
t = thread
|
|
2098
|
+
!t || t == Thread.current
|
|
2099
|
+
end
|
|
1177
2100
|
|
|
1178
2101
|
# True if the current thread is not the execution thread of this
|
|
1179
2102
|
# engine, or if there is not control thread. When you check the current
|
|
1180
2103
|
# thread context, always use a negated form. Do not do
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
2104
|
+
#
|
|
2105
|
+
# if Roby.inside_control?
|
|
2106
|
+
# ERROR
|
|
2107
|
+
# end
|
|
2108
|
+
#
|
|
2109
|
+
# Do instead
|
|
2110
|
+
#
|
|
2111
|
+
# if !Roby.outside_control?
|
|
2112
|
+
# ERROR
|
|
2113
|
+
# end
|
|
2114
|
+
#
|
|
1192
2115
|
# Since the first form will fail if there is no control thread, while
|
|
1193
2116
|
# the second form will work. Use the first form only if you require
|
|
1194
2117
|
# that there actually IS a control thread.
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
# cycle:: the cycle duration in seconds (default: 0.1)
|
|
1202
|
-
def run(options = {})
|
|
1203
|
-
if running?
|
|
1204
|
-
raise "there is already a control running in thread #{@thread}"
|
|
1205
|
-
end
|
|
2118
|
+
def outside_control?
|
|
2119
|
+
t = thread
|
|
2120
|
+
!t || t != Thread.current
|
|
2121
|
+
end
|
|
2122
|
+
|
|
2123
|
+
class AlreadyRunning < RuntimeError; end
|
|
1206
2124
|
|
|
1207
|
-
|
|
2125
|
+
# Main event loop. Valid options are
|
|
2126
|
+
# cycle:: the cycle duration in seconds (default: 0.1)
|
|
2127
|
+
def run(cycle: 0.1)
|
|
2128
|
+
if running?
|
|
2129
|
+
raise AlreadyRunning, "#run has already been called"
|
|
2130
|
+
end
|
|
2131
|
+
self.running = true
|
|
1208
2132
|
|
|
1209
|
-
@quit = 0
|
|
1210
2133
|
@allow_propagation = false
|
|
2134
|
+
@waiting_work = Concurrent::Array.new
|
|
1211
2135
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
mt.synchronize do
|
|
1215
|
-
@thread = Thread.new do
|
|
1216
|
-
@thread = Thread.current
|
|
1217
|
-
@thread.priority = THREAD_PRIORITY
|
|
2136
|
+
@thread = Thread.current
|
|
2137
|
+
@thread.name = "MAIN"
|
|
1218
2138
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
2139
|
+
@cycle_length = cycle
|
|
2140
|
+
event_loop
|
|
2141
|
+
|
|
2142
|
+
ensure
|
|
2143
|
+
self.running = false
|
|
2144
|
+
@thread = nil
|
|
2145
|
+
waiting_work.delete_if do |w|
|
|
2146
|
+
next(true) if w.complete?
|
|
2147
|
+
|
|
2148
|
+
# rubocop:disable Lint/HandleExceptions
|
|
2149
|
+
begin
|
|
2150
|
+
w.fail ExecutionQuitError
|
|
2151
|
+
Roby.warn "forcefully terminated #{w} on quit"
|
|
2152
|
+
rescue Concurrent::MultipleAssignmentError
|
|
2153
|
+
# Race condition: something completed the promise while
|
|
2154
|
+
# we were trying to make it fail
|
|
2155
|
+
end
|
|
2156
|
+
# rubocop:enable Lint/HandleExceptions
|
|
2157
|
+
|
|
2158
|
+
true
|
|
2159
|
+
end
|
|
2160
|
+
finalizers.each do |blk|
|
|
2161
|
+
begin
|
|
2162
|
+
blk.call
|
|
2163
|
+
rescue Exception => e
|
|
2164
|
+
Roby.warn "finalizer #{blk} failed"
|
|
2165
|
+
Roby.log_exception_with_backtrace(e, Roby, :warn)
|
|
1238
2166
|
end
|
|
1239
2167
|
end
|
|
1240
|
-
|
|
2168
|
+
@quit = 0
|
|
2169
|
+
@allow_propagation = true
|
|
2170
|
+
end
|
|
1241
2171
|
|
|
1242
|
-
|
|
2172
|
+
attr_reader :last_stop_count # :nodoc:
|
|
1243
2173
|
|
|
1244
2174
|
# Sets up the plan for clearing: it discards all missions and undefines
|
|
1245
2175
|
# all permanent tasks and events.
|
|
@@ -1247,268 +2177,270 @@ def run(options = {})
|
|
|
1247
2177
|
# Returns nil if the plan is cleared, and the set of remaining tasks
|
|
1248
2178
|
# otherwise. Note that quaranteened tasks are not counted as remaining,
|
|
1249
2179
|
# as it is not possible for the execution engine to stop them.
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
ExecutionEngine.info "control quitting. Waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
|
|
1275
|
-
ExecutionEngine.info " " + remaining.to_a.join("\n ")
|
|
1276
|
-
else
|
|
1277
|
-
ExecutionEngine.info "waiting for #{remaining.size} tasks to finish (#{plan.size} tasks still in plan)"
|
|
1278
|
-
ExecutionEngine.info " #{remaining.to_a.join("\n ")}"
|
|
1279
|
-
end
|
|
1280
|
-
if plan.gc_quarantine.size != 0
|
|
1281
|
-
ExecutionEngine.info "#{plan.gc_quarantine.size} tasks in quarantine"
|
|
1282
|
-
end
|
|
1283
|
-
@last_stop_count = remaining.size
|
|
1284
|
-
end
|
|
1285
|
-
remaining
|
|
1286
|
-
end
|
|
1287
|
-
end
|
|
1288
|
-
|
|
1289
|
-
# How much time remains before the end of the cycle. Updated by
|
|
1290
|
-
# #add_timepoint
|
|
1291
|
-
attr_reader :remaining_cycle_time
|
|
1292
|
-
|
|
1293
|
-
# Adds to the stats the given duration as the expected duration of the
|
|
1294
|
-
# +name+ step. The field in +stats+ is named "expected_#{name}".
|
|
1295
|
-
def add_expected_duration(stats, name, duration)
|
|
1296
|
-
stats[:"expected_#{name}"] = Time.now + duration - stats[:start]
|
|
1297
|
-
end
|
|
1298
|
-
|
|
1299
|
-
# Adds in +stats+ the current time as a timepoint named +time+, and
|
|
1300
|
-
# update #remaining_cycle_time
|
|
1301
|
-
def add_timepoint(stats, name)
|
|
1302
|
-
stats[:end] = stats[name] = Time.now - stats[:start]
|
|
1303
|
-
@remaining_cycle_time = cycle_length - stats[:end]
|
|
2180
|
+
def clear
|
|
2181
|
+
plan.mission_tasks.dup.each { |t| plan.unmark_mission_task(t) }
|
|
2182
|
+
plan.permanent_tasks.dup.each { |t| plan.unmark_permanent_task(t) }
|
|
2183
|
+
plan.permanent_events.dup.each { |t| plan.unmark_permanent_event(t) }
|
|
2184
|
+
plan.force_gc.merge( plan.tasks )
|
|
2185
|
+
|
|
2186
|
+
quaranteened_subplan = plan.compute_useful_tasks(plan.quarantined_tasks)
|
|
2187
|
+
remaining = plan.tasks - quaranteened_subplan
|
|
2188
|
+
|
|
2189
|
+
@pending_exceptions.clear
|
|
2190
|
+
|
|
2191
|
+
if remaining.empty?
|
|
2192
|
+
# Have to call #garbage_collect one more to make
|
|
2193
|
+
# sure that unneeded events are removed as well
|
|
2194
|
+
garbage_collect
|
|
2195
|
+
# Done cleaning the tasks, clear the remains
|
|
2196
|
+
plan.transactions.each do |trsc|
|
|
2197
|
+
trsc.discard_transaction if trsc.self_owned?
|
|
2198
|
+
end
|
|
2199
|
+
plan.clear
|
|
2200
|
+
emitted_events.clear
|
|
2201
|
+
return
|
|
2202
|
+
end
|
|
2203
|
+
remaining
|
|
1304
2204
|
end
|
|
1305
2205
|
|
|
1306
2206
|
# If set to true, Roby will warn if the GC cannot be controlled by Roby
|
|
1307
2207
|
attr_predicate :gc_warning?, true
|
|
1308
2208
|
|
|
2209
|
+
def issue_quit_progression_warning(remaining)
|
|
2210
|
+
info "Waiting for #{remaining.size} tasks to finish (#{plan.num_tasks} tasks still in plan) and #{waiting_work.size} async work jobs"
|
|
2211
|
+
remaining.each do |task|
|
|
2212
|
+
info " #{task}"
|
|
2213
|
+
end
|
|
2214
|
+
quarantined = remaining.find_all { |t| t.quarantined? }
|
|
2215
|
+
if quarantined.size != 0
|
|
2216
|
+
info "#{quarantined.size} tasks in quarantine"
|
|
2217
|
+
end
|
|
2218
|
+
end
|
|
2219
|
+
|
|
2220
|
+
# How many seconds between two Interrupt before the execution engine's
|
|
2221
|
+
# loop can forcefully quit
|
|
2222
|
+
INTERRUPT_FORCE_EXIT_DEAD_ZONE = 10
|
|
2223
|
+
|
|
1309
2224
|
# The main event loop. It returns when the execution engine is asked to
|
|
1310
2225
|
# quit. In general, this does not need to be called direclty: use #run
|
|
1311
2226
|
# to start the event loop in a separate thread.
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
Roby.format_exception(e).each do |line|
|
|
1346
|
-
ExecutionEngine.warn line
|
|
2227
|
+
def event_loop
|
|
2228
|
+
last_stop_count = 0
|
|
2229
|
+
last_quit_warning = Time.now
|
|
2230
|
+
@cycle_start = Time.now
|
|
2231
|
+
@cycle_index = 0
|
|
2232
|
+
|
|
2233
|
+
force_exit_deadline = nil
|
|
2234
|
+
last_process_times = Process.times
|
|
2235
|
+
last_dump_time = plan.event_logger.dump_time
|
|
2236
|
+
|
|
2237
|
+
loop do
|
|
2238
|
+
begin
|
|
2239
|
+
if profile_gc?
|
|
2240
|
+
GC::Profiler.enable
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
if quitting?
|
|
2244
|
+
if forced_exit?
|
|
2245
|
+
return
|
|
2246
|
+
end
|
|
2247
|
+
|
|
2248
|
+
begin
|
|
2249
|
+
remaining = clear
|
|
2250
|
+
return if !remaining
|
|
2251
|
+
|
|
2252
|
+
if (last_stop_count != remaining.size) || (Time.now - last_quit_warning) > 10
|
|
2253
|
+
if last_stop_count == 0
|
|
2254
|
+
info "Roby quitting ..."
|
|
2255
|
+
end
|
|
2256
|
+
|
|
2257
|
+
issue_quit_progression_warning(remaining)
|
|
2258
|
+
last_quit_warning = Time.now
|
|
2259
|
+
last_stop_count = remaining.size
|
|
1347
2260
|
end
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
@cycle_start += cycle_length
|
|
1354
|
-
@cycle_index += 1
|
|
1355
|
-
end
|
|
1356
|
-
stats[:start] = cycle_start
|
|
1357
|
-
stats[:cycle_index] = cycle_index
|
|
1358
|
-
|
|
1359
|
-
Roby.synchronize do
|
|
1360
|
-
process_events(stats)
|
|
2261
|
+
rescue Exception => e
|
|
2262
|
+
warn "Execution thread failed to clean up"
|
|
2263
|
+
Roby.log_exception_with_backtrace(e, self, :warn, filter: false)
|
|
2264
|
+
return
|
|
2265
|
+
end
|
|
1361
2266
|
end
|
|
1362
2267
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
add_expected_duration(stats, :sleep, remaining_cycle_time)
|
|
1377
|
-
sleep(remaining_cycle_time)
|
|
1378
|
-
end
|
|
1379
|
-
add_timepoint(stats, :sleep)
|
|
1380
|
-
|
|
1381
|
-
# Add some statistics and call cycle_end
|
|
1382
|
-
if defined? Roby::Log
|
|
1383
|
-
stats[:log_queue_size] = Roby::Log.logged_events.size
|
|
1384
|
-
end
|
|
1385
|
-
stats[:plan_task_count] = plan.known_tasks.size
|
|
1386
|
-
stats[:plan_event_count] = plan.free_events.size
|
|
1387
|
-
cpu_time = Process.times
|
|
1388
|
-
cpu_time = (cpu_time.utime + cpu_time.stime) * 1000
|
|
1389
|
-
stats[:cpu_time] = cpu_time - last_cpu_time
|
|
1390
|
-
last_cpu_time = cpu_time
|
|
1391
|
-
|
|
1392
|
-
if ObjectSpace.respond_to?(:live_objects)
|
|
1393
|
-
stats[:object_allocation] = ObjectSpace.allocated_objects - last_allocated_objects
|
|
1394
|
-
stats[:live_objects] = ObjectSpace.live_objects
|
|
1395
|
-
last_allocated_objects = ObjectSpace.allocated_objects
|
|
1396
|
-
end
|
|
1397
|
-
if ObjectSpace.respond_to?(:heap_slots)
|
|
1398
|
-
stats[:heap_slots] = ObjectSpace.heap_slots
|
|
2268
|
+
log_timepoint_group_start "cycle"
|
|
2269
|
+
|
|
2270
|
+
while Time.now > cycle_start + cycle_length
|
|
2271
|
+
@cycle_start += cycle_length
|
|
2272
|
+
@cycle_index += 1
|
|
2273
|
+
end
|
|
2274
|
+
stats = Hash.new
|
|
2275
|
+
stats[:start] = [cycle_start.tv_sec, cycle_start.tv_usec]
|
|
2276
|
+
stats[:actual_start] = Time.now - cycle_start
|
|
2277
|
+
stats[:cycle_index] = cycle_index
|
|
2278
|
+
|
|
2279
|
+
log_timepoint_group 'process_events' do
|
|
2280
|
+
process_events
|
|
1399
2281
|
end
|
|
1400
2282
|
|
|
1401
|
-
|
|
2283
|
+
remaining_cycle_time = cycle_length - (Time.now - cycle_start)
|
|
2284
|
+
|
|
2285
|
+
if use_oob_gc?
|
|
2286
|
+
stats[:pre_oob_gc] = GC.stat
|
|
2287
|
+
GC::OOB.run
|
|
2288
|
+
end
|
|
2289
|
+
|
|
2290
|
+
# Sleep if there is enough time for it
|
|
2291
|
+
if remaining_cycle_time > SLEEP_MIN_TIME
|
|
2292
|
+
sleep(remaining_cycle_time)
|
|
2293
|
+
end
|
|
2294
|
+
log_timepoint 'sleep'
|
|
2295
|
+
|
|
2296
|
+
cycle_end(stats)
|
|
2297
|
+
|
|
2298
|
+
# Log cycle statistics
|
|
2299
|
+
process_times = Process.times
|
|
2300
|
+
dump_time = plan.event_logger.dump_time
|
|
2301
|
+
stats[:log_queue_size] = plan.log_queue_size
|
|
2302
|
+
stats[:plan_task_count] = plan.num_tasks
|
|
2303
|
+
stats[:plan_event_count] = plan.num_free_events
|
|
2304
|
+
stats[:gc] = GC.stat
|
|
2305
|
+
stats[:utime] = process_times.utime - last_process_times.utime
|
|
2306
|
+
stats[:stime] = process_times.stime - last_process_times.stime
|
|
2307
|
+
stats[:dump_time] = dump_time - last_dump_time
|
|
1402
2308
|
stats[:state] = Roby::State
|
|
1403
|
-
|
|
2309
|
+
stats[:end] = Time.now - cycle_start
|
|
2310
|
+
if profile_gc?
|
|
2311
|
+
stats[:gc_profile_data] = GC::Profiler.raw_data
|
|
2312
|
+
stats[:gc_total_time] = GC::Profiler.total_time
|
|
2313
|
+
else
|
|
2314
|
+
stats[:gc_profile_data] = nil
|
|
2315
|
+
stats[:gc_total_time] = 0
|
|
2316
|
+
end
|
|
2317
|
+
log_flush_cycle :cycle_end, stats
|
|
2318
|
+
|
|
2319
|
+
last_dump_time = dump_time
|
|
2320
|
+
last_process_times = process_times
|
|
1404
2321
|
stats = Hash.new
|
|
1405
2322
|
|
|
1406
|
-
|
|
1407
|
-
|
|
2323
|
+
@cycle_start += cycle_length
|
|
2324
|
+
@cycle_index += 1
|
|
2325
|
+
|
|
2326
|
+
if profile_gc?
|
|
2327
|
+
GC::Profiler.disable
|
|
2328
|
+
end
|
|
2329
|
+
|
|
2330
|
+
rescue Exception => e
|
|
2331
|
+
if e.kind_of?(Interrupt)
|
|
2332
|
+
if quitting?
|
|
2333
|
+
if force_exit_deadline && (force_exit_deadline - Time.now) < 0
|
|
2334
|
+
fatal "Quitting without cleaning up"
|
|
2335
|
+
force_quit
|
|
2336
|
+
else
|
|
2337
|
+
fatal "Still #{Integer(force_exit_deadline - Time.now)}s before interruption will quit without cleaning up"
|
|
2338
|
+
end
|
|
2339
|
+
else
|
|
2340
|
+
fatal "Received interruption request"
|
|
2341
|
+
fatal "Interrupt again in #{INTERRUPT_FORCE_EXIT_DEAD_ZONE}s to quit without cleaning up"
|
|
2342
|
+
quit
|
|
2343
|
+
force_exit_deadline = Time.now + INTERRUPT_FORCE_EXIT_DEAD_ZONE
|
|
2344
|
+
end
|
|
2345
|
+
elsif !quitting?
|
|
2346
|
+
quit
|
|
1408
2347
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
2348
|
+
fatal "Execution thread quitting because of unhandled exception"
|
|
2349
|
+
Roby.log_exception_with_backtrace(e, self, :fatal)
|
|
2350
|
+
else
|
|
2351
|
+
fatal "Execution thread FORCEFULLY quitting because of unhandled exception"
|
|
2352
|
+
Roby.log_exception_with_backtrace(e, self, :fatal)
|
|
2353
|
+
raise
|
|
1413
2354
|
end
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
2355
|
+
ensure
|
|
2356
|
+
log_timepoint_group_end "cycle"
|
|
2357
|
+
end
|
|
2358
|
+
end
|
|
1417
2359
|
|
|
1418
|
-
|
|
1419
|
-
|
|
2360
|
+
ensure
|
|
2361
|
+
if !plan.tasks.empty?
|
|
2362
|
+
warn "the following tasks are still present in the plan:"
|
|
2363
|
+
plan.tasks.each do |t|
|
|
2364
|
+
warn " #{t}"
|
|
2365
|
+
end
|
|
2366
|
+
end
|
|
2367
|
+
end
|
|
1420
2368
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
2369
|
+
# Set the cycle_start attribute and increment cycle_index
|
|
2370
|
+
#
|
|
2371
|
+
# This is only used for testing purposes
|
|
2372
|
+
def start_new_cycle(time = Time.now)
|
|
2373
|
+
@cycle_start = time
|
|
2374
|
+
@cycle_index += 1
|
|
2375
|
+
end
|
|
1428
2376
|
|
|
1429
2377
|
# A set of proc objects which are to be called when the execution engine
|
|
1430
2378
|
# quits.
|
|
1431
2379
|
attr_reader :finalizers
|
|
1432
2380
|
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
# If the event thread has been started in its own thread,
|
|
1454
|
-
# wait for it to terminate
|
|
1455
|
-
def join
|
|
1456
|
-
thread.join if thread
|
|
1457
|
-
|
|
1458
|
-
rescue Interrupt
|
|
1459
|
-
Roby.synchronize do
|
|
1460
|
-
return unless thread
|
|
1461
|
-
|
|
1462
|
-
ExecutionEngine.logger.level = Logger::INFO
|
|
1463
|
-
ExecutionEngine.warn "received interruption request"
|
|
1464
|
-
quit
|
|
1465
|
-
if @quit > 2
|
|
1466
|
-
thread.raise Interrupt, "interrupting control thread at user request"
|
|
1467
|
-
end
|
|
1468
|
-
end
|
|
1469
|
-
|
|
1470
|
-
retry
|
|
1471
|
-
end
|
|
2381
|
+
# True if the control thread is currently quitting
|
|
2382
|
+
def quitting?; @quit > 0 end
|
|
2383
|
+
# True if the control thread is currently quitting
|
|
2384
|
+
def forced_exit?; @quit > 1 end
|
|
2385
|
+
# Make control quit properly
|
|
2386
|
+
def quit; @quit = 1 end
|
|
2387
|
+
# Force quitting, without cleaning up
|
|
2388
|
+
def force_quit; @quit = 2 end
|
|
2389
|
+
|
|
2390
|
+
# Make a quit EE ready for reuse
|
|
2391
|
+
def reset
|
|
2392
|
+
@quit = 0
|
|
2393
|
+
end
|
|
2394
|
+
|
|
2395
|
+
# Called at each cycle end
|
|
2396
|
+
def cycle_end(stats, raise_framework_errors: Roby.app.abort_on_application_exception?)
|
|
2397
|
+
gather_framework_errors("#cycle_end", raise_caught_exceptions: raise_framework_errors) do
|
|
2398
|
+
call_poll_blocks(at_cycle_end_handlers)
|
|
2399
|
+
end
|
|
2400
|
+
end
|
|
1472
2401
|
|
|
1473
2402
|
# Block until the given block is executed by the execution thread, at
|
|
1474
2403
|
# the beginning of the event loop, in propagation context. If the block
|
|
1475
2404
|
# raises, the exception is raised back in the calling thread.
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
# Roby.global_lock
|
|
1481
|
-
def execute
|
|
1482
|
-
if inside_control?
|
|
1483
|
-
return Roby.synchronize { yield }
|
|
1484
|
-
end
|
|
1485
|
-
|
|
1486
|
-
cv = Roby.condition_variable
|
|
1487
|
-
|
|
1488
|
-
return_value = nil
|
|
1489
|
-
Roby.synchronize do
|
|
1490
|
-
if !running?
|
|
1491
|
-
raise "control thread not running"
|
|
1492
|
-
end
|
|
2405
|
+
def execute(catch: [], type: :external_events)
|
|
2406
|
+
if inside_control?
|
|
2407
|
+
return yield
|
|
2408
|
+
end
|
|
1493
2409
|
|
|
1494
|
-
|
|
1495
|
-
|
|
2410
|
+
capture_catch = lambda do |symbol, *other|
|
|
2411
|
+
caught = catch(symbol) do
|
|
2412
|
+
if other.empty?
|
|
2413
|
+
return [:ret, yield]
|
|
2414
|
+
else
|
|
2415
|
+
return capture_catch(block, *other)
|
|
2416
|
+
end
|
|
2417
|
+
end
|
|
2418
|
+
[:throw, [symbol, caught]]
|
|
2419
|
+
end
|
|
1496
2420
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
2421
|
+
ivar = Concurrent::IVar.new
|
|
2422
|
+
once(sync: ivar, type: type) do
|
|
2423
|
+
begin
|
|
2424
|
+
if !catch.empty?
|
|
2425
|
+
result = capture_catch.call(*catch) { yield }
|
|
2426
|
+
ivar.set(result)
|
|
2427
|
+
else
|
|
2428
|
+
ivar.set([:ret, yield])
|
|
2429
|
+
end
|
|
2430
|
+
rescue ::Exception => e # rubocop:disable Lint/RescueException
|
|
2431
|
+
ivar.set([:raise, e])
|
|
2432
|
+
end
|
|
2433
|
+
end
|
|
1509
2434
|
|
|
1510
|
-
|
|
1511
|
-
|
|
2435
|
+
mode, value = ivar.value!
|
|
2436
|
+
case mode
|
|
2437
|
+
when :ret
|
|
2438
|
+
return value
|
|
2439
|
+
when :throw
|
|
2440
|
+
throw *value
|
|
2441
|
+
else
|
|
2442
|
+
raise value
|
|
2443
|
+
end
|
|
1512
2444
|
end
|
|
1513
2445
|
|
|
1514
2446
|
# Stops the current thread until the given even is emitted. If the event
|
|
@@ -1518,44 +2450,159 @@ def wait_until(ev)
|
|
|
1518
2450
|
raise ThreadMismatch, "cannot use #wait_until in execution threads"
|
|
1519
2451
|
end
|
|
1520
2452
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
2453
|
+
ivar = Concurrent::IVar.new
|
|
2454
|
+
result = nil
|
|
2455
|
+
once(sync: ivar) do
|
|
2456
|
+
if ev.unreachable?
|
|
2457
|
+
ivar.fail(UnreachableEvent.new(ev, ev.unreachability_reason))
|
|
2458
|
+
else
|
|
2459
|
+
ev.if_unreachable(cancel_at_emission: true) do |reason, event|
|
|
2460
|
+
ivar.fail(UnreachableEvent.new(event, reason)) if !ivar.complete?
|
|
2461
|
+
end
|
|
2462
|
+
ev.once do |ev|
|
|
2463
|
+
ivar.set(result) if !ivar.complete?
|
|
2464
|
+
end
|
|
2465
|
+
begin
|
|
2466
|
+
result = yield if block_given?
|
|
2467
|
+
rescue Exception => e
|
|
2468
|
+
ivar.fail(e)
|
|
2469
|
+
end
|
|
2470
|
+
end
|
|
2471
|
+
end
|
|
2472
|
+
ivar.value!
|
|
2473
|
+
end
|
|
2474
|
+
|
|
2475
|
+
def shutdown
|
|
2476
|
+
killall
|
|
2477
|
+
thread_pool.shutdown
|
|
2478
|
+
end
|
|
2479
|
+
|
|
2480
|
+
def reset_thread_pool
|
|
2481
|
+
if @thread_pool
|
|
2482
|
+
@thread_pool.shutdown
|
|
2483
|
+
end
|
|
2484
|
+
@thread_pool = Concurrent::CachedThreadPool.new(idletime: 10)
|
|
2485
|
+
end
|
|
2486
|
+
|
|
2487
|
+
# Kill all tasks that are currently running in the plan
|
|
2488
|
+
def killall
|
|
2489
|
+
scheduler_enabled = scheduler.enabled?
|
|
2490
|
+
|
|
2491
|
+
plan.permanent_tasks.clear
|
|
2492
|
+
plan.permanent_events.clear
|
|
2493
|
+
plan.mission_tasks.clear
|
|
2494
|
+
|
|
2495
|
+
scheduler.enabled = false
|
|
2496
|
+
quit
|
|
2497
|
+
|
|
2498
|
+
start_new_cycle
|
|
2499
|
+
process_events
|
|
2500
|
+
cycle_end(Hash.new)
|
|
2501
|
+
|
|
2502
|
+
plan.transactions.each do |trsc|
|
|
2503
|
+
trsc.discard_transaction!
|
|
2504
|
+
end
|
|
2505
|
+
|
|
2506
|
+
start_new_cycle
|
|
2507
|
+
Thread.pass
|
|
2508
|
+
process_events
|
|
2509
|
+
cycle_end(Hash.new)
|
|
2510
|
+
|
|
2511
|
+
ensure
|
|
2512
|
+
scheduler.enabled = scheduler_enabled
|
|
2513
|
+
end
|
|
2514
|
+
|
|
2515
|
+
# Exception kind passed to {#on_exception} handlers for non-fatal,
|
|
2516
|
+
# unhandled exceptions
|
|
2517
|
+
EXCEPTION_NONFATAL = :nonfatal
|
|
2518
|
+
|
|
2519
|
+
# Exception kind passed to {#on_exception} handlers for fatal,
|
|
2520
|
+
# unhandled exceptions
|
|
2521
|
+
EXCEPTION_FATAL = :fatal
|
|
2522
|
+
|
|
2523
|
+
# Exception kind passed to {#on_exception} handlers for handled
|
|
2524
|
+
# exceptions
|
|
2525
|
+
EXCEPTION_HANDLED = :handled
|
|
2526
|
+
|
|
2527
|
+
# Exception kind passed to {#on_exception} handlers for free event
|
|
2528
|
+
# exceptions
|
|
2529
|
+
EXCEPTION_FREE_EVENT = :free_event
|
|
2530
|
+
|
|
2531
|
+
# Registers a callback that will be called when exceptions are propagated in the plan
|
|
2532
|
+
#
|
|
2533
|
+
# @yieldparam [Symbol] kind one of {EXCEPTION_NONFATAL},
|
|
2534
|
+
# {EXCEPTION_FATAL}, {EXCEPTION_FREE_EVENT} or {EXCEPTION_HANDLED}
|
|
2535
|
+
# @yieldparam [Roby::ExecutionException] error the exception
|
|
2536
|
+
# @yieldparam [Array<Roby::Task>] tasks the tasks that are involved in this exception
|
|
2537
|
+
#
|
|
2538
|
+
# @return [Object] an ID that can be used as argument to {#remove_exception_listener}
|
|
2539
|
+
def on_exception(description: 'exception listener', on_error: :disable, &block)
|
|
2540
|
+
handler = PollBlockDefinition.new(description, block, on_error: on_error)
|
|
2541
|
+
exception_listeners << handler
|
|
2542
|
+
handler
|
|
2543
|
+
end
|
|
2544
|
+
|
|
2545
|
+
# Controls whether this engine should indiscriminately display all fatal
|
|
2546
|
+
# exceptions
|
|
2547
|
+
#
|
|
2548
|
+
# This is on by default
|
|
2549
|
+
def display_exceptions=(flag)
|
|
2550
|
+
if flag
|
|
2551
|
+
@exception_display_handler ||= on_exception do |kind, error, tasks|
|
|
2552
|
+
level = if kind == EXCEPTION_HANDLED then :debug
|
|
2553
|
+
else :warn
|
|
2554
|
+
end
|
|
1526
2555
|
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
2556
|
+
send(level) do
|
|
2557
|
+
send(level, "encountered a #{kind} exception")
|
|
2558
|
+
Roby.log_exception_with_backtrace(error.exception, self, level)
|
|
2559
|
+
if kind == EXCEPTION_HANDLED
|
|
2560
|
+
send(level, "the exception was handled by")
|
|
2561
|
+
else
|
|
2562
|
+
send(level, "the exception involved")
|
|
1531
2563
|
end
|
|
1532
|
-
|
|
1533
|
-
|
|
2564
|
+
tasks.each do |t|
|
|
2565
|
+
send(level, " #{t}")
|
|
1534
2566
|
end
|
|
1535
|
-
|
|
2567
|
+
break
|
|
1536
2568
|
end
|
|
1537
|
-
cv.wait(mt)
|
|
1538
2569
|
end
|
|
2570
|
+
else
|
|
2571
|
+
remove_exception_listener(@exception_display_handler)
|
|
2572
|
+
@exception_display_handler = nil
|
|
1539
2573
|
end
|
|
1540
2574
|
end
|
|
1541
|
-
end
|
|
1542
2575
|
|
|
1543
|
-
|
|
1544
|
-
#
|
|
1545
|
-
|
|
2576
|
+
# whether this engine should indiscriminately display all fatal
|
|
2577
|
+
# exceptions
|
|
2578
|
+
def display_exceptions?
|
|
2579
|
+
!!@exception_display_handler
|
|
2580
|
+
end
|
|
2581
|
+
|
|
2582
|
+
# Removes an exception listener registered with {#on_exception}
|
|
2583
|
+
#
|
|
2584
|
+
# @param [Object] the value returned by {#on_exception}
|
|
2585
|
+
# @return [void]
|
|
2586
|
+
def remove_exception_listener(handler)
|
|
2587
|
+
exception_listeners.delete(handler)
|
|
2588
|
+
end
|
|
1546
2589
|
|
|
1547
|
-
#
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
elsif control && new_engine.control != control
|
|
1554
|
-
raise ArgumentError, "must have Roby.control == Roby.engine.control"
|
|
2590
|
+
# Call to notify the listeners registered with {#on_exception} of the
|
|
2591
|
+
# occurence of an exception
|
|
2592
|
+
def notify_exception(kind, error, involved_objects)
|
|
2593
|
+
log(:exception_notification, plan.droby_id, kind, error, involved_objects)
|
|
2594
|
+
exception_listeners.each do |listener|
|
|
2595
|
+
listener.call(self, kind, error, involved_objects)
|
|
1555
2596
|
end
|
|
2597
|
+
end
|
|
1556
2598
|
|
|
1557
|
-
|
|
1558
|
-
|
|
2599
|
+
# Create a promise to execute the given block in a separate thread
|
|
2600
|
+
#
|
|
2601
|
+
# Note that the returned value is a {Roby::Promise}. This means that
|
|
2602
|
+
# callbacks added with #on_success or #rescue will be executed in the
|
|
2603
|
+
# execution engine thread by default.
|
|
2604
|
+
def promise(description: nil, executor: thread_pool, &block)
|
|
2605
|
+
Promise.new(self, executor: executor, description: description, &block)
|
|
1559
2606
|
end
|
|
1560
2607
|
end
|
|
1561
2608
|
|
|
@@ -1563,42 +2610,42 @@ def engine=(new_engine)
|
|
|
1563
2610
|
# wait for its completion like Roby.execute does
|
|
1564
2611
|
#
|
|
1565
2612
|
# See ExecutionEngine#once
|
|
1566
|
-
def self.once;
|
|
2613
|
+
def self.once; execution_engine.once { yield } end
|
|
1567
2614
|
|
|
1568
2615
|
# Make the main engine call +block+ during each propagation step.
|
|
1569
2616
|
# See ExecutionEngine#each_cycle
|
|
1570
|
-
def self.each_cycle(&block);
|
|
2617
|
+
def self.each_cycle(&block); execution_engine.each_cycle(&block) end
|
|
1571
2618
|
|
|
1572
2619
|
# Install a periodic handler on the main engine
|
|
1573
|
-
def self.every(duration, &block);
|
|
2620
|
+
def self.every(duration, options = Hash.new, &block); execution_engine.every(duration, options, &block) end
|
|
1574
2621
|
|
|
1575
2622
|
# True if the current thread is the execution thread of the main engine
|
|
1576
2623
|
#
|
|
1577
2624
|
# See ExecutionEngine#inside_control?
|
|
1578
|
-
def self.inside_control?;
|
|
2625
|
+
def self.inside_control?; execution_engine.inside_control? end
|
|
1579
2626
|
|
|
1580
2627
|
# True if the current thread is not the execution thread of the main engine
|
|
1581
2628
|
#
|
|
1582
2629
|
# See ExecutionEngine#outside_control?
|
|
1583
|
-
def self.outside_control?;
|
|
2630
|
+
def self.outside_control?; execution_engine.outside_control? end
|
|
1584
2631
|
|
|
1585
2632
|
# Execute the given block during the event propagation step of the main
|
|
1586
2633
|
# engine. See ExecutionEngine#execute
|
|
1587
2634
|
def self.execute
|
|
1588
|
-
|
|
2635
|
+
execution_engine.execute do
|
|
1589
2636
|
yield
|
|
1590
2637
|
end
|
|
1591
2638
|
end
|
|
1592
2639
|
|
|
1593
2640
|
# Blocks until the main engine has executed at least one cycle.
|
|
1594
2641
|
# See ExecutionEngine#wait_one_cycle
|
|
1595
|
-
def self.wait_one_cycle;
|
|
2642
|
+
def self.wait_one_cycle; execution_engine.wait_one_cycle end
|
|
1596
2643
|
|
|
1597
2644
|
# Stops the current thread until the given even is emitted. If the event
|
|
1598
2645
|
# becomes unreachable, an UnreachableEvent exception is raised.
|
|
1599
2646
|
#
|
|
1600
2647
|
# See ExecutionEngine#wait_until
|
|
1601
|
-
def self.wait_until(ev, &block);
|
|
2648
|
+
def self.wait_until(ev, &block); execution_engine.wait_until(ev, &block) end
|
|
1602
2649
|
end
|
|
1603
2650
|
|
|
1604
2651
|
|