问题
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.
- Native Timeout
- System timeout
- 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