Тайм-аут, тайм-аут системы и терминатор не работает для функции на основе FFI - PullRequest
1 голос
/ 12 марта 2011

Я написал оболочку через FFI для функции общей библиотеки (сторонней функции). Эта общая библиотека пытается установить соединение с сервером. Во время установления соединения, когда сервер недоступен, сторонняя функция ожидает 3 минуты. Во избежание этого при вызове в рельсах я пытался использовать следующие таймауты, но, к сожалению, это не сработало.

  1. Собственное время ожидания
  2. Системное время ожидания
  3. Terminator

Примечание: когда я использую Terminator, создаваемый им дополнительный процесс превращается в несуществующий процесс.

Я использую Ruby Enterprise версии 1.8

Ответы [ 3 ]

2 голосов
/ 12 марта 2011

Кажется, что вызовы через 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.

В родительском процессе нам просто нужно тайм-аут дочернего процесса и уничтожить его, если он не сделан вовремя.

1 голос
/ 13 января 2012

Немного чище:

require 'ffi'

module Sleep
  extend FFI::Library

  ffi_lib FFI::Library::LIBC

  attach_function :sleep, [:uint], :void, :blocking => true
end
0 голосов
/ 15 марта 2011

Вы можете пометить функции, которые будут блокировать в библиотеке C, как «блокирующие» функции, и FFI будет разблокировать GIL при вызовах этих функций. (Требуется ffi-1.0.x).

, например

require 'ffi'
module Sleep
  extend FFI::Library

  ffi_lib FFI::Library::LIBC

  # Tell FFI that this function may block
  @blocking = true
  attach_function :sleep, [:uint], :void
end

@ блокировка не является липкой - ее нужно устанавливать перед каждым вызовом 'attach_function', который вы хотите пометить как блокирующий.

И это не 100% надежное решение. Прерывание функции, которая заблокирована в собственном коде, будет работать для функций, которые прерываются (например, сон, чтение, запись и т. Д.), Но не для некоторого собственного кода (например, вычисления с интенсивным использованием ЦП, возможно, и многих других типов).

Имейте в виду: на ruby ​​1.8.x вызовы функций блокировки действительно медленные (по сравнению с вызовами 1.9 или JRuby).

...