class Orocos::RemoteProcesses::Server

A remote process management server.

It allows to start/stop and monitor the status of processes on a client/server way.

Use {ProcessClient} to access a server

Public Class Methods

create_pkgconfig_loader() click to toggle source
# File lib/orocos/remote_processes/server.rb, line 97
def self.create_pkgconfig_loader
    OroGen::Loaders::RTT.new(Orocos.orocos_target)
end
new(default_start_options = DEFAULT_OPTIONS, port = DEFAULT_PORT, loader = self.class.create_pkgconfig_loader) click to toggle source
# File lib/orocos/remote_processes/server.rb, line 101
def initialize(default_start_options = DEFAULT_OPTIONS,
               port = DEFAULT_PORT,
               loader = self.class.create_pkgconfig_loader)

    @default_start_options = Kernel.validate_options default_start_options,
        :wait => false,
        :output => '%m-%p.txt'

    @loader = loader
    @required_port = port
    @port = nil
    @processes = Hash.new
    @all_ios = Array.new
end
run(options = DEFAULT_OPTIONS, port = DEFAULT_PORT) click to toggle source

Start a standalone process server using the given options and port. The options are passed to ::run when a new deployment is started

# File lib/orocos/remote_processes/server.rb, line 60
def self.run(options = DEFAULT_OPTIONS, port = DEFAULT_PORT)
    Orocos.disable_sigchld_handler = true
    Orocos.initialize
    new({ :wait => false }.merge(options), port).exec

rescue Interrupt
end
unique_dirname(base_dir, path_spec, date_tag = nil) click to toggle source

Returns a unique directory name as a subdirectory of base_dir, based on path_spec. The generated name is of the form

<base_dir>/a/b/c/YYYYMMDD-HHMM-basename

if path_spec = "a/b/c/basename". A .<number> suffix is appended if the path already exists.

Shamelessly taken from Roby

# File lib/orocos/remote_processes/server.rb, line 22
def self.unique_dirname(base_dir, path_spec, date_tag = nil)
    if path_spec =~ /\/$/
        basename = ""
        dirname = path_spec
    else
        basename = File.basename(path_spec)
        dirname  = File.dirname(path_spec)
    end

    date_tag ||= Time.now.strftime('%Y%m%d-%H%M')
    if basename && !basename.empty?
        basename = date_tag + "-" + basename
    else
        basename = date_tag
    end

    # Check if +basename+ already exists, and if it is the case add a
    # .x suffix to it
    full_path = File.expand_path(File.join(dirname, basename), base_dir)
    base_dir  = File.dirname(full_path)

    unless File.exists?(base_dir)
        FileUtils.mkdir_p(base_dir)
    end

    final_path, i = full_path, 0
    while File.exists?(final_path)
        i += 1
        final_path = full_path + ".#{i}"
    end

    final_path
end

Public Instance Methods

build_system_info() click to toggle source
# File lib/orocos/remote_processes/server.rb, line 358
def build_system_info
    available_projects = Hash.new
    available_typekits = Hash.new
    available_deployments = Hash.new
    loader.each_available_project_name do |name|
        available_projects[name] = loader.project_model_text_from_name(name)
    end
    loader.each_available_typekit_name do |name|
        available_typekits[name] = loader.typekit_model_text_from_name(name)
    end
    loader.each_available_deployment_name do |name|
        available_deployments[name] = loader.find_project_from_deployment_name(name)
    end
    return available_projects, available_deployments, available_typekits
end
create_log_dir(log_dir, time_tag, metadata = Hash.new) click to toggle source
# File lib/orocos/remote_processes/server.rb, line 337
def create_log_dir(log_dir, time_tag, metadata = Hash.new)
    log_dir     = File.expand_path(log_dir)
    Server.debug "  #{log_dir}, time: #{time_tag}"
    FileUtils.mkdir_p(log_dir)
    File.open(File.join(log_dir, 'time_tag'), 'w') do |io|
        io.write(time_tag)
    end
    File.open(File.join(log_dir, 'info.yml'), 'w') do |io|
        YAML.dump(Hash['time' => time_tag].merge(metadata), io)
    end
