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

current_color[R]

Used by the to_dot* methods for color allocation

quit_ready_event_monitor[R]

@api private

Event used to quit the ready monitor started by {#schedule_ready_event_monitor}

@return [Concurrent::Event]

Public Class Methods

allocate_color() click to toggle source

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
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) click to toggle source

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
conf() click to toggle source

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
connect(source, sink, policy) click to toggle source

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
create_proxy_task_model_for(models, options = Hash.new) click to toggle source

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
deployment_by_process(process) click to toggle source

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_model(model, &block) click to toggle source

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
needs_reconfiguration!(plan, orocos_name) click to toggle source

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
proxy_task_model_for(models) click to toggle source

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
register_process_server(name, client, log_dir = nil) click to toggle source

(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
resolve_connections(output_ports, input_ports) click to toggle source

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
resolve_proxy_task_model_requirements(models) click to toggle source

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
update_connection_policy(old, new) click to toggle source
# 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
warn_about_new_naming_convention() click to toggle source
# 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

configuration_changed?(orocos_name, conf, dynamic_services) click to toggle source

@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
configuring?(orocos_name) click to toggle source

@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
create_state_access(remote_task, distance: TaskContext::D_UNKNOWN) click to toggle source

@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
dead!(result) click to toggle source

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
distance_to(other_deployment) click to toggle source

“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
distance_to_syskit() click to toggle source

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
finished_configuration(orocos_name) click to toggle source

@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
host_id() click to toggle source

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
in_process?() click to toggle source

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
log_dir() click to toggle source
# File lib/syskit/deployment.rb, line 318
def log_dir
    process_server_config.log_dir
end
log_port?(port) click to toggle source

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
logger_task() click to toggle source

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
mark_changed_configuration_as_not_reusable(changed) click to toggle source

@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
needs_reconfiguration!(orocos_name) click to toggle source

@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
needs_reconfiguration?(orocos_name) click to toggle source

@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
on_localhost?() click to toggle source

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
pending_reconfigurations() click to toggle source

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
process_server_name() click to toggle source

The name of the process server

# File lib/syskit/deployment.rb, line 362
def process_server_name
    arguments[:on]
end
ready_to_die!() click to toggle source
# File lib/syskit/deployment.rb, line 632
def ready_to_die!
   @ready_to_die = true
end
schedule_ready_event_monitor(handles_from_plan, ready_polling_period: self.ready_polling_period) click to toggle source

@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
setup_task_handles(remote_tasks) click to toggle source

@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
start!() click to toggle source

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
start_configuration(orocos_name) click to toggle source

@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
stop!() click to toggle source

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
update_current_configuration(orocos_name, model, conf, current_dynamic_services) click to toggle source

@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