module Syskit
Constants
- ActualDataFlow
(see ConnectionGraph)
- BasicObject
- COLOR_PALETTE
A set of colors to be used in graphiz graphs
- ComBus
- CurrentTaskConfiguration
@api private
The last applied task configuration
- DataFlow
- DataService
- Device
- Flows
- RemoteTaskHandles
@api private
Representation of the handles needed by {Syskit::TaskContext} to get state updates from a remote task
They are initialized once and for all since they won't change across TaskContext restarts, allowing us to save costly back-and-forth between the remote task and the local process
- RequiredDataFlow
(see ConnectionGraph)
- SYSKIT_LIB_DIR
- SYSKIT_ROOT_DIR
- VERSION
Attributes
Used by the to_dot* methods for color allocation
@api private
Event used to quit the ready monitor started by {#schedule_ready_event_monitor}
@return [Concurrent::Event]
Public Class Methods
Returns a color from COLOR_PALETTE, rotating each time the method is called. It is used by the to_dot* methods.
# File lib/syskit/graphviz.rb, line 10 def self.allocate_color @current_color = (@current_color + 1) % COLOR_PALETTE.size COLOR_PALETTE[@current_color] end
Create the spawn options needed to start this deployment for the given configuration
@return [Orocos::Process::CommandLine]
# File lib/syskit/deployment.rb, line 288 def self.command_line(name, name_mappings, working_directory: Roby.app.log_dir, log_level: nil, cmdline_args: Hash.new, tracing: false, gdb: nil, valgrind: nil, name_service_ip: 'localhost', loader: Roby.app.default_pkgconfig_loader) cmdline_args = cmdline_args.dup each_default_run_option do |option_name, option_value| if !cmdline_args.has_key?(option_name) cmdline_args[option_name] = option_value end end process = Orocos::Process.new(name, orogen_model, loader: loader, name_mappings: name_mappings) process.command_line( working_directory: working_directory, log_level: log_level, cmdline_args: cmdline_args, tracing: tracing, gdb: gdb, valgrind: valgrind, name_service_ip: name_service_ip) end
The main configuration object
For consistency reasons, it is also available as Roby.conf.syskit when running in a Roby application
# File lib/syskit/roby_app.rb, line 29 def conf @conf ||= RobyApp::Configuration.new(Roby.app) end
Generic implementation of connection handling
This is used to connect everything that can be connected: component and service instances, composition child models. The method resolves both source and sinks as a set of ports using each_output_port and each_input_port if they are not plain ports, finds which connections need to be created using {Syskit.resolve_connections} and then calls output_port.connect_to input_port for each of these connections.
@param [Port,Models::Port,#each_output_port] source the source part of
the connection
@param [Port,Models::Port,#each_input_port] sink the sink part of the
connection
@param [Hash] policy the connection policy @return [Array<(Port,Port)>] the set of connections actually created @raise (see ::resolve_connections)
# File lib/syskit/connection_graphs.rb, line 151 def self.connect(source, sink, policy) output_ports = if source.respond_to?(:each_output_port) source.each_output_port.to_a else [source] end input_ports = if sink.respond_to?(:each_input_port) sink.each_input_port.to_a else [sink] end connections = resolve_connections(output_ports, input_ports) if connections.empty? raise InvalidAutoConnection.new(source, sink) end connections.each do |out_port, in_port| out_port.connect_to in_port, policy end connections end
This method creates a task model that is an aggregate of all the provided models (components and services)
@option options (see Component#create_proxy_task_model)
# File lib/syskit/models/component.rb, line 1311 def self.create_proxy_task_model_for(models, options = Hash.new) task_model, service_models, _ = resolve_proxy_task_model_requirements(models) task_model.create_proxy_task_model(service_models, options) end
Returns the deployment object that matches the given process object
@param process the deployment's process object. Note that it is
usually not a Ruby Process object, but a process representation from orocosrb's process server infrastructure
# File lib/syskit/deployment.rb, line 704 def self.deployment_by_process(process) all_deployments.fetch(process) end
Extend an auto-generated with custom code
This is used mainly for Syskit models generated from oroGen models, in extension files within models/orogen/:
Syskit.extend_model OroGen.test.Task do
def configure
super
...
end
end
# File lib/syskit/models/task_context.rb, line 21 def self.extend_model(model, &block) model.class_eval(&block) end
Force reconfiguration for all tasks in a plan that match the given orocos name
# File lib/syskit/deployment.rb, line 528 def self.needs_reconfiguration!(plan, orocos_name) plan.find_local_tasks(Syskit::Deployment). each do |deployment_task| if deployment_task.has_orocos_name?(orocos_name) deployment_task.needs_reconfiguration!(orocos_name) end end end
This method creates a task model that can be used to represent the models
listed in models in a plan. The returned task model is
obviously abstract
@option options [Boolean] :force (false) if false, the returned model will
be a plain component model if the models argument contains only a component model. Otherwise, a proxy task model will always be returned
@option options [String] name if given, it is used to name the returned
model. See {Component#create_proxy_task_model) for more details
# File lib/syskit/models/component.rb, line 1325 def self.proxy_task_model_for(models) task_model, service_models, service = resolve_proxy_task_model_requirements(models) # If all that is required is a proper task model, just return it task_model = if service_models.empty? task_model else task_model.proxy_task_model(service_models) end if service service.attach(task_model) else task_model end end
(see Syskit::RobyApp::Configuration#register_process_server)
# File lib/syskit/deployment.rb, line 8 def register_process_server(name, client, log_dir = nil) Syskit.conf.register_process_server(name, client, log_dir = nil) end
Resolves possible connections between a set of output ports and a set of input ports
@param [Array<Port>] output_ports the set of output ports @param [Array<Port>] input_ports the set of output ports @return [Array<(Port,Port)>] the set of connections @raise [AmbiguousAutoConnection] if more than one input port is found
for a given output port
# File lib/syskit/connection_graphs.rb, line 65 def self.resolve_connections(output_ports, input_ports) Models.debug do Models.debug "resolving connections from #{output_ports.map(&:name).sort.join(",")} to #{input_ports.map(&:name).sort.join(",")}" break end result = Array.new matched_input_ports = Set.new # First resolve the exact matches remaining_outputs = output_ports.dup remaining_outputs.delete_if do |out_port| in_port = input_ports. find do |in_port| in_port.name == out_port.name && in_port.type == out_port.type end if in_port result << [out_port, in_port] matched_input_ports << in_port true end end # In the second stage, we match by type. If there are ambiguities, # we try to resolve them by excluding the ports that had an exact # match. This is, by experience, expected behaviour in practice remaining_outputs.each do |out_port| candidates = input_ports. find_all { |in_port| in_port.type == out_port.type } if candidates.size > 1 filtered_candidates = candidates. find_all { |p| !matched_input_ports.include?(p) } if filtered_candidates.size == 1 candidates = filtered_candidates end end if candidates.size > 1 raise AmbiguousAutoConnection.new(out_port, candidates) elsif candidates.size == 1 result << [out_port, candidates.first] end end # Finally, verify that we autoconnect multiple outputs to a single # input only if it is a multiplexing port outputs_per_input = Hash.new result.each do |out_port, in_port| if outputs_per_input[in_port] if !in_port.multiplexes? candidates = result.map { |o, i| o if i == in_port }. compact raise AmbiguousAutoConnection.new(in_port, candidates) end end outputs_per_input[in_port] = out_port end Models.debug do result.each do |out_port, in_port| Models.debug " #{out_port.name} => #{in_port.name}" end if !remaining_outputs.empty? Models.debug " no matches found for outputs #{remaining_outputs.map(&:name).sort.join(",")}" end break end result end
Resolves the base task model and set of service models that should be used to create a proxy task model for the given component and/or service models
@param [Array<Model<Component>,Model<DataService>>] set of component and
services that will be proxied
@return [Model<Component>,Array<Model<DataService>>,(BoundDataService,nil)
This is a helper method for {create_proxy_task_model_for} and {proxy_task_model_for}
# File lib/syskit/models/component.rb, line 1286 def self.resolve_proxy_task_model_requirements(models) service = nil models = models.map do |m| if m.respond_to?(:component_model) service = m m.component_model else m end end task_models, service_models = models.partition { |t| t <= Component } if task_models.empty? return Component, service_models, service elsif task_models.size == 1 task_model = task_models.first service_models.delete_if { |srv| task_model.fullfills?(srv) } return task_model, service_models, service else raise ArgumentError, "cannot create a proxy for multiple component models at the same time" end end
# File lib/syskit/connection_graphs.rb, line 12 def self.update_connection_policy(old, new) old = old.dup new = new.dup if old.empty? return new elsif new.empty? return old end old_fallback = old.delete(:fallback_policy) new_fallback = new.delete(:fallback_policy) if old_fallback && new_fallback fallback = update_connection_policy(old_fallback, new_fallback) else fallback = old_fallback || new_fallback end old = Orocos::Port.validate_policy(old) new = Orocos::Port.validate_policy(new) type = old[:type] || new[:type] merged = old.merge(new) do |key, old_value, new_value| if old_value == new_value old_value elsif key == :type raise ArgumentError, "connection types mismatch: #{old_value} != #{new_value}" elsif key == :transport if old_value == 0 then new_value elsif new_value == 0 then old_value else raise ArgumentError, "policy mismatch for transport: #{old_value} != #{new_value}" end elsif key == :size [old_value, new_value].max else raise ArgumentError, "policy mismatch for #{key}: #{old_value} != #{new_value}" end end if fallback merged[:fallback_policy] = fallback end merged end
# File lib/syskit/roby_app/plugin.rb, line 16 def self.warn_about_new_naming_convention Syskit.warn 'We have finally adopted a systematic naming convention in Syskit, this led to files and classes to be renamed' end
Public Instance Methods
@api private
The currently applied configuration for the given task
# File lib/syskit/deployment.rb, line 510 def configuration_changed?(orocos_name, conf, dynamic_services) current = remote_task_handles[orocos_name].current_configuration current.conf != conf || current.dynamic_services != dynamic_services.to_set end
@api private
Whether one of this deployment's task is being configured
# File lib/syskit/deployment.rb, line 489 def configuring?(orocos_name) remote_task_handles[orocos_name].configuring end
@api private
Called asynchronously to initialize the {RemoteTaskHandles} object once and for all
# File lib/syskit/deployment.rb, line 612 def create_state_access(remote_task, distance: TaskContext::D_UNKNOWN) state_getter = RemoteStateGetter.new( remote_task, initial_state: remote_task.rtt_state) if remote_task.model.extended_state_support? state_port = remote_task.raw_port('state') state_reader = state_port.reader( type: :buffer, size: STATE_READER_BUFFER_SIZE, init: true, distance: distance) state_reader.extend Orocos::TaskContext::StateReader state_reader.state_symbols = remote_task.state_symbols else state_reader = state_getter end return state_reader, state_getter end
Called when the process is finished.
result is the Process::Status object describing how this
process finished.
# File lib/syskit/deployment.rb, line 674 def dead!(result) if history.find(&:terminal?) # Do nothing. A terminal event already happened, so we don't # need to tell what kind of end this is for the system stop_event.emit elsif !result failed_event.emit elsif result.success? success_event.emit elsif result.signaled? signaled_event.emit result else failed_event.emit result end Deployment.all_deployments.delete(orocos_process) # do NOT call cleanup_dead_connections here. # Runtime.update_deployment_states will first announce all the # dead processes and only then call #cleanup_dead_connections, # thus avoiding to disconnect connections between already-dead # processes end
“How far” this deployment is from another
It returns one of the TaskContext::D_ constants
# File lib/syskit/deployment.rb, line 385 def distance_to(other_deployment) if other_deployment == self TaskContext::D_SAME_PROCESS elsif other_deployment.host_id == host_id if host_id == 'syskit' TaskContext::D_SAME_PROCESS else TaskContext::D_SAME_HOST end else TaskContext::D_DIFFERENT_HOSTS end end
How “far” this process is from the Syskit process
@return one of the {TaskContext}::D_* constants
# File lib/syskit/deployment.rb, line 351 def distance_to_syskit if in_process? TaskContext::D_SAME_PROCESS elsif on_localhost? TaskContext::D_SAME_HOST else TaskContext::D_DIFFERENT_HOSTS end end
@api private
Declare that the given task is being configured
# File lib/syskit/deployment.rb, line 503 def finished_configuration(orocos_name) remote_task_handles[orocos_name].configuring = false end
The name of the host this deployment is running on, i.e. the name given to the :on argument.
# File lib/syskit/deployment.rb, line 368 def host_id process_server_config.host_id end
Whether this task runs within the Syskit process itself
# File lib/syskit/deployment.rb, line 373 def in_process? process_server_config.in_process? end
# File lib/syskit/deployment.rb, line 318 def log_dir process_server_config.log_dir end
Returns true if the syskit plugin configuration requires port
to be logged
@param [Syskit::Port] port @return [Boolean]
# File lib/syskit/deployment.rb, line 404 def log_port?(port) if Syskit.conf.logs.port_excluded_from_log?(port) false else Syskit.info "not logging #{port.component}.#{port.name}" true end end
Returns this deployment's logger
@return [TaskContext,nil] either the logging task, or nil if this
deployment has none
# File lib/syskit/deployment.rb, line 326 def logger_task if arguments[:logger_task] @logger_task = arguments[:logger_task] elsif @logger_task && @logger_task.reusable? @logger_task elsif process_name logger_name = "#{process_name}_Logger" @logger_task = each_executed_task.find { |t| t.orocos_name == logger_name } || begin task(logger_name) # Automatic setup by {NetworkGeneration::LoggerConfigurationSupport} rescue ArgumentError end if @logger_task @logger_task.default_logger = true end @logger_task end end
@api private
Mark tasks affected by a change in configuration section as non-reusable
# File lib/syskit/deployment.rb, line 568 def mark_changed_configuration_as_not_reusable(changed) needed = Set.new remote_task_handles.each do |orocos_name, remote_handle| current_conf = remote_handle.current_configuration next if current_conf.conf.empty? if modified_sections = changed[current_conf.model.concrete_model] if modified_sections.any? { |section_name| current_conf.conf.include?(section_name) } needed << orocos_name remote_handle.needs_reconfiguration = true end end end needed end
@api private
Force a task to be reconfigured during the next network adaptation
# File lib/syskit/deployment.rb, line 550 def needs_reconfiguration!(orocos_name) if handle = remote_task_handles[orocos_name] handle.needs_reconfiguration = true end end
@api private
Whether a task should be forcefully reconfigured during the next network adaptation
# File lib/syskit/deployment.rb, line 541 def needs_reconfiguration?(orocos_name) if handle = remote_task_handles[orocos_name] handle.needs_reconfiguration end end
Whether this deployment runs on the same host than the Syskit process
# File lib/syskit/deployment.rb, line 378 def on_localhost? process_server_config.on_localhost? end
List of task (orocos names) that are marked as needing reconfiguration
# File lib/syskit/deployment.rb, line 558 def pending_reconfigurations remote_task_handles.keys.find_all do |orocos_name| remote_task_handles[orocos_name].needs_reconfiguration end end
The name of the process server
# File lib/syskit/deployment.rb, line 362 def process_server_name arguments[:on] end
# File lib/syskit/deployment.rb, line 632 def ready_to_die! @ready_to_die = true end
@api private
Schedule a promise to resolve the task handles
It will reschedule itself until the process is ready, and will emit the ready event when it happens
# File lib/syskit/deployment.rb, line 437 def schedule_ready_event_monitor(handles_from_plan, ready_polling_period: self.ready_polling_period) distance_to_syskit = self.distance_to_syskit promise = execution_engine.promise(description: "#{self}:ready_event_monitor") do while !quit_ready_event_monitor.set? && !(handles = orocos_process.resolve_all_tasks(handles_from_plan)) sleep ready_polling_period end (handles || Hash.new).map_value do |_, remote_task| state_reader, state_getter = create_state_access(remote_task, distance: distance_to_syskit) properties = remote_task.property_names.map do |p_name| p = remote_task.raw_property(p_name) [p, p.raw_read] end current_configuration = CurrentTaskConfiguration.new(nil, [], Set.new) RemoteTaskHandles.new(remote_task, state_reader, state_getter, properties, false, current_configuration) end end.on_success(description: "#{self}#schedule_ready_event_monitor#emit") do |remote_tasks| if running? && !finishing? && remote_tasks @remote_task_handles = remote_tasks ready_event.emit end end promise.on_error(description: "#{self}#emit_failed") do |reason| if !finishing? || !finished? emit_failed(reason) end end ready_event.achieve_asynchronously(promise, emit_on_success: false, on_failure: :nothing) end
@api private
# File lib/syskit/deployment.rb, line 584 def setup_task_handles(remote_tasks) model.each_orogen_deployed_task_context_model do |act| name = orocos_process.get_mapped_name(act.name) if !remote_tasks.has_key?(name) raise InternalError, "expected #{orocos_process}'s reported tasks to include mapped_task_name, but got handles only for invalid_name" end end remote_tasks.each_value do |task| task.handle.process = nil end each_parent_object(Roby::TaskStructure::ExecutionAgent) do |task| if remote_handles = remote_tasks[task.orocos_name] task.initialize_remote_handles(remote_handles) else task.failed_to_start!( Roby::CommandFailed.new( InternalError.exception("#{task} is supported by #{self} but there does not seem to be any task called #{task.orocos_name} on this deployment"), task.start_event)) end end end
Starts the process and emits the start event immediately. The :ready event will be emitted when the deployment is up and running.
# File lib/syskit/deployment.rb, line 251 event :start do |context| if !process_name raise ArgumentError, "must set process_name" end spawn_options = self.spawn_options options = (spawn_options[:cmdline_args] || Hash.new).dup model.each_default_run_option do |name, value| options[name] = value end spawn_options = spawn_options.merge( output: "%m-%p.txt", wait: false, cmdline_args: options) if log_dir spawn_options = spawn_options.merge(working_directory: log_dir) else spawn_options.delete(:working_directory) end Deployment.info do "starting deployment #{process_name} using #{model.deployment_name} on #{arguments[:on]} with #{spawn_options} and mappings #{name_mappings}" end @orocos_process = process_server_config.client.start( process_name, model.orogen_model, name_mappings, spawn_options) Deployment.all_deployments[orocos_process] = self start_event.emit end
@api private
Declare that the given task is being configured
# File lib/syskit/deployment.rb, line 496 def start_configuration(orocos_name) remote_task_handles[orocos_name].configuring = true end
Stops all tasks that are running on top of this deployment, and kill the deployment
# File lib/syskit/deployment.rb, line 641 event :stop do |context| quit_ready_event_monitor.set promise = execution_engine.promise(description: "#{self}.stop_event.on") do begin remote_task_handles.each_value do |remote_task| remote_task.state_getter.disconnect if remote_task.handle.rtt_state == :STOPPED remote_task.handle.cleanup(false) end end remote_task_handles.each_value do |remote_task| remote_task.state_getter.join end rescue Orocos::ComError # Assume that the process is killed as it is not reachable end end.on_success(description: "#{self}#stop_event#command#dead!") do ready_to_die! begin orocos_process.kill(false) rescue Orocos::ComError # The underlying process server cannot be reached. Just emit # failed ourselves dead!(nil) end end stop_event.achieve_asynchronously(promise, emit_on_success: false) end
@api private
Update the last known configuration of a task
# File lib/syskit/deployment.rb, line 519 def update_current_configuration(orocos_name, model, conf, current_dynamic_services) task_info = remote_task_handles[orocos_name] task_info.needs_reconfiguration = false task_info.current_configuration = CurrentTaskConfiguration.new(model, conf, current_dynamic_services) end