2011-10-04 4 views
5

Estoy usando Ruby 1.9.2.Usar el tiempo de espera de ruby ​​en un hilo haciendo una llamada a la base de datos

Tengo un hilo ejecutándose que realiza llamadas periódicas a una base de datos. Las llamadas pueden ser bastante largas, y algunas veces (por varias razones) la conexión de DB desaparece. Si desaparece, el hilo se cuelga silenciosamente para siempre.

Por lo tanto, quiero envolverlo todo en un tiempo de espera para manejar esto. El problema es que, en la segunda ocasión en que debe llamarse un tiempo de espera (siempre en segundo lugar), simplemente se bloquea. El tiempo de espera nunca tiene efecto. Sé que este problema existía en 1.8, pero me hicieron creer que timeout.rb funcionó en 1.9.

t = Thread.new do 
    while true do 
    sleep SLEEPTIME 
    begin 
     Timeout::timeout(TIMEOUTTIME) do 
     puts "About to do DB stuff, it will hang here on the second timeout" 
     db.do_db_stuff() 
     process_db_stuff() 
     end 
    rescue Timeout::Error 
     puts "Timed out" 
     #handle stuff here 
    end 
    end 
end 

¿Alguna idea de por qué esto está sucediendo y qué puedo hacer al respecto?

+0

Así que no está tirando Tiempo de espera :: ¿Error? ¿Está arrojando algún error? –

+0

Correcto, no pasa nada, simplemente se congela justo donde está. Al menos, eso es lo que he determinado al poner 'puts' excesivas en todas partes para determinar dónde/si la ejecución se recogió una copia de seguridad. –

+0

¿Por qué estaría haciendo eso tirar? El punto de espera total debería ser que genera un error si el bloque que se ejecuta se ejecuta durante demasiado tiempo. He intentado con datamapper y mysql2 directamente, esperando o hacer una reconexión automática y resolver el problema de esa manera, pero sin suerte. Si los detalles ayudan, digamos que es una conexión mysql2 en la que hago un '.query (...)'. Nunca se arroja nada desde allí, de todos modos, y una cláusula genérica de 'rescate' fuera debería atraparlo en cualquier caso. –

Respuesta

5

Una posibilidad es que su hilo no se cuelga, en realidad muere. Esto es lo que debe hacer para descubrir qué está pasando. Añadir este antes de crear el subproceso de trabajo:

Thread.abort_on_exception = true 

Cuando se produce una excepción dentro de su hilo que no está atrapado, todo el proceso se termina, y se puede ver qué excepción fue levantado. De lo contrario (y este es el valor predeterminado), su hilo se mata.

Si esto resulta no ser el problema, sigue leyendo ...

implementación de los tiempos de espera de Ruby es bastante ingenuo. Configura un hilo separado que duerme durante n segundos, luego levanta ciegamente una excepción de tiempo de espera dentro del hilo original.

Ahora, el código original en realidad podría estar en el medio de un bloque rescue o ensure. Al generar una excepción en dicho bloque se cancelará silenciosamente cualquier tipo de código de limpieza. Esto podría dejar el código que se agota en un estado incorrecto.

Es bastante difícil determinar si este es su problema exactamente, pero al ver cómo los manejadores de base de datos podrían hacer un poco de bloqueo y manejo de excepciones, podría ser muy probable. Here's an article that explains the issue in more depth.

¿Hay alguna forma de que pueda utilizar el manejo de tiempo de espera integrado de la biblioteca de su base de datos? Puede implementarse en un nivel inferior, sin utilizar la implementación de tiempo de espera de Ruby.

Una alternativa simple es programar las llamadas a la base de datos en un proceso separado. Puede bifurcar el proceso principal cada vez que realice una pesada carga de base de datos. O puede configurar un cronjob simple para ejecutar un script que lo ejecute. Esto será un poco más difícil si necesita comunicarse con su hilo principal. Por favor, deje algunos detalles más si desea algún consejo sobre qué opción puede satisfacer sus necesidades.


Según sus comentarios, el hilo está muriendo. Esto podría ser un error en las bibliotecas o en el código de la aplicación que puede o no ser capaz de corregir.Si desea atrapar cualquier error arbitrario que se genera por el código de gestión de base de datos y, posteriormente, volver a intentar, puede intentar algo como lo siguiente:

t = Thread.new do 
    loop do 
    sleep INTERVAL 
    begin 
     # Execute database queries and process data 
    rescue StandardError 
     # Log error or recover from error situation before retrying 
    end 
    end 
end 

También puede utilizar la palabra clave retry en el bloque rescue volver a intentar, pero es probable que deba mantener un contador para asegurarse de no volver a intentar accidentalmente de forma indefinida cuando se produce un error irrecuperable.

+0

Parece que de hecho está muriendo, aunque ahora no entiendo el error que estoy recibiendo: 'rescue in block in ': constante no inicializada Object :: Error (NameError)' –

+0

Parece que es de hecho muriendo, aunque ahora no entiendo el error que estoy recibiendo: 'rescue in block in ': constante no inicializada Object :: Error (NameError)'. Independientemente de eso, sin embargo, ¿cómo puedo manejar esto? Los datos que obtengo de la base de datos se carbonizan y se fusionan con otras cosas y luego los servicios lo llaman, por lo que me gustaría poder generar otra cadena idéntica si/cuando este hilo muere. Como siempre ocurre _después_ del primer tiempo de espera, también podría matar proactivamente el hilo en el manejador de tiempo de espera, de nuevo, si pudiera clonar el hilo en otra parte. –

+0

¡Es bueno saberlo! Sería mejor averiguar la causa de esta excepción y corregirla, pero puede ser suficiente para detectar esas excepciones y simplemente intentarlo de nuevo. La respuesta ha sido actualizada para explicar cómo. – molf

Cuestiones relacionadas