Skip to content

Instantly share code, notes, and snippets.

@ridiculous
Created September 12, 2017 17:46
Show Gist options
  • Save ridiculous/6580461ccbdfe58404fbb2175eeef50b to your computer and use it in GitHub Desktop.
Save ridiculous/6580461ccbdfe58404fbb2175eeef50b to your computer and use it in GitHub Desktop.
require 'fileutils'
class ProcessRunner
PIDFILE_MODE = (::File::CREAT | ::File::EXCL | ::File::WRONLY)
LOG_DIR_MODE = 0o0755
LOG_FILE_MODE = 0o0644
attr_reader :name, :pidfile, :logfile
def initialize(opts = {})
@stop = false
@trap_term = false
@name = opts[:name]
@daemonize = !!opts[:daemonize]
# Daemonizing could change CWD, expand now or die!
@logfile = File.expand_path(opts[:logfile]) if opts[:logfile]
@pidfile = File.expand_path(opts[:pidfile]) if opts[:pidfile]
end
def call
fail 'You must supply a block to #run!' unless block_given?
check_pid
daemonize if daemonize?
write_pid
trap_signals
if logfile?
redirect_output
elsif daemonize?
suppress_output
end
while running?
yield
end
info 'Shutting down'
exit # Exit to run `at_exit` hooks
end
def daemonize?
@daemonize
end
def pid
@pid ||= Process.pid
end
def pidfile?
!@pidfile.nil?
end
def logfile?
!@logfile.nil?
end
def trap_term!
@trap_term = true
end
def trap_term?
@trap_term
end
def stop!
@stop = true
end
def stopped?
@stop
end
def running?
!stopped?
end
def info(msg)
puts "[#{name || pid}] #{Time.now} - #{msg}"
end
private
def daemonize
exit if fork
Process.setsid
exit if fork
Dir.chdir "/"
end
# Checks if there is a current PID for the given `#pidfile` and exits
# if there is one and a running process is already associated with this
# file
def check_pid
return unless pidfile?
case pid_status
when :running, :not_owned
puts "A server is already running. Check #{pidfile}"
exit(1)
when :dead
File.delete(pidfile)
end
end
def pid_status
return :exited unless File.exists?(pidfile)
current_pid = ::File.read(pidfile).to_i
return :dead if current_pid == 0
Process.kill(0, current_pid)
:running
rescue Errno::ESRCH
:dead
rescue Errno::EPERM
:not_owned
end
def write_pid
return unless pidfile?
begin
File.open(pidfile, PIDFILE_MODE) { |f| f.write(pid) }
at_exit { File.delete(pidfile) if File.exists?(pidfile) }
rescue Errno::EEXIST
check_pid
retry
end
end
def trap_signals
# Graceful shutdown
trap(:QUIT) { stop! }
trap(:INT) { stop! }
trap(:TERM) { stop! } if trap_term? # Optional
end
def redirect_output
unless File.writable?(logfile)
FileUtils.mkdir_p(File.dirname(logfile), mode: LOG_DIR_MODE)
FileUtils.touch(logfile)
File.chmod(LOG_FILE_MODE, logfile)
end
$stderr.reopen(logfile, 'a')
$stdout.reopen($stderr)
$stdout.sync = $stderr.sync = true
end
def suppress_output
$stderr.reopen('/dev/null', 'a')
$stdout.reopen($stderr)
end
end
@ridiculous
Copy link
Author

Usage:

runner = ProcessRunner.new
runner.trap_term!
runner.call do
  # do work
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment