require 'ruby-debug' require 'socket' require 'forwardable' # Experimental! No automated tests are checking this, use at your own risk! 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) puts "\n\n - Debug Session Started - \n\n\n" Debugger.start_client(SporkDebugger::HOST, port) puts "\n\n - Debug Session Terminated - \n\n\n" 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 puts "\n\n - breakpoint - see your spork server for the debug terminal - \n\n" 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 if Debugger.started? 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 Debugger.start [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