end
each_client(&block) click to toggle source
# File lib/orocos/remote_processes/server.rb, line 116
def each_client(&block)
    clients = @all_ios[2..-1]
    if clients
        clients.each(&block)
    end
end
end_process(p) click to toggle source
# File lib/orocos/remote_processes/server.rb, line 382
def end_process(p)
    p.kill(false)
end
exec() click to toggle source
# File lib/orocos/remote_processes/server.rb, line 123
def exec
    open
    listen
end
listen() click to toggle source

Main server loop. This will block and only return when CTRL+C is hit.

All started processes are stopped when the server quits

# File lib/orocos/remote_processes/server.rb, line 148
def listen
    Server.info "process server listening on port #{port}"
    server_io, com_r = *@all_ios[0, 2]

    while true
        readable_sockets, _ = select(@all_ios, nil, nil)
        if readable_sockets.include?(server_io)
            readable_sockets.delete(server_io)
            client_socket = server_io.accept
            client_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
            client_socket.fcntl(Fcntl::FD_CLOEXEC, 1)
            Server.debug "new connection: #{client_socket}"
            @all_ios << client_socket
        end

        if readable_sockets.include?(com_r)
            readable_sockets.delete(com_r)
            cmd = com_r.read(1)
            if cmd == INTERNAL_SIGCHLD_TRIGGERED
                process_dead_processes
            elsif cmd
                Server.warn "unknown internal communication code #{cmd.inspect}"
            end
        end

        readable_sockets.each do |socket|
            if !handle_command(socket)
                Server.debug "#{socket} closed or errored"
                socket.close
                @all_ios.delete(socket)
            end
        end
    end

rescue Exception => e
    if e.class == Interrupt # normal procedure
        Server.fatal "process server exited normally"
        return
    end

    Server.fatal "process server exited because of unhandled exception"
    Server.fatal "#{e.message} #{e.class}"
    e.backtrace.each do |line|
        Server.fatal "  #{line}"
    end

ensure
    quit_and_join
end
move_log_dir(log_dir, results_dir) click to toggle source
# File lib/orocos/remote_processes/server.rb, line 349
def move_log_dir(log_dir, results_dir)
    date_tag    = File.read(File.join(log_dir, 'time_tag')).strip
    Server.debug "  #{log_dir} => #{results_dir}"
    if File.directory?(log_dir)
        dirname = Server.unique_dirname(results_dir + '/', '', date_tag)
        FileUtils.mv log_dir, dirname
    end
end
open() click to toggle source
# File lib/orocos/remote_processes/server.rb, line 130
def open
    Server.info "starting on port #{required_port}"
    server = TCPServer.new(nil, required_port)
    server.fcntl(Fcntl::FD_CLOEXEC, 1)
    @port = server.addr[1]

    com_r, com_w = IO.pipe
    @all_ios.clear
    @all_ios << server << com_r

    trap 'SIGCHLD' do
        com_w.write INTERNAL_SIGCHLD_TRIGGERED
    end
end
process_dead_processes() click to toggle source
# File lib/orocos/remote_processes/server.rb, line 198
def process_dead_processes
    while exited = ::Process.wait2(-1, ::Process::WNOHANG)
        pid, exit_status = *exited
        process_name, process = processes.find { |_, p| p.pid == pid }
        next if !process_name

        process.dead!(exit_status)
        processes.delete(process_name)
        Server.debug "announcing death: #{process_name}"
        each_client do |socket|
            begin
                Server.debug "  announcing to #{socket}"
                socket.write(EVENT_DEAD_PROCESS)
                Marshal.dump([process_name, exit_status], socket)
            rescue IOError
                Server.debug "  #{socket}: IOError"
            end
        end
    end
rescue Errno::ECHILD
end
quit() click to toggle source
# File lib/orocos/remote_processes/server.rb, line 386
def quit
    raise Interrupt
end
start_process(name, deployment_name, name_mappings, options) click to toggle source
# File lib/orocos/remote_processes/server.rb, line 374
def start_process(name, deployment_name, name_mappings, options)
    p = Orocos::Process.new(name, deployment_name,
        loader: @loader,
        name_mappings: name_mappings)
    p.spawn(**self.default_start_options.merge(options))
    processes[name] = p
end