roby 0.8.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|