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 deamon 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
-
#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
readonly
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
true
if the daemon process is currently running. -
#initialize(opts = {}) {|self| ... } ⇒ Daemon
constructor
Create a new Daemon that will manage the
startup_command
as a deamon 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 deamon process.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/servolux/daemon.rb', line 143 def initialize( opts = {} ) self.server = opts[:server] || opts[:startup_command] @name = opts[:name] if opts.key?(:name) @logger = opts[:logger] if opts.key?(:logger) @pid_file = opts[:pid_file] if opts.key?(:pid_file) @timeout = opts[:timeout] || 30 @nochdir = opts[:nochdir] || false @noclose = opts[:noclose] || false @shutdown_command = opts[:shutdown_command] @piper = nil @logfile_reader = nil self.log_file = opts[:log_file] self.look_for = opts[:look_for] 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
#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 (readonly)
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
.
280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/servolux/daemon.rb', line 280 def alive? pid = retrieve_pid Process.kill(0, pid) true rescue Errno::ESRCH, Errno::ENOENT false rescue Errno::EACCES => err logger.error "You do not have access to the PID file at " \ "#{pid_file.inspect}: #{err.}" false 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.
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/servolux/daemon.rb', line 300 def kill( signal = 'INT' ) signal = Signal.list.invert[signal] if signal.is_a?(Integer) pid = retrieve_pid logger.info "Killing PID #{pid} with #{signal}" Process.kill(signal, pid) self rescue Errno::EINVAL logger.error "Failed to kill PID #{pid} with #{signal}: " \ "'#{signal}' is an invalid or unsupported signal number." rescue Errno::EPERM logger.error "Failed to kill PID #{pid} with #{signal}: " \ "Insufficient permissions." rescue Errno::ESRCH logger.error "Failed to kill PID #{pid} with #{signal}: " \ "Process is deceased or zombie." rescue Errno::EACCES => err logger.error err. rescue Errno::ENOENT => err logger.error "Could not find a PID file at #{pid_file.inspect}. " \ "Most likely the process is no longer running." rescue Exception => err unless err.is_a?(SystemExit) logger.error "Failed to kill PID #{pid} with #{signal}: #{err.}" end 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.
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/servolux/daemon.rb', line 257 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.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/servolux/daemon.rb', line 230 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 |