Skip to content

Commit

Permalink
feat: capture errors during network generation
Browse files Browse the repository at this point in the history
Instead of raising we will capture them, which allows the generation
process to finish for all the tasks that dont contain any errors. For
those that do, these errors are propagate after the network generation
process ends.
  • Loading branch information
jhonasiv committed Feb 4, 2025
1 parent a658ce3 commit f0f50e5
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 34 deletions.
31 changes: 27 additions & 4 deletions ruby/lib/transformer/syskit/extensions/system_network_generator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# frozen_string_literal: true

require "syskit/network_generation"

module Transformer
# Module used to add some functionality to Syskit::NetworkGeneration::Engine
module SystemNetworkGeneratorExtension
Expand All @@ -19,6 +23,16 @@ def transformer_validate_all_frames_assigned
tr.each_needed_transformation do |transform|
transformer_validate_frame_assigned(task, transform.from)
transformer_validate_frame_assigned(task, transform.to)
rescue BaseException => e
failed_task = Syskit::NetworkGeneration::SystemNetworkGenerator
.find_toplevel_tasks_with_index_of(
task, toplevel_tasks: @toplevel_tasks, plan: @plan,
merge_solver: @merge_solver
)
failure = Syskit::NetworkGeneration::InternalResolutionFailure.new(
failed_task, e
)
register_resolution_failure(failure)
end
end
end
Expand All @@ -42,10 +56,19 @@ def transformer_validate_no_missing_transform
raise InvalidChain.new(
placeholder_task.transformer,
task, task_from, from, task_to, to, e
),
"cannot find a transformation chain to produce "\
"#{from} => #{to} for #{task} (task-local frames: "\
"#{task_from} => #{task_to}): #{e.message}", e.backtrace
), "cannot find a transformation chain to produce "\
"#{from} => #{to} for #{task} (task-local frames: "\
"#{task_from} => #{task_to}): #{e.message}", e.backtrace
rescue BaseException => e
failed_task = Syskit::NetworkGeneration::SystemNetworkGenerator
.find_toplevel_tasks_with_index_of(
task, toplevel_tasks: @toplevel_tasks, plan: @plan,
merge_solver: @merge_solver
)
failure = Syskit::NetworkGeneration::InternalResolutionFailure.new(
failed_task, e
)
register_resolution_failure(failure)
end
end

Expand Down
31 changes: 13 additions & 18 deletions ruby/lib/transformer/syskit/frame_propagation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -332,19 +332,8 @@ def propagate_task(task)
# frame to a global frame name
def self.initial_frame_selection_from_device(task, dev)
tr = task.model.transformer
if selected_frame = dev.frame
if !task.transformer.has_frame?(selected_frame)
raise Transformer::InvalidConfiguration, "undefined frame #{selected_frame} selected as reference frame for #{dev}, known frames: #{task.transformer.frames.sort.join(", ")}"
end
end
if selected_transform = dev.frame_transform
if !task.transformer.has_frame?(selected_transform.from)
raise Transformer::InvalidConfiguration, "undefined frame #{selected_transform.from} selected as 'from' frame for #{dev}, known frames: #{task.transformer.frames.sort.join(", ")}"
end
if !task.transformer.has_frame?(selected_transform.to)
raise Transformer::InvalidConfiguration, "undefined frame #{selected_transform.to} selected as 'to' frame for #{dev}, known frames: #{task.transformer.frames.sort.join(", ")}"
end
end
selected_frame = dev.frame
selected_transform = dev.frame_transform

new_selections = Hash.new
task.find_all_driver_services_for(dev).each do |srv|
Expand Down Expand Up @@ -427,15 +416,21 @@ def self.initialize_selected_frames(task, current_selection)
current_selection
else
debug { "adding frame selection from #{task}: #{new_selections}" }
new_selections.each do |frame_name, selected_frame|
if !task.transformer.has_frame?(selected_frame)
raise InvalidConfiguration, "undefined frame #{selected_frame} selected for '#{frame_name}' in #{task}, known frames: #{task.transformer.frames.sort.join(", ")}"
end
end
new_selections
end
result = result.merge(static_frames)
task.select_frames(result)

# Select the frames and then validate that they exist. This is using the fact
# that any raised exception is caught and resolved independently.
result.each do |frame_name, selected_frame|
if !task.transformer.has_frame?(selected_frame)
raise InvalidConfiguration,
"undefined frame #{selected_frame} selected for "\
"'#{frame_name}' in #{task}, known frames: " \
"#{task.transformer.frames.sort.join(", ")}"
end
end
result
end

Expand Down
48 changes: 38 additions & 10 deletions ruby/lib/transformer/syskit/plugin.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# frozen_string_literal: true

require "syskit/network_generation/exceptions"

module Transformer
module SyskitPlugin
class MissingTransform < Syskit::Component
Expand Down Expand Up @@ -88,7 +92,6 @@ def self.compute_required_transformations(manager, task)
)
end


static.each do |trsf|
static_transforms[[trsf.from, trsf.to]] = trsf
end
Expand All @@ -99,10 +102,26 @@ def self.compute_required_transformations(manager, task)
# Just ignore it here, we don't need to instanciate it
# ourselves
next if dyn.producer.kind_of?(Orocos::Spec::Port)

dynamic_transforms[dyn.producer] << dyn
end
end
return static_transforms, dynamic_transforms
[static_transforms, dynamic_transforms]
end

def self.internal_resolution_failure_from_exception(
task, exception, engine, message = ""
)
failed_toplevel_tasks =
Syskit::NetworkGeneration::SystemNetworkGenerator
.find_toplevel_tasks_with_index_of(
task, toplevel_tasks: engine.toplevel_tasks,
plan: engine.plan, merge_solver: engine.merge_solver
)

