Simple, hot-restartable Ruby service
require 'socket' | |
class SuperSimpleService | |
attr_reader :bind_address, :bind_port | |
def initialize | |
@bind_address = 'localhost' | |
@bind_port = 12345 | |
end | |
def run | |
if ENV['SOCKET_FD'] | |
@socket_server = TCPServer.for_fd(ENV['SOCKET_FD'].to_i) | |
kill_parent | |
else | |
@socket_server = TCPServer.new(bind_address, bind_port) | |
end | |
@socket_server.autoclose = false | |
@socket_server.close_on_exec = false | |
setup_signal_traps | |
loop do | |
begin | |
client_socket = @socket_server.accept # blocks until a new connection is made | |
@connection_active = true | |
client_socket.puts "Hello World!" | |
client_socket.close | |
ensure | |
@connection_active = false | |
end | |
end | |
end | |
def setup_signal_traps | |
trap('USR1') { hot_restart } | |
trap('TERM') { graceful_shutdown } | |
end | |
def hot_restart | |
fork do | |
# :close_others ensures that open file descriptors are inherited by the new process | |
exec("SOCKET_FD=#{@socket_server.fileno} ruby super_simple_service.rb", close_others: false) | |
end | |
end | |
def graceful_shutdown | |
@socket_server.close # Stop listening for new connections | |
sleep 0.1 while @connection_active # Wait for active connection to complete | |
Process.exit(0) | |
end | |
def kill_parent | |
parent_process_id = Process.ppid | |
Process.kill('TERM', parent_process_id) | |
end | |
end | |
SuperSimpleService.new.run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment