Skip to content

Instantly share code, notes, and snippets.

@vjt
Created March 10, 2011 13:14
Show Gist options
  • Save vjt/864073 to your computer and use it in GitHub Desktop.
Save vjt/864073 to your computer and use it in GitHub Desktop.
The simplest foreground Rails service framework you'll ever see
# vim: ft=ruby
#
# Usage:
#
# Save as ./script/service
#
# Create your ./script/job containing:
#
# load './script/service'
# Service.new do
# log 'Starting marvellous service'
# on_graceful_exit {log 'Bye bye!'}
# loop {puts 'something'; sleep 10}
# end
#
# chmod +x ./script/job
# ./script/job
#
# Run it under daemontools, runit, upstart, God, Bluepill, launchd, whatever.
# Read the source and have fun. Tests and kudos are welcome :-p
#
# - vjt Thu Mar 10 14:06:17 CET 2011
#
require 'pathname'
class Service
def initialize(name = nil, &service)
@name = name || File.basename($0)
check_pidfile
write_pidfile
setup_signals
boot! service
rescue StandardError => e
@horrible_proc.call if @horrible_proc rescue nil
fail! "Uncaught exception #{e.inspect}\n #{e.backtrace.join("\n ")}"
end
protected
def log(*message)
puts [Time.now, "[#$$]", *message].join(' ')
end
def fail!(message)
log message
exit 1
end
def graceful(reason)
@graceful_proc.call if @graceful_proc
log "Exiting via #{reason}"
exit 0
end
def on_graceful_exit(&block)
@graceful_proc = block
end
def on_horrible_exit(&block)
@horrible_proc = block
end
private
def check_pidfile
@pidfile = Pathname("tmp/pids/#@name.pid")
return unless @pidfile.exist?
pid = @pidfile.read.to_i
begin
if Process.kill 0, pid
fail! "#@name is already running as PID #{pid}"
end
rescue Errno::ESRCH, Errno::EPERM => e
# Process does not exist, or is owned by another user:
# either way, it's a stale pifile: remove it and go on
log "Removing stale pidfile [#{pid}]"
@pidfile.unlink
rescue Errno
fail! "#@pidfile already exist: is #@name already running as PID #{pid}?"
end
end
def write_pidfile
@pidfile.open('w+') {|f| f.write($$)}
end
def setup_signals
trap('EXIT') { @pidfile.unlink } # Reminds me of Erlang times...
trap('TERM') { graceful 'SIGTERM' }
trap('INT') { graceful 'SIGINT' }
end
def boot!(service)
log 'Loading application...'
require 'config/environment'
instance_eval &service
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment