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

orocos_process[R]

The underlying process object

quit_ready_event_monitor[R]

@api private

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

@return [Concurrent::Event]

remote_task_handles[R]

Handles to all remote tasks from this deployment

@return [Hash<String,RemoteTaskHandles>]

Public Class Methods

all_deployments() click to toggle source
# File lib/syskit/deployment.rb, line 62
def all_deployments; @@all_deployments 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
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
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
new(options = Hash.new) click to toggle source
Calls superclass method
# 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

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_deployed_task(orogen_task_deployment_model, syskit_task_model, scheduler_task, auto_conf: false) click to toggle source

@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
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
each_orogen_deployed_task_context_model(&block) click to toggle source

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
find_or_create_task(name, syskit_task_model = nil, auto_conf: false) click to toggle source

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
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
has_orocos_name?(orocos_name) click to toggle source
# File lib/syskit/deployment.rb, line 91
def has_orocos_name?(orocos_name)
    name_mappings.each_value.any? { |n| n == orocos_name }
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
instanciate_all_tasks() click to toggle source
# 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
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
pid() click to toggle source

The PID of this process

# File lib/syskit/deployment.rb, line 66
def pid
    if running?
        @pid ||= orocos_process.pid
    end
end
process_server_config() click to toggle source

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
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
task(name, syskit_task_model = nil) click to toggle source

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
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