How to write a Ruby service(?)?


#1

I need to write what might be called a Ruby service or server(?)–a Ruby
program that:

  • runs only one instance even if “invoked” multiple times
  • can be sent additional “commands” via CLI commands, e.g., if the
    name of
    the program is, for example, “program” I can issue multiple CLI commands
    like:

program

If program is not running, it will start and perform appropriate actions
based
on the parameters.

If program is running, the existing instance of the program will receive
those
parameters and perform appropriate actions.

At the moment (and maybe permanently :wink: I’m drawing a blank as to what
a
program like that is called (service, server, …?), and where to dig to
learn how to implement such a thing (preferably in Ruby).

Hints?

Initially I plan to implement this under Linux–some day it may get
ported to
Windows, etc.

Thanks!
Randy K.

Incidentally, if the program crashes, the next invocation of the program
will
restart it and recover as much of the previous “context” as possible
(which
is, of course, only what has been recorded to disk).


#2

On Saturday 18 March 2006 08:19 am, Randy K. wrote:

I need to write what might be called a Ruby service or server(?)–a Ruby
program that:

  • runs only one instance even if “invoked” multiple times
  • can be sent additional “commands” via CLI commands, e.g., if the name
    of the program is, for example, “program” I can issue multiple CLI commands
    like:

Oops, wait–I’m starting to wake up now–that would be a daemon,
wouldn’t it?
Off to Google [Ruby daemon].

But, any hints appreciated!

Randy K.


#3

On 3/18/06, Randy K. removed_email_address@domain.invalid wrote:

But, any hints appreciated!

Randy K.

Well fwiw, usually you would write two apps - a client and a server.
The server will be invoked once and daemonized, and you use the client
to issue commands. You can connect to the client either via TCP or
unix domain sockets.

Pat


#4

Pat M. wrote:

But, any hints appreciated!

Randy K.

Well fwiw, usually you would write two apps - a client and a server.
The server will be invoked once and daemonized, and you use the client
to issue commands. You can connect to the client either via TCP or
unix domain sockets.

That approach has been working really nicely for me. Some suggestions:

  • look on raa for daemon or daemonize, it’s got the 5 or 6 basic steps
    you need to do to make a process a daemon on unix/linux (maybe use
    Daniel B.'s win32service on windows–I haven’t tried that).

  • when the server starts, it reads its URI from a config file, and sets
    up drb (or other protocol) at that URI. If the service is already
    running there, bail out (so the server is unique). (I like drbunix for
    this, since you can chmod the socket.)

  • the client has a command line option to read from a server config
    file, so you just have to know the filename, not URI (assuming you are
    on the same host).

  • the client uses the return code to indicate server up/down, to make
    shell scripting easy (“client --remote server.config || server --start
    –file server.config”)

  • the client can have an additional command line option (or set of them)
    to send drb requests to the server

I can send you a link to the code that I use for this, but it’s mixed in
with a lot of other stuff… (the command line doc is at
http://path.berkeley.edu/vii/viicatl/doc/ruby-api/files/doc/cli_txt.html).


#5

On Saturday 18 March 2006 03:21 pm, Guillaume M. wrote:

I have some code that do kind of what you are describing. But I have to
admit that lately, when I run a daemon/service on my Linux box, I am
incline to manage it with something like runit
(http://smarden.sunsite.dk/runit/). I find it easier than to manage
than the SysV services.

Thanks to all who replied!

I’m enough of a newbie that it will take me a little while to sort
through
these responses–when I’m ready, I’ll ask the next question (because I’m
sure
there will be one :wink:

Randy K.

Aside: Someone else suggested using a fifo (starting with mkfifo)–I’ll
need
to do some experimenting with that as well. I suspect it won’t give all
the
functionality I thought I wanted, but it may be a simple approach to get
started.


#6

Le 18 mars 06, à 08:19, Randy K. a écrit :

program
what a
program like that is called (service, server, …?), and where to dig
to
learn how to implement such a thing (preferably in Ruby).

Hints?

Initially I plan to implement this under Linux–some day it may get
ported to
Windows, etc.

I have some code that do kind of what you are describing. But I have to
admit that lately, when I run a daemon/service on my Linux box, I am
incline to manage it with something like runit
(http://smarden.sunsite.dk/runit/). I find it easier than to manage
than the SysV services.

On to some code. The server says:

module TelnetShellServer
def self.daemonize
return true if fork # Parent exits, child continues.
Process.setsid # Become session leader.
exit if fork # Zap session leader. See [1].
Dir.chdir “/” # Release old working directory.
File.umask 0000 # Ensure sensible umask. Adjust as
needed.
STDIN.reopen("/dev/null", “r”) # Free file descriptors and
stdout_r, stdout_w = IO.pipe
stderr_r, stderr_w = IO.pipe
Thread.new { loop { $logger.info(stdout_r.gets.chomp) } }
Thread.new { loop { $logger.error(stderr_r.gets.chomp) } }
STDOUT.reopen(stdout_w)
STDERR.reopen(stderr_w)
nil
end

class Broker
[… actual useful service code …]
end
end

[ … read options and configuration file …]

unless foreground
TelnetShellServer.daemonize and exit 0
end

$SAFE = 1 # disable eval() and friends
BROKER = Broker.new(login_files, auto_kill)
$logger.info(“Starting DRb”)
DRb.start_service(uri.to_s, BROKER)
$logger.info(“Broker listening at #{uri}”)
trap(“TERM”) { $logger.debug(“TERM”); BROKER.stop }
trap(“QUIT”) { $logger.debug(“QUIT”); BROKER.stop }
trap(“INT”) { $logger.debug(“INT”); BROKER.stop }
trap(“HUP”) { $logger.debug(“HUP”); BROKER.reread_logins }

DRb.thread.join

The client says:

[ … read options and configuration file …]
uri = URI.parse(uri)
DRb.start_service
broker = DRbObject.new_with_uri(uri.to_s)
tries = 2
host, cmd = ARGV.shift, ARGV.join(’ ')
begin
res = broker.connection_do(host, cmd)
puts(res) if res
rescue DRb::DRbConnError
if auto_vivify && ((tries -= 1) > 0)
system(“telnet_shell_serverd”) # server not present, start it!
sleep(2)
retry
end
raise
end

HTH,
Guillaume.