Timeouts de Ruby são perigosos

Os tempos limite do Ruby (especificamente usando o MRI – Ruby 1.8, 1.9 e 2.0) são perigosos . Use-os com cautela ou apenas evite-os. Os erros a seguir mostram que existem algumas condições de corrida relacionadas a tempos limite. Veja https://bugs.ruby-lang.org/issues/4266 , bem como https://bugs.ruby-lang.org/issues/7503 .

Os tempos limite do Ruby criam um encadeamento para cada tempo limite. Este thread então dorme por um determinado período de tempo antes que as chamadas de código rendam no bloco fornecido. Aqui está o snippet do ruby ​​2.0

Veja timeout.rb ( https://github.com/ruby/ruby/blob/trunk/lib/timeout.rb#L50 )

def timeout(sec, klass = nil)   #:yield: +sec+
...
begin
x
= Thread.current
y
= Thread.start { ... }
return yield(sec)
ensure
if y
y
.kill
y
.join # make sure y is dead.
end
end

Vamos usar a gem mysql2 em combinação com o tempo limite para ver o quão “perigoso” isso realmente é.

Crie um banco de dados mysql e preencha-o com algumas linhas

CREATE DATABASE testsql;
USE testsql
;
CREATE TABLE statements
(id INT);
INSERT INTO statements
(id) VALUES(9);
INSERT INTO statements
(id) VALUES(11);

Vamos executar algum código com tempo limite. Use o rvm com ruby ​​2.0 ou mesmo 1.9

require 'timeout'
require 'mysql2'
client
= Mysql2::Client.new(:host => 'localhost',
:database => 'testsql',
:username => 'root',
:password => '')
begin
Timeout.timeout(1) {
results
= client.query("select * from statements where id = 17")
puts
"outer:#{results.count}"
while(true) do
Timeout.timeout(0.0001) {
results
= client.query("select * from statements where id = 9")
puts
"inner:#{results.count}"
}
sleep
0.2
end
}
rescue Timeout::Error
results
= client.query("select * from statements where id = 9")
puts
"timeout:#{results.count}"
end

Execute isso algumas vezes e você verá alguns resultados estranhos que são condições de corrida na gem mysql2. Às vezes você vai ver isso

in `query': closed MySQL connection (Mysql2::Error)

ou isto

in `query': This connection is still waiting for a result, try again once you have the result (Mysql2::Error)

e às vezes ele simplesmente funciona sem erros.

Se você olhar profundamente dentro do código c da gem mysql2, você notará wrapper-> active_thread que retornará nil se não for a thread ativa. Consulte https://github.com/brianmario/mysql2/blob/master/ext/mysql2/client.c .

Se você usar ruby ​​2.0 e tracepoint, notará algo assim; onde o código chama para dentro e para fora do código C de vários threads.

line    timeout  Timeout
line

c
-call query Mysql2::Client
c
-call sleep Kernel
c
-return sleep Kernel
line timeout
Timeout
c
-call raise Thread
c
-call exception Exception
c
-call initialize Exception
c
-return initialize Exception
c
-return exception Exception
c
-return raise Thread
c
-return query Mysql2::Client
line timeout
Timeout

Em geral, evite tempos limite ou evite fazer muito dentro de um tempo limite que envolve código C e extensões que podem ter problemas de reentrada.

Se você usar tempos limites (e eles estão disparando), você está executando um aplicativo multithread.

Esta postagem antiga do blog fornece um contexto melhor e ainda parece relevante – http://headius.blogspot.com/2008/02/rubys-threadraise-threadkill-timeoutrb.html .