Как отследить тупик в Ruby
Я использую BrB для совместного использования источника данных для различных рабочих процессов в Ruby 1.9, который я разветвляю с помощью Process#fork
:
Thread.abort_on_exception = true
fork do
puts "Initializing data source process... (PID: #{Process.pid})"
data = DataSource.new(files)
BrB::Service.start_service(:object => data, :verbose => false, :host => host, :port => port)
EM.reactor_thread.join
end
Рабочие разветвляются следующим образом:
8.times do |t|
fork do
data = BrB::Tunnel.create(nil, "brb://#{host}:#{port}", :verbose => false)
puts "Launching #{threads_num} worker threads... (PID: #{Process.pid})"
threads = []
threads_num.times { |i|
threads << Thread.new {
while true
begin
worker = Worker.new(data, config)
rescue OutOfTargetsError
break
rescue Exception => e
puts "An unexpected exception was caught: #{e.class} => #{e}"
sleep 5
end
end
}
}
threads.each { |t| t.join }
data.stop_service
EM.stop
end
end
Это работает почти идеально, но примерно через 10 минут работы я получаю следующую ошибку:
bootstrap.rb:47:in 'join': deadlock detected (fatal)
from bootstrap.rb:47:in 'block in <main>'
from bootstrap.rb:39:in 'fork'
from bootstrap.rb:39:in '<main>'</pre>
Эта ошибка не говорит мне много о том, где фактически происходит тупик, она только указывает мне на join
в потоке EventMachine.
Как мне отследить, в какой момент программа блокируется?
Ответы
Ответ 1
Это блокировка при join
в родительском потоке, эта информация точна. Чтобы отследить, где он блокируется в дочернем потоке, попробуйте обернуть работу потока в блок timeout
. Вам нужно будет временно удалить всеохватывающее rescue
чтобы сработало исключение тайм-аута.
В настоящее время родительский поток пытается объединить все потоки по порядку, блокируя до тех пор, пока не завершится. Однако каждый поток будет присоединяться только к OutOfTargetsError
. Взаимоблокировки можно избежать, используя короткую-жили нить и перемещая while
цикл в родитель. Гарантий нет, но может что-то подобное сработает?
8.times do |t|
fork do
running = true
Signal.trap("INT") do
puts "Interrupt signal received, waiting for threads to finish..."
running = false
end
data = BrB::Tunnel.create(nil, "brb://#{host}:#{port}", :verbose => false)
puts "Launching max #{threads_num} worker threads... (PID: #{Process.pid})"
threads = []
while running
# Start new threads until we have threads_num running
until threads.length >= threads_num do
threads << Thread.new {
begin
worker = Worker.new(data, config)
rescue OutOfTargetsError
rescue Exception => e
puts "An unexpected exception was caught: #{e.class} => #{e}"
sleep 5
end
}
end
# Make sure the parent process doesn't spin too much
sleep 1
# Join finished threads
finished_threads = threads.reject &:status
threads -= finished_threads
finished_threads.each &:join
end
data.stop_service
EM.stop
end
end
Ответ 2
У меня была та же проблема, и я решил ее, используя следующий фрагмент кода:
# Wait for all threads (other than the current thread and
# main thread) to stop running.
# Assumes that no new threads are started while waiting
def join_all
main = Thread.main # The main thread
current = Thread.current # The current thread
all = Thread.list # All threads still running
# Now call join on each thread
all.each{|t| t.join unless t == current or t == main }
end
Источник: Язык программирования Ruby, О'Рейли (2008)