Skip to content

Commit 36a04de

Browse files
committed
Dump with debugger before killing stuck worker
1 parent c88c231 commit 36a04de

File tree

4 files changed

+97
-9
lines changed

4 files changed

+97
-9
lines changed

tool/lib/dump.gdb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
set height 0
2+
set width 0
3+
set confirm off
4+
5+
echo \n>>> Threads\n\n
6+
info threads
7+
8+
echo \n>>> Machine level backtrace\n\n
9+
thread apply all info stack full
10+
11+
echo \n>>> Dump Ruby level backtrace (if possible)\n\n
12+
call rb_vmdebug_stack_dump_all_threads()
13+
call fflush(stderr)
14+
15+
echo ">>> Finish\n"
16+
detach
17+
quit

tool/lib/dump.lldb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
script print("\n>>> Threads\n\n")
2+
thread list
3+
4+
script print("\n>>> Machine level backtrace\n\n")
5+
thread backtrace all
6+
7+
script print("\n>>> Dump Ruby level backtrace (if possible)\n\n")
8+
call rb_vmdebug_stack_dump_all_threads()
9+
call fflush(stderr)
10+
11+
script print(">>> Finish\n")
12+
detach
13+
quit

tool/lib/envutil.rb

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,70 @@ def timeout(sec, klass = nil, message = nil, &blk)
7979
end
8080
module_function :timeout
8181

82+
class Debugger
83+
@list = []
84+
85+
attr_accessor :name
86+
87+
def self.register(name, &block)
88+
@list << new(name, &block)
89+
end
90+
91+
def initialize(name, &block)
92+
@name = name
93+
instance_eval(&block)
94+
end
95+
96+
def usable?; false; end
97+
98+
def start(pid, *args) end
99+
100+
def dump(pid, timeout: 60, reprieve: timeout&.div(4))
101+
dpid = start(pid, *command_file(File.join(__dir__, "dump.#{name}")))
102+
rescue Errno::ENOENT
103+
return
104+
else
105+
return unless dpid
106+
[[timeout, :TERM], [reprieve, :KILL]].find do |t, sig|
107+
return EnvUtil.timeout(t) {Process.wait(dpid)}
108+
rescue Timeout::Error
109+
Process.kill(sig, dpid)
110+
end
111+
true
112+
end
113+
114+
# sudo -n: --non-interactive
115+
PRECOMMAND = (%[sudo -n] if /darwin/ =~ RUBY_PLATFORM)
116+
117+
def spawn(*args, **opts)
118+
super(*PRECOMMAND, *args, **opts)
119+
end
120+
121+
register("gdb") do
122+
class << self
123+
def usable?; system(*%w[gdb --batch --quiet --nx -ex exit]); end
124+
def start(pid, *args)
125+
spawn(*%w[gdb --batch --quiet --pid #{pid}], *args)
126+
end
127+
def command_file(file) "--command=#{file}"; end
128+
end
129+
end
130+
131+
register("lldb") do
132+
class << self
133+
def usable?; system(*%w[lldb -Q --no-lldbinit -o exit]); end
134+
def start(pid, *args)
135+
spawn(*%w[lldb --batch -Q --attach-pid #{pid}])
136+
end
137+
def command_file(file) ["--source", file]; end
138+
end
139+
end
140+
141+
def self.search
142+
@debugger ||= @list.find(&:usable?)
143+
end
144+
end
145+
82146
def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
83147
reprieve = apply_timeout_scale(reprieve) if reprieve
84148

@@ -94,17 +158,10 @@ def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
94158
pgroup = pid
95159
end
96160

97-
lldb = true if /darwin/ =~ RUBY_PLATFORM
98-
99161
while signal = signals.shift
100162

101-
if lldb and [:ABRT, :KILL].include?(signal)
102-
lldb = false
103-
# sudo -n: --non-interactive
104-
# lldb -p: attach
105-
# -o: run command
106-
system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit])
107-
true
163+
if (dbg = Debugger.search) and [:ABRT, :KILL].include?(signal)
164+
dbg.dump(pid)
108165
end
109166

110167
begin

tool/lib/test/unit.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ def quit(reason = :normal)
421421
end
422422

423423
def kill
424+
EnvUtil::Debugger.search&.dump(@pid)
424425
signal = RUBY_PLATFORM =~ /mswin|mingw/ ? :KILL : :SEGV
425426
Process.kill(signal, @pid)
426427
warn "worker #{to_s} does not respond; #{signal} is sent"

0 commit comments

Comments
 (0)