Skip to content

Commit 1911834

Browse files
committed
multiprocess debugging support
1 parent 3958320 commit 1911834

File tree

9 files changed

+167
-10
lines changed

9 files changed

+167
-10
lines changed

bin/rdebug-ide

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ options = OpenStruct.new(
2626
'port' => 1234,
2727
'stop' => false,
2828
'tracing' => false,
29-
'int_handler' => true
29+
'int_handler' => true,
30+
'dispatcher_port' => -1
3031
)
3132

3233
opts = OptionParser.new do |opts|
@@ -38,7 +39,10 @@ EOB
3839
opts.separator ""
3940
opts.separator "Options:"
4041
opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host}
41-
opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port}
42+
opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port}
43+
opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp|
44+
options.dispatcher_port = dp
45+
end
4246
opts.on('--stop', 'stop when the script is loaded') {options.stop = true}
4347
opts.on("-x", "--trace", "turn on line tracing") {options.tracing = true}
4448
opts.on("-l", "--load-mode", "load mode (experimental)") {options.load_mode = true}
@@ -86,7 +90,19 @@ end
8690

8791
# save script name
8892
Debugger::PROG_SCRIPT = ARGV.shift
89-
93+
94+
if (options.dispatcher_port != -1)
95+
ENV['IDE_PROCESS_DISPATCHER'] = options.dispatcher_port.to_s
96+
if RUBY_VERSION < "1.9"
97+
require 'ruby-debug-ide/multiprocess'
98+
else
99+
require_relative '../lib/ruby-debug-ide/multiprocess'
100+
end
101+
102+
old_opts = ENV['RUBYOPT']
103+
ENV['RUBYOPT'] = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/ruby-debug-ide/multiprocess/starter"
104+
ENV['RUBYOPT'] += " #{old_opts}" if old_opts
105+
end
90106

91107
if options.int_handler
92108
# install interruption handler

lib/ruby-debug-ide.rb

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class << self
1515
# Prints to the stderr using printf(*args) if debug logging flag (-d) is on.
1616
def print_debug(*args)
1717
if Debugger.cli_debug
18+
$stderr.printf("#{Process.pid}: ")
1819
$stderr.printf(*args)
1920
$stderr.printf("\n")
2021
$stderr.flush
@@ -76,7 +77,8 @@ def at_return(file, line)
7677
class << self
7778

7879
attr_accessor :event_processor, :cli_debug, :xml_debug
79-
attr_reader :control_thread
80+
attr_accessor :control_thread
81+
attr_reader :interface
8082

8183
#
8284
# Interrupts the current thread
@@ -104,7 +106,7 @@ def start_server(host = nil, port = 1234)
104106
start_control(host, port)
105107
end
106108

107-
def debug_program(options)
109+
def prepare_debugger(options)
108110
start_server(options.host, options.port)
109111

110112
raise "Control thread did not start (#{@control_thread}}" unless @control_thread && @control_thread.alive?
@@ -116,7 +118,11 @@ def debug_program(options)
116118
@mutex.synchronize do
117119
@proceed.wait(@mutex)
118120
end
119-
121+
end
122+
123+
def debug_program(options)
124+
prepare_debugger(options)
125+
120126
abs_prog_script = File.expand_path(Debugger::PROG_SCRIPT)
121127
bt = debug_load(abs_prog_script, options.stop, options.load_mode)
122128
if bt && !bt.is_a?(SystemExit)
@@ -143,13 +149,18 @@ def start_control(host, port)
143149
$stderr.printf "Fast Debugger (ruby-debug-ide #{IDE_VERSION}, ruby-debug-base #{VERSION}) listens on #{host}:#{port}\n"
144150
server = TCPServer.new(host, port)
145151
while (session = server.accept)
152+
$stderr.puts "Connected from #{session.addr[2]}" if Debugger.cli_debug
153+
dispatcher = ENV['IDE_PROCESS_DISPATCHER']
154+
if (dispatcher)
155+
ENV['IDE_PROCESS_DISPATCHER'] = "#{session.addr[2]}:#{dispatcher}" unless dispatcher.include?(":")
156+
end
146157
begin
147-
interface = RemoteInterface.new(session)
158+
@interface = RemoteInterface.new(session)
148159
@event_processor = EventProcessor.new(interface)
149160
IdeControlCommandProcessor.new(interface).process_commands
150161
rescue StandardError, ScriptError => ex
151162
bt = ex.backtrace
152-
$stderr.printf "Exception in DebugThread loop: #{ex.message}\nBacktrace:\n#{bt ? bt.join("\n from: ") : "<none>"}\n"
163+
$stderr.printf "#{Process.pid}: Exception in DebugThread loop: #{ex.message}\nBacktrace:\n#{bt ? bt.join("\n from: ") : "<none>"}\n"
153164
exit 1
154165
end
155166
end

