Кажется, что вызовы через FFI полностью блокируют планировщик Ruby, не пропуская потоки. Это может быть связано с зелеными нитями Руби.
В приведенном ниже примере показано, как ведет себя параллелизм Ruby при использовании FFI:
require 'ffi'
module Sleep
extend FFI::Library
ffi_lib FFI::Library::LIBC
attach_function :sleep, [:uint], :void
end
thread = Thread.start do
count = 1
while count <= 10
puts count
count += 1
sleep 0.5
end
end
puts "FFI sleep"
Sleep.sleep 5 # Everything blocks, second thread is run after sleep
puts "Ruby sleep"
sleep 5 # Scheduling works, other thread runs simultaneously
thread.join if thread.alive?
Один из способов преодолеть это - запустить отдельный процесс для выполнения вызова FFI и вместо этого установить таймаут:
require 'ffi'
require 'timeout'
module Sleep
extend FFI::Library
ffi_lib FFI::Library::LIBC
attach_function :sleep, [:uint], :void
end
child_pid = Process.fork do
Signal.trap("INT") do
exit
end
Sleep.sleep 5
exit
end
begin
Timeout::timeout(2) do
Process.wait(child_pid)
end
rescue Timeout::Error
Process.kill("INT", child_pid)
end
В разветвленном дочернем процессе все, что нам интересно, это прислушиваться к осторожному отключению сигнала INT
при достижении тайм-аута и, конечно, к вызову FFI.
В родительском процессе нам просто нужно тайм-аут дочернего процесса и уничтожить его, если он не сделан вовремя.