10 ene 2011

Los Zombies

Comienza a caer la noche. Estas cómoda y tranquilamente sentado en tu escritorio, ejecutando una trivial prueba de una funcionalidad que acabas de terminar. Tu último compañero acaba de salir, lo miras de reojo mientras abandona la oficina. En ese momento te das cuenta que quedas completamente solo. De pronto lo presientes, te sobrecoge una sensación extraña. Te re acomodas en tu silla, miras a tu alrededor, suspiras y te dices "no pasa nada". Te enfocas de nuevo en el monitor y das el siguiente enter para finalizar el test y... entonces sucede: salta atropelladamente desde tu pantalla directo a tus ojos. Un Zombie!
Ok, un poco dramático, pero esta basado en una historia de la vida real. Hace un tiempo se me presentó el siguiente error

This SqlTransaction has completed; it is no longer usable. at System.Data.SqlClient.SqlTransaction.ZombieCheck() at System.Data.SqlClient.SqlTransaction.Rollback()

Un Zombie molestándome a estas alturas del partido y haciendo un Rollback
Resulta que esto se produce porque en algún punto de la transacción que estamos llevando a cabo; o finalizó satisfactoriamente o ya hicimos un rollbak (o sucedió algún otro fenómeno "inexplicable"). Posteriormente, y muy probablemente debido a un fallo en nuestra implementación (quizás en el manejo de errores), volvemos a invocar el rollback sobre la transacción que, como dijimos antes, por una u otra razón ya fue finalizada.
Nuestra implementación del Rollback probablemente fuese similar esto:
    trn = con.BeginTransaction();
    try
    {
         ...
         //Lógica de la transacción
         ...
         trn.Commit();
    }
    catch(Exception ex)
    {
         ...
         //instrumentalización del error
         ...
         trn.Rollback();
    }

Si la transacción ya ha sido finalizada (por alguna situación en la lógica de la misma) e invocamos la linea trn.Rollback();, entonces se producirá el error en cuestión. Para paliar este tipo de posibles situaciones, la recomendación de Microsoft es simplemente ubicar un try/catch para la ejecución del rollback:
    trn = con.BeginTransaction();
    try
    {
         ...
         //Logica de la transaccion
         ...
         trn.Commit()
    }
    catch(Exception ex)
    {
         ...
         //instrumentalización del error de la transacción
         ...
         try
         {
              transaction.Rollback();
         }
         catch (Exception ex2)
         {
              ...
              //instrumentalización del error del Rollback
              ...
         }
    }
Como aprendí no es tan difícil lidiar con zombies... Al menos por el momento...
Aquí un link con información adicional