Syskit::NetworkGeneration::InternalResolutionFailure.new(
failed_toplevel_tasks, exception, message
)
end

def self.instanciate_producer(manager, task, producer_model, transformations)
Expand Down Expand Up @@ -166,7 +185,7 @@ def self.instanciate_producer(manager, task, producer_model, transformations)
#
# @return [Boolean] true if producers have been added to the plan and
# false otherwise
def self.add_needed_producers(tasks, instanciated_producers)
def self.add_needed_producers(tasks, instanciated_producers, engine)
has_new_producers = false
tasks.each do |task|
dependency_graph = task.relation_graph_for(Roby::TaskStructure::Dependency)
Expand All @@ -179,7 +198,7 @@ def self.add_needed_producers(tasks, instanciated_producers)
task.static_transforms = static_transforms.values
dynamic_transforms.each do |producer_model, transformations|
producer_tasks = instanciated_producers[producer_model]
if !producer_tasks.empty?
unless producer_tasks.empty?
is_recursive = producer_tasks.any? do |prod_task|
prod_task == task || dependency_graph.reachable?(prod_task, task)
end
Expand All @@ -188,11 +207,18 @@ def self.add_needed_producers(tasks, instanciated_producers)
end
end

if producer_task = instanciate_producer(tr_manager, task, producer_model, transformations)
producer_task = instanciate_producer(
tr_manager, task, producer_model, transformations
)
if producer_task
has_new_producers = true
instanciated_producers[producer_model] << producer_task
end
end
rescue BaseException => e
failure =
internal_resolution_failure_from_exception(e.task, e, engine)
engine.register_resolution_failure(failure)
end
has_new_producers
end
Expand Down Expand Up @@ -250,6 +276,9 @@ def self.instanciation_postprocessing_hook(engine, plan)
tasks = plan.find_local_tasks(Syskit::Component).roots(Roby::TaskStructure::Dependency)
tasks.each do |root_task|
propagate_local_transformer_configuration(root_task)
rescue BaseException => e
failure = internal_resolution_failure_from_exception(root_task, e, engine)
engine.register_resolution_failure(failure)
end
end

Expand Down Expand Up @@ -282,15 +311,15 @@ def self.propagate_local_transformer_configuration(root_task)
# It only checks its inputs, as it is meant to iterate over all tasks
def self.instanciated_network_postprocessing_hook(engine, plan)
needed = true
all_producers = Hash.new { |h, k| h[k] = Array.new }
all_producers = Hash.new { |h, k| h[k] = [] }
while needed
FramePropagation.compute_frames(plan)
transformer_tasks = plan.find_local_tasks(Syskit::TaskContext).
find_all { |task| task.model.transformer }
transformer_tasks = plan.find_local_tasks(Syskit::TaskContext)
.find_all { |task| task.model.transformer }

# Now find out the frame producers that each task needs, and add them to
# the graph
needed = add_needed_producers(transformer_tasks, all_producers)
needed = add_needed_producers(transformer_tasks, all_producers, engine)
end
end

Expand Down Expand Up @@ -404,4 +433,3 @@ def self.register
end
end
end

40 changes: 38 additions & 2 deletions ruby/test/syskit/test_syskit_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,51 @@
.use_frames("object" => "object_global", "world" => "world_global")
.transformer { frames "object_global", "world_global" }

e = assert_raises(Transformer::InvalidChain) do
e = assert_raises(Syskit::NetworkGeneration::PartialNetworkResolution) do
syskit_deploy(task_m)
end
invalid_chain =
e.original_exceptions
.find { |excpt| excpt.kind_of? Transformer::InvalidChain }

expected_m = "cannot find a transformation chain to produce object_global => "\
"world_global for DataConsumer.* \\\(task-local frames: object => "\
"world\\\): no transformation from 'object_global' to "\
"'world_global' available"
assert_match Regexp.new(expected_m), e.message
assert_match Regexp.new(expected_m), invalid_chain.message
end

it "raises if at least one of the assigned frames is invalid" do
task_m =
data_consumer_m
.use_frames("object" => "object_global", "world" => "wrong_frame")
.transformer { frames "object_global", "world_global" }

e = assert_raises(Syskit::NetworkGeneration::PartialNetworkResolution) do
syskit_deploy(task_m)
end
invalid_configuration =
e.original_exceptions
.find { |excpt| excpt.kind_of? Transformer::InvalidConfiguration }
assert invalid_configuration
expected_m = "undefined frame wrong_frame"
assert_match Regexp.new(expected_m), invalid_configuration.message
end

it "raises if the tasks transform frames arent all assigned" do
task_m =
data_consumer_m
.use_frames("object" => "object_global")
.transformer { frames "object_global", "world_global" }

e = assert_raises(Syskit::NetworkGeneration::PartialNetworkResolution) do
syskit_deploy(task_m)
end
missing_frame = e.original_exceptions
.find { |excpt| excpt.kind_of? Transformer::MissingFrame }
assert missing_frame
expected_m = /could not find a frame assignment for world in/
assert_match expected_m, missing_frame.message
end

it "instanciates dynamic producers" do
Expand Down
1 change: 1 addition & 0 deletions ruby/test/syskit/test_transformer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
before do
Roby.app.import_types_from "base"
Roby.app.import_types_from "transformer"
Roby.app.using_task_library "transformer"
end

it "detects unknown frames" do
Expand Down

0 comments on commit f0f50e5

Please sign in to comment.