Create a gist now

Instantly share code, notes, and snippets.

require 'ruby-debug'
require 'socket'
require 'forwardable'
class SporkDebugger
DEFAULT_PORT = 10_123
HOST = '127.0.0.1'
extend Forwardable
def_delegators :state, *[:prepare_debugger, :initialize]
attr_reader :state
class << self
attr_reader :instance
def run
@instance = new
end
end
def initialize
@state = SporkDebugger::PreloadState.new
Spork.send(:each_run_procs).unshift(lambda do
@state = @state.transition_to_each_run_state
end)
end
module NetworkHelpers
def find_port(starting_with)
port = starting_with
begin
server = TCPServer.new(HOST, port)
server.close
rescue Errno::EADDRINUSE
port += 1
retry
end
port
end
end
class PreloadState
include NetworkHelpers
def initialize
install_hook
listen_for_connection_signals
end
def finish
@tcp_service.close; @tcp_service = nil;
end
def transition_to_each_run_state
finish
SporkDebugger::EachRunState.new(@port)
end
protected
def install_hook
Kernel.class_eval do
alias :debugger_without_spork_hook :debugger
def debugger(steps = 1)
SporkDebugger.instance.prepare_debugger
debugger_without_spork_hook
end
end
end
def listen_for_connection_signals
@port = SporkDebugger::DEFAULT_PORT
begin
@tcp_service = TCPServer.new(SporkDebugger::HOST, @port)
rescue Errno::EADDRINUSE
@port += 1
retry
end
Thread.new { main_loop }
end
def main_loop
while @tcp_service do
socket = @tcp_service.accept
port = Marshal.load(socket)
Marshal.dump(true, socket)
connect_rdebug_client(port)
socket.close
end
rescue => e
puts "#{$$} - error: #{e}"
end
def connect_rdebug_client(port = Debugger::PORT)
Debugger.start_client(SporkDebugger::HOST, port)
puts "- Process terminated -"
end
end
class EachRunState
include NetworkHelpers
def initialize(connection_request_port)
@connection_request_port = connection_request_port
end
def prepare_debugger
port, cport = start_rdebug_server
signal_spork_server_to_connect_to_rdebug_server(port)
wait_for_connection
end
def signal_spork_server_to_connect_to_rdebug_server(rdebug_server_port)
socket = TCPSocket.new(SporkDebugger::HOST, @connection_request_port)
Marshal.dump(rdebug_server_port, socket)
response = Marshal.load(socket)
socket.close
end
def start_rdebug_server
Debugger.stop rescue nil
port = find_port(Debugger::PORT)
cport = find_port(port + 1)
Debugger.start_remote(SporkDebugger::HOST, [port, cport]) do
Debugger.run_init_script(StringIO.new)
end
[port, cport]
end
protected
def wait_for_connection
while Debugger.handler.interface.nil?; sleep 0.10; end
end
end
end
Spork.prefork do
SporkDebugger.run
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment