Skip to content

Commit 0a62987

Browse files
authored
Merge pull request #125 from ViugiNick/jruby-heuristic
Implementation of exec_with_allocation_control without exceptions
2 parents 4b2c816 + c9361b1 commit 0a62987

File tree

3 files changed

+103
-62
lines changed

3 files changed

+103
-62
lines changed

bin/rdebug-ide

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ end
1111
$stdout.sync=true
1212

1313
options = OpenStruct.new(
14-
'frame_bind' => false,
15-
'host' => nil,
16-
'load_mode' => false,
17-
'port' => 1234,
18-
'stop' => false,
19-
'tracing' => false,
20-
'int_handler' => true,
21-
'dispatcher_port' => -1,
22-
'evaluation_timeout' => 10,
23-
'rm_protocol_extensions' => false,
24-
'catchpoint_deleted_event' => false,
25-
'value_as_nested_element' => false,
26-
'attach_mode' => false,
27-
'cli_debug' => false
14+
'frame_bind' => false,
15+
'host' => nil,
16+
'load_mode' => false,
17+
'port' => 1234,
18+
'stop' => false,
19+
'tracing' => false,
20+
'int_handler' => true,
21+
'dispatcher_port' => -1,
22+
'evaluation_timeout' => 10,
23+
'rm_protocol_extensions' => false,
24+
'catchpoint_deleted_event' => false,
25+
'value_as_nested_element' => false,
26+
'attach_mode' => false,
27+
'cli_debug' => false
2828
)
2929

3030
opts = OptionParser.new do |opts|
@@ -36,20 +36,25 @@ Usage: rdebug-ide is supposed to be called from RDT, NetBeans, RubyMine, or
3636
EOB
3737
opts.separator ""
3838
opts.separator "Options:"
39-
39+
40+
Debugger.trace_to_s = false
41+
opts.on("--evaluation-control", "trace to_s evaluation") do
42+
Debugger.trace_to_s = true
43+
end
44+
4045
ENV['DEBUGGER_MEMORY_LIMIT'] = '10'
41-
opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit|
42-
ENV['DEBUGGER_MEMORY_LIMIT'] = limit
46+
opts.on("-m", "--memory-limit LIMIT", Integer, "evaluation memory limit in mb (default: 10)") do |limit|
47+
ENV['DEBUGGER_MEMORY_LIMIT'] = limit.to_s
4348
end
44-
49+
4550
ENV['INSPECT_TIME_LIMIT'] = '100'
46-
opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit|
47-
ENV['INSPECT_TIME_LIMIT'] = limit
51+
opts.on("-t", "--time-limit LIMIT", Integer, "evaluation time limit in milliseconds (default: 100)") do |limit|
52+
ENV['INSPECT_TIME_LIMIT'] = limit.to_s
4853
end
4954

5055
opts.on("-h", "--host HOST", "Host name used for remote debugging") {|host| options.host = host}
5156
opts.on("-p", "--port PORT", Integer, "Port used for remote debugging") {|port| options.port = port}
52-
opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp|
57+
opts.on("--dispatcher-port PORT", Integer, "Port used for multi-process debugging dispatcher") do |dp|
5358
options.dispatcher_port = dp
5459
end
5560
opts.on('--evaluation-timeout TIMEOUT', Integer,'evaluation timeout in seconds (default: 10)') do |timeout|
@@ -133,7 +138,7 @@ if options.dispatcher_port != -1
133138
end
134139
Debugger::MultiProcess.do_monkey
135140

136-
ENV['DEBUGGER_STORED_RUBYLIB'] = ENV['RUBYLIB']
141+
ENV['DEBUGGER_STORED_RUBYLIB'] = ENV['RUBYLIB']
137142
old_opts = ENV['RUBYOPT'] || ''
138143
starter = "-r#{File.expand_path(File.dirname(__FILE__))}/../lib/ruby-debug-ide/multiprocess/starter"
139144
unless old_opts.include? starter

lib/ruby-debug-ide.rb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,22 @@ def print_debug(*args)
2929
end
3030

3131
def cleanup_backtrace(backtrace)
32-
cleared = []
33-
return cleared unless backtrace
34-
backtrace.each do |line|
35-
if line.index(File.expand_path(File.dirname(__FILE__) + "/..")) == 0
36-
next
37-
end
38-
if line.index("-e:1") == 0
39-
break
40-
end
41-
cleared << line
42-
end
43-
cleared
32+
cleared = []
33+
return cleared unless backtrace
34+
backtrace.each do |line|
35+
if line.index(File.expand_path(File.dirname(__FILE__) + "/..")) == 0
36+
next
37+
end
38+
if line.index("-e:1") == 0
39+
break
40+
end
41+
cleared << line
42+
end
43+
cleared
4444
end
4545

4646
attr_accessor :attached
47-
attr_accessor :cli_debug, :xml_debug, :evaluation_timeout
47+
attr_accessor :cli_debug, :xml_debug, :evaluation_timeout, :trace_to_s
4848
attr_accessor :control_thread
4949
attr_reader :interface
5050
# protocol extensions