lib/ruby-debug-ide/event_processor.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def line_event(context, file, line)
5353
@last_breakpoint = nil
5454
end
5555
raise "DebuggerThread are not supposed to be traced (#{context.thread})" if context.thread.is_a?(Debugger::DebugThread)
56-
@printer.print_debug("Stopping Thread %s", context.thread.to_s)
56+
@printer.print_debug("Stopping Thread %s (%s)", context.thread.to_s, Process.pid.to_s)
5757
@printer.print_debug("Threads equal: %s", Thread.current == context.thread)
5858
IdeCommandProcessor.new(@interface).process_commands
5959
InspectCommand.clear_references

lib/ruby-debug-ide/interface.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class LocalInterface < Interface
2323

2424
class RemoteInterface < Interface # :nodoc:
2525
attr_accessor :command_queue
26+
attr_accessor :socket
2627

2728
def initialize(socket)
2829
@socket = socket

lib/ruby-debug-ide/multiprocess.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
if RUBY_VERSION < "1.9"
2+
require 'ruby-debug-ide/multiprocess/pre_child'
3+
require 'ruby-debug-ide/multiprocess/monkey'
4+
else
5+
require_relative 'multiprocess/pre_child'
6+
require_relative 'multiprocess/monkey'
7+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module Debugger
2+
module MultiProcess
3+
def self.create_mp_fork
4+
%Q{
5+
alias pre_debugger_fork fork
6+
7+
def fork(*args)
8+
if block_given?
9+
return pre_debugger_fork{Debugger::MultiProcess::pre_child; yield}
10+
end
11+
result = pre_debugger_fork
12+
Debugger::MultiProcess::pre_child unless result
13+
result
14+
end
15+
}
16+
end
17+
18+
def self.create_mp_exec
19+
%Q{
20+
alias pre_debugger_exec exec
21+
22+
def exec(*args)
23+
Debugger.interface.close
24+
pre_debugger_exec(*args)
25+
end
26+
}
27+
end
28+
end
29+
end
30+
31+
module Kernel
32+
class << self
33+
module_eval Debugger::MultiProcess.create_mp_fork
34+
module_eval Debugger::MultiProcess.create_mp_exec
35+
end
36+
module_eval Debugger::MultiProcess.create_mp_fork
37+
module_eval Debugger::MultiProcess.create_mp_exec
38+
end
39+
40+
module Process
41+
class << self
42+
module_eval Debugger::MultiProcess.create_mp_fork
43+
module_eval Debugger::MultiProcess.create_mp_exec
44+
end
45+
end
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
module Debugger
2+
module MultiProcess
3+
class << self
4+
def pre_child
5+
6+
require "socket"
7+
require "ostruct"
8+
9+
host = '127.0.0.1'
10+
port = find_free_port(host)
11+
12+
options = OpenStruct.new(
13+
'frame_bind' => false,
14+
'host' => host,
15+
'load_mode' => false,
16+
'port' => port,
17+
'stop' => false,
18+
'tracing' => false,
19+
'int_handler' => true
20+
)
21+
22+
acceptor_host, acceptor_port = ENV['IDE_PROCESS_DISPATCHER'].split(":")
23+
acceptor_host, acceptor_port = '127.0.0.1', acceptor_host unless acceptor_port
24+
25+
connected = false
26+
3.times do |i|
27+
begin
28+
s = TCPSocket.open(acceptor_host, acceptor_port)
29+
s.print(port)
30+
s.close
31+
connected = true
32+
start_debugger(options)
33+
return
34+
rescue => bt
35+
$stderr.puts "#{Process.pid}: connection failed(#{i+1})"
36+
$stderr.puts "Exception: #{bt}"
37+
$stderr.puts bt.backtrace.map { |l| "\t#{l}" }.join("\n")
38+
sleep 0.3
39+
end unless connected
40+
end
41+
end
42+
43+
def start_debugger(options)
44+
if Debugger.started?
45+
#we're in forked child, only need to restart control thread
46+
Debugger.control_thread = nil
47+
Debugger.start_control(options.host, options.port)
48+
end
49+
50+
if options.int_handler
51+
# install interruption handler
52+
trap('INT') { Debugger.interrupt_last }
53+
end
54+
55+
# set options
56+
Debugger.keep_frame_binding = options.frame_bind
57+
Debugger.tracing = options.tracing
58+
59+
Debugger.prepare_debugger(options)
60+
end
61+
62+
63+
def find_free_port(host)
64+
server = TCPServer.open(host, 0)
65+
port = server.addr[1]
66+
server.close
67+
port
68+
end
69+
end
70+
end
71+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
if ENV['IDE_PROCESS_DISPATCHER']
2+
require 'rubygems'
3+
require 'ruby-debug-ide'
4+
require 'ruby-debug-ide/multiprocess'
5+
Debugger::MultiProcess::pre_child
6+
end

lib/ruby-debug-ide/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Debugger
2-
IDE_VERSION='0.4.17.beta9'
2+
IDE_VERSION='0.4.17.beta10'
33
end

0 commit comments

Comments
 (0)