The Server class makes it simple to create a server-type application in Ruby. A server in this context is any process that should run for a long period of time either in the foreground or as a daemon.
The Server class provides for standard server
features: process ID file management, signal handling, run loop, logging,
etc. All that you need to provide is a run method that will be
called by the server’s run loop. Optionally, you can provide a block to
the new method and it will be called within the run loop
instead of a run method.
SIGINT and SIGTERM are handled by default. These signals will gracefully
shutdown the server by calling the shutdown method (provided
by default, too). A few other signals can be handled by defining a few
methods on your server instance. For example, SIGINT is handled by the
int method (an alias for shutdown). Likewise,
SIGTERM is handled by the term method (another alias for
shutdown). The following signal methods are recognized by the
Server class:
Method | Signal | Default Action --------+----------+---------------- hup SIGHUP none int SIGINT shutdown term SIGTERM shutdown usr1 SIGUSR1 none usr2 SIGUSR2 none
In order to handle SIGUSR1 you would define a usr1 method for
your server.
There are a few other methods that are useful and should be mentioned. Two
methods are called before and after the run loop starts:
before_starting and after_starting. The first is
called just before the run loop thread is created and started. The second
is called just after the run loop thread has been created (no guarantee is
made that the run loop thread has actually been scheduled).
Likewise, two other methods are called before and after the run loop is
shutdown: before_stopping and after_stopping. The
first is called just before the run loop thread is signaled for shutdown.
The second is called just after the run loop thread has died; the
after_stopping method is guaranteed to NOT be called till
after the run loop thread is well and truly dead.
For simple, quick and dirty servers just pass a block to the Server initializer. This block will be used as the run method.
server = Servolux::Server.new('Basic', :interval => 1) { puts "I'm alive and well @ #{Time.now}" } server.startup
For more complex services you will need to define your own server methods:
the run method, signal handlers, and before/after methods. Any
pattern that Ruby provides for defining methods on objects can be used to
define these methods. In a nutshell:
Inheritance
class MyServer < Servolux::Server def run puts "I'm alive and well @ #{Time.now}" end end server = MyServer.new('MyServer', :interval => 1) server.startup
Extension
module MyServer def run puts "I'm alive and well @ #{Time.now}" end end server = Servolux::Server.new('Module', :interval => 1) server.extend MyServer server.startup
Singleton Class
server = Servolux::Server.new('Singleton', :interval => 1) class << server def run puts "I'm alive and well @ #{Time.now}" end end server.startup
This example shows how to change the log level of the server when SIGUSR1 is sent to the process. The log level toggles between “debug” and the original log level each time SIGUSR1 is sent to the server process. Since this is a module, it can be used with any Servolux::Server instance.
module DebugSignal def usr1 @old_log_level ||= nil if @old_log_level logger.level = @old_log_level @old_log_level = nil else @old_log_level = logger.level logger.level = :debug end end end server = Servolux::Server.new('Debugger', :interval => 2) { logger.info "Running @ #{Time.now}" logger.debug "hey look - a debug message" } server.extend DebugSignal server.startup
Creates a new server identified by name and configured from the options hash. The block is run inside a separate thread that will loop at the configured interval.
The logger instance this server will use
Location of the PID file
Sleep interval between invocations of the block
# File lib/servolux/server.rb, line 148 def initialize( name, opts = {}, &block ) @name = name @mutex = Mutex.new @shutdown = nil self.logger = opts.fetch(:logger, Servolux::NullLogger()) self.interval = opts.fetch(:interval, 0) self.pid_file = opts.fetch(:pid_file, name) if block eg = class << self; self; end eg.__send__(:define_method, :run, &block) end ary = %w[name logger pid_file].map { |var| self.send(var).nil? ? var : nil }.compact raise Error, "These variables are required: #{ary.join(', ')}." unless ary.empty? end
Set the PID file to the given `value`. If a PidFile instance is given, then it is used. If a name is given, then that name is used to create a PifFile instance.
value - The PID file name or a PidFile instance.
Raises an ArgumentError if the `value` cannot be used as a PID file.
# File lib/servolux/server.rb, line 247 def pid_file=( value ) @pid_file = case value when Servolux::PidFile value when String path = File.dirname(value) fn = File.basename(value, ".pid") Servolux::PidFile.new(:name => fn, :path => path, :logger => logger) else raise ArgumentError, "#{value.inspect} cannot be used as a PID file" end end
Stop the server if it is running. This method will return after three things have occurred:
1) The ‘before_stopping’ method has returned. 2) The server’s activity thread has stopped. 3) The ‘after_stopping’ method has returned.
It is entirely possible that the activity thread will stop before either
the before_stopping or after_stopping methods
return. To make sure the server is completely stopped, use the
wait_for_shutdown method to be notified when the this
shutdown method is finished executing.
@return [Server] self
# File lib/servolux/server.rb, line 215 def shutdown return self unless running? stop @mutex.synchronize { @shutdown.signal @shutdown = nil } self end
Start the server running using it’s own internal thread. This method will not return until the server is shutdown.
Startup involves creating a PID file, registering signal handlers to shutdown the server, starting and joining the server thread. The PID file is deleted when this method returns.
If true is passed to this method, then it will not return
until the wait_for_shutdown method has been called from
another thread. This flag is used to ensure that the server has shutdown
completely when shutdown by a signal.
@return [Server] self
# File lib/servolux/server.rb, line 182 def startup( wait = false ) return self if running? @mutex.synchronize { @shutdown = ConditionVariable.new } begin trap_signals pid_file.write start join wait_for_shutdown if wait ensure pid_file.delete halt_signal_processing end return self end
If the server has been started, this method waits till the
shutdown method has been called and has completed. The current
thread will be blocked until the server has been safely stopped.
# File lib/servolux/server.rb, line 229 def wait_for_shutdown @mutex.synchronize { @shutdown.wait(@mutex) unless @shutdown.nil? } self end