Timeout, System timeout & terminator not working for FFI based function

筅森魡賤 提交于 2019-12-11 06:16:33

问题


I have wrote a wrapper through FFI for a shared library function(third party function). This shared library tries to establish a connection with a server. During connection establishment when the server is not reachable third party function waits for 3 mins. In order to avoid that while calling in rails I used tried to use the following timeouts but unfortunately it did not work.

  1. Native Timeout
  2. System timeout
  3. Terminator

Note: when I use Terminator the additional process that is created by it was turning as defunct process.

I am using ruby enterprise version 1.8


回答1:


Seems that calls via FFI block Ruby's scheduler completely, not allowing any threading. This may be related to Ruby's green threads.

The below example illustrates how Ruby concurrency behaves when using 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?

One way to overcome this, is to fork a separate process to carry out the FFI call, and have a timeout on that instead:

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

In the forked child process, all we're interested in doing, is listening for the INT signal to shutdown gently if the timeout is reached, and of course to do the FFI call.

In the parent process, we simply need to timeout the child process, and kill it unless it is done on time.




回答2:


A bit cleaner:

require 'ffi'

module Sleep
  extend FFI::Library

  ffi_lib FFI::Library::LIBC

  attach_function :sleep, [:uint], :void, :blocking => true
end



回答3:


You can mark the functions which will block in the C library as 'blocking' functions, and FFI will unlock the GIL around calls to those functions. (Requires ffi-1.0.x).

e.g.

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

@blocking is not sticky - you need to set it before every 'attach_function' call that you want to mark as blocking.

And its not a 100% sure-fire solution. Interrupting a function which is blocked in native code will work for functions that are interruptible (e.g. sleep, read, write, etc), but won't for some native code (e.g. cpu intensive computations, possibly many other types as well).

Be warned: on ruby 1.8.x, blocking function calls are really slow (compared to blocking calls on 1.9 or JRuby).



来源:https://stackoverflow.com/questions/5281287/timeout-system-timeout-terminator-not-working-for-ffi-based-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!