Class: Servolux::Daemon
- Inherits:
-
Object
- Object
- Servolux::Daemon
- Defined in:
- lib/servolux/daemon.rb
Overview
Synopsis
The Daemon takes care of the work of creating and managing daemon processes from Ruby.
Details
A daemon process is a long running process on a UNIX system that is detached from a TTY -- i.e. it is not tied to a user session. These types of processes are notoriously difficult to setup correctly. This Daemon class encapsulates some best practices to ensure daemons startup properly and can be shutdown gracefully.
Starting a daemon process involves forking a child process, setting the child as a session leader, forking again, and detaching from the current working directory and standard in/out/error file descriptors. Because of this separation between the parent process and the daemon process, it is difficult to know if the daemon started properly.
The Daemon class opens a pipe between the parent and the daemon. The PID of the daemon is sent to the parent through this pipe. The PID is used to check if the daemon is alive. Along with the PID, any errors from the daemon process are marshalled through the pipe back to the parent. These errors are wrapped in a StartupError and then raised in the parent.
If no errors are passed up the pipe, the parent process waits till the daemon starts. This is determined by sending a signal to the daemon process.
If a log file is given to the Daemon instance, then it is monitored for a change in size and mtime. This lets the Daemon instance know that the daemon process is updating the log file. Furthermore, the log file can be watched for a specific pattern; this pattern signals that the daemon process is up and running.
Shutting down the daemon process is a little simpler. An external shutdown command can be used, or the Daemon instance will send an INT or TERM signal to the daemon process.
Again, the Daemon instance will wait till the daemon process shuts down. This is determined by attempting to signal the daemon process PID and then returning when this signal fails -- i.e. then the daemon process has died.
Examples
Bad Example
This is a bad example. The daemon will not start because the startup command "/usr/bin/no-command-by-this-name" cannot be found on the file system. The daemon process will send an Errno::ENOENT through the pipe back to the parent which gets wrapped in a StartupError
daemon = Servolux::Daemon.new(
:name => 'Bad Example',
:pid_file => '/dev/null',
:startup_command => '/usr/bin/no-command-by-this-name'
)
daemon.startup #=> raises StartupError
Good Example
This is a simple Ruby server that prints the time to a file every minute. So, it's not really a "good" example, but it will work.
server = Servolux::Server.new('TimeStamp', :interval => 60)
class << server
def file() @fd ||= File.open('timestamps.txt', 'w'); end
def run() file.puts Time.now; end
end
daemon = Servolux::Daemon.new(:server => server, :log_file => 'timestamps.txt')
daemon.startup
Defined Under Namespace
Classes: LogfileReader
Constant Summary collapse
Instance Attribute Summary collapse
-
#after_fork ⇒ Object
Returns the value of attribute after_fork.
-
#before_exec ⇒ Object
Returns the value of attribute before_exec.
-
#log_file ⇒ Object
Returns the value of attribute log_file.
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#look_for ⇒ Object
Returns the value of attribute look_for.
-
#name ⇒ Object
Returns the value of attribute name.
-
#nochdir ⇒ Object
Returns the value of attribute nochdir.
-
#noclose ⇒ Object
Returns the value of attribute noclose.
-
#pid_file ⇒ Object
Returns the value of attribute pid_file.
-
#shutdown_command ⇒ Object
Returns the value of attribute shutdown_command.
-
#startup_command ⇒ Object
(also: #server)
Returns the value of attribute startup_command.
-
#timeout ⇒ Object
Returns the value of attribute timeout.
Instance Method Summary collapse
-
#alive? ⇒ Boolean
Returns
trueif the daemon process is currently running. -
#initialize(opts = {}) {|self| ... } ⇒ Daemon
constructor
Create a new Daemon that will manage the
startup_commandas a daemon process. -
#kill(signal = 'INT') ⇒ Daemon
Send a signal to the daemon process identified by the PID file.
-
#shutdown ⇒ Daemon
Stop the daemon process.
-
#startup(do_exit = true) ⇒ Daemon
Start the daemon process.
Constructor Details
#initialize(opts = {}) {|self| ... } ⇒ Daemon
Create a new Daemon that will manage the startup_command as a daemon
process.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/servolux/daemon.rb', line 153 def initialize( opts = {} ) @piper = nil @logfile_reader = nil @pid_file = nil self.name = opts.fetch(:name, nil) self.logger = opts.fetch(:logger, Servolux::NullLogger()) self.startup_command = opts.fetch(:server, nil) || opts.fetch(:startup_command, nil) self.shutdown_command = opts.fetch(:shutdown_command, nil) self.timeout = opts.fetch(:timeout, 30) self.nochdir = opts.fetch(:nochdir, false) self.noclose = opts.fetch(:noclose, false) self.log_file = opts.fetch(:log_file, nil) self.look_for = opts.fetch(:look_for, nil) self.after_fork = opts.fetch(:after_fork, nil) self.before_exec = opts.fetch(:before_exec, nil) self.pid_file = opts.fetch(:pid_file, name) if pid_file.nil? yield self if block_given? ary = %w[name logger pid_file startup_command].map { |var| self.send(var).nil? ? var : nil }.compact raise Error, "These variables are required: #{ary.join(', ')}." unless ary.empty? end |
Instance Attribute Details
#after_fork ⇒ Object
Returns the value of attribute after_fork.
88 89 90 |
# File 'lib/servolux/daemon.rb', line 88 def after_fork @after_fork end |
#before_exec ⇒ Object
Returns the value of attribute before_exec.
89 90 91 |
# File 'lib/servolux/daemon.rb', line 89 def before_exec @before_exec end |
#log_file ⇒ Object
Returns the value of attribute log_file.
86 87 88 |
# File 'lib/servolux/daemon.rb', line 86 def log_file @log_file end |
#logger ⇒ Object
Returns the value of attribute logger.
79 80 81 |
# File 'lib/servolux/daemon.rb', line 79 def logger @logger end |
#look_for ⇒ Object
Returns the value of attribute look_for.
87 88 89 |
# File 'lib/servolux/daemon.rb', line 87 def look_for @look_for end |
#name ⇒ Object
Returns the value of attribute name.
78 79 80 |
# File 'lib/servolux/daemon.rb', line 78 def name @name end |
#nochdir ⇒ Object
Returns the value of attribute nochdir.
84 85 86 |
# File 'lib/servolux/daemon.rb', line 84 def nochdir @nochdir end |
#noclose ⇒ Object
Returns the value of attribute noclose.
85 86 87 |
# File 'lib/servolux/daemon.rb', line 85 def noclose @noclose end |
#pid_file ⇒ Object
Returns the value of attribute pid_file.
80 81 82 |
# File 'lib/servolux/daemon.rb', line 80 def pid_file @pid_file end |
#shutdown_command ⇒ Object
Returns the value of attribute shutdown_command.
82 83 84 |
# File 'lib/servolux/daemon.rb', line 82 def shutdown_command @shutdown_command end |
#startup_command ⇒ Object Also known as: server
Returns the value of attribute startup_command.
81 82 83 |
# File 'lib/servolux/daemon.rb', line 81 def startup_command @startup_command end |
#timeout ⇒ Object
Returns the value of attribute timeout.
83 84 85 |
# File 'lib/servolux/daemon.rb', line 83 def timeout @timeout end |
Instance Method Details
#alive? ⇒ Boolean
Returns true if the daemon process is currently running. Returns
false if this is not the case. The status of the process is determined
by sending a signal to the process identified by the pid_file.
314 315 316 |
# File 'lib/servolux/daemon.rb', line 314 def alive? pid_file.alive? end |
#kill(signal = 'INT') ⇒ Daemon
Send a signal to the daemon process identified by the PID file. The default signal to send is 'INT' (2). The signal can be given either as a string or a signal number.
326 327 328 |
# File 'lib/servolux/daemon.rb', line 326 def kill( signal = 'INT' ) pid_file.kill signal end |
#shutdown ⇒ Daemon
Stop the daemon process. If a shutdown command has been defined, it will be called to stop the daemon process. Otherwise, SIGINT will be sent to the daemon process to terminate it.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/servolux/daemon.rb', line 291 def shutdown return unless alive? case shutdown_command when nil; kill when Integer; kill(shutdown_command) when String; exec(shutdown_command) when Array; exec(*shutdown_command) when Proc, Method; shutdown_command.call when ::Servolux::Server; shutdown_command.shutdown else raise Error, "Unrecognized shutdown command #{shutdown_command.inspect}" end wait_for_shutdown end |
#startup(do_exit = true) ⇒ Daemon
Start the daemon process. Passing in false to this method will prevent
the parent from exiting after the daemon process starts.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/servolux/daemon.rb', line 264 def startup( do_exit = true ) raise Error, "Fork is not supported in this Ruby environment." unless ::Servolux.fork? return if alive? logger.debug "About to fork ..." @piper = ::Servolux::Piper.daemon(nochdir, noclose) # Make sure we have an idea of the state of the log file BEFORE the child # gets a chance to write to it. @logfile_reader.updated? if @logfile_reader @piper.parent { @piper.timeout = 0.1 wait_for_startup exit!(0) if do_exit } @piper.child { run_startup_command } self end |