class Syskit::Deployment
In oroGen, a deployment is a Unix process that holds a certain number of task contexts. This Roby task represents the unix process itself. Once it gets instanciated, the associated task contexts can be accessed with task(name)
Constants
- CurrentTaskConfiguration
@api private
The last applied task configuration
- 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
- STATE_READER_BUFFER_SIZE
The size of the buffered connection created between this object and the remote task's state port
Attributes
The underlying process object
@api private
Event used to quit the ready monitor started by {#schedule_ready_event_monitor}
@return [Concurrent::Event]
Handles to all remote tasks from this deployment
@return [Hash<String,RemoteTaskHandles>]
Public Class Methods
# File lib/syskit/deployment.rb, line 62 def all_deployments; @@all_deployments 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
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
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
# File lib/syskit/deployment.rb, line 44 def initialize(options = Hash.new) super @quit_ready_event_monitor = Concurrent::Event.new @remote_task_handles = Hash.new if !self.spawn_options self.spawn_options = Hash.new end if !self.name_mappings self.name_mappings = Hash.new end model.each_default_name_mapping do |k, v| self.name_mappings[k] ||= v end 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
Create and add a task model supported by this deployment
@param [OroGen::Spec::TaskDeployment] orogen_task_deployment_model
the orogen model that describes this deployment
@param [Models::TaskContext,nil] syskit_task_model the expected syskit task model, or nil
if it is meant to use the basic model. This is useful in specialized models (e.g. dynamic services)
@param [Deployment,TaskContext] syskit_execution_agent the task that will be used as an execution agent.
this is usually self, but may be a task in master/slave relationships.
@param [Boolean] auto_conf if true, the method will attempt to select
a configuration that matches the task's orocos name (if it exists). This is mostly used for scheduling tasks, which are automatically instanciated by Syskit.
@see #find_or_create_task task
# File lib/syskit/deployment.rb, line 161 def create_deployed_task(orogen_task_deployment_model, syskit_task_model, scheduler_task, auto_conf: false) mapped_name = name_mappings[orogen_task_deployment_model.name] base_syskit_task_model = TaskContext. model_for(orogen_task_deployment_model.task_model) if syskit_task_model if !(syskit_task_model <= base_syskit_task_model) raise ArgumentError, "incompatible explicit selection of task "\ "model #{syskit_task_model} for the model of #{mapped_name} "\ " in #{self}" end else syskit_task_model = base_syskit_task_model end plan.add(task = syskit_task_model.new(orocos_name: mapped_name)) task.executed_by self if scheduler_task task.depends_on scheduler_task, role: 'scheduler' task.should_configure_after scheduler_task.start_event end task.orogen_model = orogen_task_deployment_model if ready? if remote_task = remote_task_handles[mapped_name] task.initialize_remote_handles(remote_task) else raise InternalError, "no remote handle describing #{mapped_name} in #{self} for #{task} (got #{remote_task_handles.keys.sort.join(", ")})" end end auto_select_conf(task) if auto_conf task 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
The list of deployed task contexts for this particular deployment
It takes into account deployment prefix
# File lib/syskit/deployment.rb, line 104 def each_orogen_deployed_task_context_model(&block) model.each_orogen_deployed_task_context_model(&block) end
Either find the existing task that matches the given deployment specification, or creates and adds it.
@param (see task)
# File lib/syskit/deployment.rb, line 112 def find_or_create_task(name, syskit_task_model = nil, auto_conf: false) orogen_task_deployment_model = each_orogen_deployed_task_context_model. find { |act| name == name_mappings[act.name] } if orogen_master = orogen_task_deployment_model.master mapped_master = name_mappings[orogen_master.name] scheduler_task = find_or_create_task( mapped_master, auto_conf: true) candidates = scheduler_task.each_parent_task else candidates = each_executed_task end # I don't know why name_mappings[orogen.name] would not be # equal to 'name' and I couldn't find a reason for this in the # git history when I refactored this. # # I keep it here for now, just in case, but that would need to # be investigated # # TODO mapped_name = name_mappings[orogen_task_deployment_model.name] candidates.each do |task| return task if task.orocos_name == mapped_name end create_deployed_task( orogen_task_deployment_model, syskit_task_model, scheduler_task, auto_conf: auto_conf) 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
# File lib/syskit/deployment.rb, line 91 def has_orocos_name?(orocos_name) name_mappings.each_value.any? { |n| n == orocos_name } 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 95 def instanciate_all_tasks model.each_orogen_deployed_task_context_model.map do |act| task(name_mappings[act.name]) end 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 PID of this process
# File lib/syskit/deployment.rb, line 66 def pid if running? @pid ||= orocos_process.pid end end
An object describing the underlying pocess server
@return [RobyApp::Configuration::ProcessServerConfig]
# File lib/syskit/deployment.rb, line 40 def process_server_config @process_server_config ||= Syskit.conf.process_server_config_for(process_server_name) 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
Returns an task instance that represents the given task in this deployment.
@param [String] name the unmapped name of the task @param [Models::TaskContext,nil] syskit_task_model the Syskit
model that should be used to create the task, if it is not the same as the base model. This is used for specialized models (e.g. dynamic services)
# File lib/syskit/deployment.rb, line 204 def task(name, syskit_task_model = nil) if finishing? || finished? raise InvalidState, "#{self} is either finishing or already "\ "finished, you cannot call #task" end orogen_task_deployment_model = each_orogen_deployed_task_context_model. find { |act| name == name_mappings[act.name] } if !orogen_task_deployment_model available = each_orogen_deployed_task_context_model. map { |act| name_mappings[act.name] }.sort.join(", ") mappings = name_mappings.map { |k,v| "#{k} => #{v}" }.join(", ") raise ArgumentError, "no task called #{name} in "\ "#{self.class.deployment_name}, available tasks are #{available}"\ " using name mappings #{mappings}" end if orogen_master = orogen_task_deployment_model.master scheduler_task = find_or_create_task( orogen_master.name, auto_conf: true) end create_deployed_task(orogen_task_deployment_model, syskit_task_model, scheduler_task) 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