lib/ruby-debug-ide/xml_printer.rb

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,29 @@ module OverflowMessageType
1313
SPECIAL_SYMBOL_MESSAGE = lambda {|e| '<?>'}
1414
end
1515

16-
class ExecError < StandardError
16+
class ExecError
1717
attr_reader :message
18-
attr_reader :trace_point
1918
attr_reader :backtrace
2019

21-
def initialize(message, trace_point, backtrace = [])
20+
def initialize(message, backtrace = [])
2221
@message = message
23-
@trace_point = trace_point
2422
@backtrace = backtrace
2523
end
2624
end
2725

28-
class MemoryLimitError < ExecError; end
26+
class SimpleTimeLimitError < StandardError
27+
attr_reader :message
28+
29+
def initialize(message)
30+
@message = message
31+
end
32+
end
33+
34+
class MemoryLimitError < ExecError;
35+
end
2936

30-
class TimeLimitError < ExecError; end
37+
class TimeLimitError < ExecError;
38+
end
3139

3240
class XmlPrinter # :nodoc:
3341
class ExceptionProxy
@@ -163,48 +171,76 @@ def print_string(string)
163171
end
164172
end
165173

174+
def exec_with_timeout(sec, error_message)
175+
return yield if sec == nil or sec.zero?
176+
if Thread.respond_to?(:critical) and Thread.critical
177+
raise ThreadError, "timeout within critical session"
178+
end
179+
begin
180+
x = Thread.current
181+
y = DebugThread.start {
182+
sleep sec
183+
x.raise SimpleTimeLimitError.new(error_message) if x.alive?
184+
}
185+
yield sec
186+
ensure
187+
y.kill if y and y.alive?
188+
end
189+
end
190+
166191
def exec_with_allocation_control(value, memory_limit, time_limit, exec_method, overflow_message_type)
167-
return value.send exec_method if RUBY_VERSION < '2.0'
192+
return value.send exec_method if !Debugger.trace_to_s
193+
return exec_with_timeout(time_limit * 1e-3, "Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.") {value.send exec_method} if defined?(JRUBY_VERSION) || memory_limit <= 0 || (RUBY_VERSION < '2.0' && time_limit > 0)
194+
168195

169-
check_memory_limit = !defined?(JRUBY_VERSION) && ENV['DEBUGGER_MEMORY_LIMIT'].to_i > 0
170196
curr_thread = Thread.current
197+
control_thread = Debugger.control_thread
171198

172199
result = nil
200+
201+
trace_queue = Queue.new
202+
173203
inspect_thread = DebugThread.start do
174-
start_alloc_size = ObjectSpace.memsize_of_all if check_memory_limit
204+
start_alloc_size = ObjectSpace.memsize_of_all
175205
start_time = Time.now.to_f
176206

177-
trace_point = TracePoint.new(:c_call, :call) do | |
178-
next unless Thread.current == inspect_thread
179-
next unless rand > 0.75
180-
207+
trace_point = TracePoint.new(:c_call, :call) do |tp|
181208
curr_time = Time.now.to_f
182209

183210
if (curr_time - start_time) * 1e3 > time_limit
184-
curr_thread.raise TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", trace_point, caller.to_a)
211+
trace_queue << TimeLimitError.new("Timeout: evaluation of #{exec_method} took longer than #{time_limit}ms.", caller.to_a)
212+
trace_point.disable
213+
inspect_thread.kill
185214
end
186215

187-
if check_memory_limit
188-
curr_alloc_size = ObjectSpace.memsize_of_all
189-
start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size
216+
next unless rand > 0.75
190217

191-
if curr_alloc_size - start_alloc_size > 1e6 * memory_limit
192-
curr_thread.raise MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", trace_point, caller.to_a)
193-
end
218+
curr_alloc_size = ObjectSpace.memsize_of_all
219+
start_alloc_size = curr_alloc_size if curr_alloc_size < start_alloc_size
220+
221+
if curr_alloc_size - start_alloc_size > 1e6 * memory_limit
222+
trace_queue << MemoryLimitError.new("Out of memory: evaluation of #{exec_method} took more than #{memory_limit}mb.", caller.to_a)
223+
trace_point.disable
224+
inspect_thread.kill
194225
end
195226
end
196227
trace_point.enable
197228
result = value.send exec_method
229+
trace_queue << result
198230
trace_point.disable
199231
end
200-
inspect_thread.join
201-
return result
202-
rescue ExecError => e
203-
e.trace_point.disable
204-
print_debug(e.message + "\n" + e.backtrace.map {|l| "\t#{l}"}.join("\n"))
232+
233+
while(mes = trace_queue.pop)
234+
if(mes.is_a? TimeLimitError or mes.is_a? MemoryLimitError)
235+
print_debug(mes.message + "\n" + mes.backtrace.map {|l| "\t#{l}"}.join("\n"))
236+
return overflow_message_type.call(mes)
237+
else
238+
return mes
239+
end
240+
end
241+
rescue SimpleTimeLimitError => e
242+
print_debug(e.message)
205243
return overflow_message_type.call(e)
206-
ensure
207-
inspect_thread.kill if inspect_thread && inspect_thread.alive?
208244
end
209245

210246
def print_variable(name, value, kind)

0 commit comments

Comments
 (0)