Consulta por DeadLock con TransactionScope - WCF - Workflow Foundation - Asp.net

110 views
Skip to first unread message

Hernan Garat

unread,
Nov 22, 2011, 9:30:33 AM11/22/11
to AltNet-Argentina
Estimados,

Nos encontramos con un problema al implementar TransactionScope en
nuestra aplicacion con el fin de minimizar los riesgos de
inconsistencia en las distintas aplicaciones que se comunican via WCF.

El sistema que nos encontramos desarrollando consta de los siguientes
componentes y pasos de ejecucion:

1. En una Aplicacion (Aplicacion A) el usuario carga solicitudes de
compra.
2. Al Aceptar la Solicitud, esta misma via WCF se comunica con un
Motor de Workflow (Aplicacion B) a la cual se le solicita la Creacion
de una Instancia de Workflow.
3. La aplicacion B realiza la creacion de la instancia y contesta con
exito.
4. La aplicacion A guarda luego de recibir la respuesta exitosa, todos
los datos de la solicitud.
5. Luego la Aplicacion B al procesar la instancia creada responde a la
Aplicacion A con una llamada de WCF.
6. La Aplicacion A realiza los Inserts correspondientes (4 Inserts)
7. La Transaccion hace commit y luego se cierra.

El problema del deadlock ocurre en el paso 6 , cuando se procesan 2 o
mas Solicitudes en Paralelo (con una solicitud finaliza con exito).

El TransactionScope se inicia en el BeginRequest y Finaliza en el
EndRequest donde hace commit de los cambios.

Las pruebas que se realizar fueron:

En el Profiler se analizaron los procesos que dan el DeadLock, el cual
se da siempre en el primer Insert que tiene que realizar en el punto 6
Se creo una pequeña aplicacion con los inserts que se encuentran
generando el dead lock encerrados en un TransactionScope, y se
ejecutaron 2 instancias de la aplicacion en simultaneo.
Se realizaron los inserts dentro de una transaccion pero esta vez
desde el ManagementStudio
No se pudo replicar el Problema en ninguna de las pruebas.

Por ultimo se logro ejecutar de manera aislada el paso 6 (cerrando
todas las conexiones anteriores), desde una aplicacion externa que
reemplaza a la Aplicacion B, y se pudo verificar que el error sigue
ocurriendo, y que ninguna de las operaciones anteriores al punto 6 son
culpables del deadlock.

Se aceptan todos los salvavidas, paracaidas y demas yerbas.

Saludos!

Angel Java Lopez

unread,
Nov 22, 2011, 9:52:53 AM11/22/11
to altnet-a...@googlegroups.com
Hola gente!

Hmmmm... Hernan, primer gran pequenia pregunta:
- Cual es el sintoma cuando ocurre el problema? Da exception? Se queda colgado A y B? Se queda colgado B? Fallan los dos? Falla uno? Como falla? Mensaje?

No queda claro por que lo llaman DeadLock, no deberia aparecer un Deadlock en transacciones.

Nos leemos!

Angel "Java" Lopez


2011/11/22 Hernan Garat <garat....@gmail.com>

Hernan Garat

unread,
Nov 22, 2011, 10:06:03 AM11/22/11
to altnet-a...@googlegroups.com
Angel, te comento...

Cuando ocurre el error, da la Exception que deje abajo de todo en color azul.

El error ocurre del lado de A, es el que lanza la Exception, B por las pruebas realizadas no tendría ningún tipo de culpa.

Desde ya muchas gracias por contestar!

Saludos!

System.Data.SqlClient.SqlException (0x80131904): Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader()
at System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult)
at System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries)
at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
at System.Data.Linq.ChangeDirector.StandardChangeDirector.DynamicInsert(TrackedObject item)
at System.Data.Linq.ChangeDirector.StandardChangeDirector.Insert(TrackedObject item)
at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges()
at SanWF.Common.Linq.Context.ContextGroupManager.SubmitChanges() in C:\Santillana\repo\workflows\SanWF\SanWF\SanWF.Common\Linq\Context\ContextGroupManager.cs:line 204
at SanWF.Negocio.Comun.DespuesNotificarCambio(InstanciaInfo instanciaInfo) in C:\Santillana\repo\workflows\solucion\reutilizable\negocio\SanWF.Negocio\Comun.cs:line 606
at SanWF.Soluciones.Compras.Negocio.ComunEspecifico.DespuesNotificarCambio(InstanciaInfo instanciaInfo) in C:\Santillana\repo\workflows\solucion\no-reutilizable\SanWF.Soluciones\SanWF.Soluciones.Compras\SanWF.Soluciones.Compras.Negocio\ComunEspecifico.cs:line 118
at SanWF.Negocio.Comun.ProcesarNotificacion(InstanciaInfo instanciaInfo, Int64 idNotificacion) in C:\Santillana\repo\workflows\solucion\reutilizable\negocio\SanWF.Negocio\Comun.cs:line 544
at SanWF.Negocio.ComunServices.NotificarCambio(InstanciaInfo instanciaInfo, Int64 idNotificacion) in C:\Santillana\repo\workflows\solucion\reutilizable\negocio\SanWF.Negocio\ComunServices.cs:line 335 stackTrace





--
Atte.



 Web     www.hgarat.com.ar
 Tel    1530164748


       
      

Leonardo Micheloni

unread,
Nov 22, 2011, 10:22:14 AM11/22/11
to altnet-a...@googlegroups.com
Una pregunta, el transaction scope encierra todos los pasos? porque si es así la transacción debe ser muy larga, dependiendo del nivel de aislación y de el modelo de datos no sería tan raro un deadlock

2011/11/22 Hernan Garat <garat....@gmail.com>



--
Leonardo Micheloni
@leomicheloni

Hernan Garat

unread,
Nov 22, 2011, 10:31:50 AM11/22/11
to altnet-a...@googlegroups.com
Agrego un dato, acabamos de replicar el deadlock en el managment studio... 

El dato que no teniamos era el select que se realizaba antes del insert.
El isolation level al usar transaction scope pasa a ser Serializable.

Lo que se ejecuta es...

Por un lado una de las solicitudes realiza lo siguiente:

USE PRESUPUESTO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
select * from SanWF.historia where guid = 'CC92BC50-085E-4E61-8ABF-B9C7A5A7DA48'
exec sp_executesql N'INSERT INTO [SanWF].[Historia]([guid], [tarea], [usuario], [inicio], [final], [accion], [automatico]) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',N'@p0 uniqueidentifier,@p1 varchar(8000),@p2 varchar(8000),@p3 datetime,@p4 datetime,@p5 varchar(8000),@p6 bit',@p0='CC92BC50-085E-4E61-8ABF-B9C7A5A7DA48',@p1='Validar solicitud',@p2=NULL,@p3='2011-11-18 16:58:43:667',@p4=NULL,@p5=NULL,@p6=0

Por otro lado, la solicitud en paralelo realiza esto:

use presupuesto
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
begin tran
select * from SanWF.historia where guid = '71E422E4-E717-4F48-A282-3BB95285C784'
exec sp_executesql N'INSERT INTO [SanWF].[Historia]([guid], [tarea], [usuario], [inicio], [final], [accion], [automatico]) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value]',N'@p0 uniqueidentifier,@p1 varchar(8000),@p2 varchar(8000),@p3 datetime,@p4 datetime,@p5 varchar(8000),@p6 bit',@p0='71E422E4-E717-4F48-A282-3BB95285C784',@p1='Validar solicitud',@p2=NULL,@p3='2011-11-18 16:58:43:590',@p4=NULL,@p5=NULL,@p6=0

Ejecutando esto en paralelo, es suficiente para reproducir el deadlock....

El cambio que se realizo en la aplicación es utilizar el transactionScope seteando el isolation level en ReadCommitted

El Deadlock no se esta produciendo mas....

Saludos y gracias!

Hernan Garat

unread,
Nov 22, 2011, 8:42:11 PM11/22/11
to altnet-a...@googlegroups.com
Estimados,

Para comentar sobre el problema con el que nos encontramos, les paso la siguiente información ya que explica el porque del deadlock que nos estaba ocurriendo.
En el anterior mail comente exactamente lo que se ejecutaba, y el problema en si era el isolation level que se encuentra por default.

Les paso una parte de la información que se encuentra en msdn que habla exactamente sobre lo que nos estaba ocurriendo...

But the choice of Serializable as the default isolation level much worse.  In SQL Server SERIALIZABLE transactions are rarely useful and extremely deadlock-prone.  Put another way, when the default READ COMMITTED isolation level does not provide the right isolation semantics, SERIALIZABLE is rarely any better and often introduces severe blocking and deadlocking problems.  And since the TransactionScope is the recommended way to manage transactions in .NET, its default constructor is setting up SQL Server applications to be deadlock-prone.  In fact I was prompted to write this post after working with some customers who were getting deadlocks in their applciation, and who had no idea that they were running transactions under the SERIALIZABLE isolation level.

So please, copy this C# code:

Fuente: http://blogs.msdn.com/b/dbrowne/archive/2010/06/03/using-new-transactionscope-considered-harmful.aspx 

Ademas agrego información extra... Saludos a todos!

Setting the TransactionScope isolation level

Some of the overloaded constructors of TransactionScope accept a structure of type TransactionOptions to specify an isolation level, in addition to a timeout value. By default, the transaction executes with isolation level set to Serializable. Selecting an isolation level other than Serializable is commonly used for read-intensive systems. This requires a solid understanding of transaction processing theory and the semantics of the transaction itself, the concurrency issues involved, and the consequences for system consistency.

In addition, not all resource managers support all levels of isolation, and they may elect to take part in the transaction at a higher level than the one configured.

Every isolation level besides Serializable is susceptible to inconsistency resulting from other transactions accessing the same information. The difference between the different isolation levels is in the way read and write locks are used. A lock can be held only when the transaction accesses the data in the resource manager, or it can be held until the transaction is committed or aborted. The former is better for throughput, the latter for consistency. The two kinds of locks and the two kinds of operations (read/write) give four basic isolation levels. See IsolationLevelfor more information.

When using nested TransactionScope objects, all nested scopes must be configured to use exactly the same isolation level if they want to join the ambient transaction. If a nestedTransactionScope object tries to join the ambient transaction yet it specifies a different isolation level, an ArgumentException is thrown.

Angel Java Lopez

unread,
Nov 23, 2011, 4:23:53 AM11/23/11
to altnet-a...@googlegroups.com
Gracias Hernan por el detalle de todo!

Ah, ahora entendi! Pense que habia DeadLock en el programa, que se quedaba colgado algo.

Una implementacion ideal de Serializable seria: se atienden transacciones de a una. Pero como se persigue la concurrencia, las implementaciones tratan de ejecutar las que vengan, en "paralelo".

Hmmm... dejenme visitar algunos temas relacionados ;-)

Curiosamente, el articulo de Oracle descarta que Serializable para ellos tenga Deadlocks (o asi lo entiendo yo):

Coding serializable transactions requires extra work by the application developer to check for the Cannot serialize access error and to undo and retry the transaction. Similar extra coding is needed in other database management systems to manage deadlocks

Noten lo de "other". Para ellos (la gente de Oracle), da "Cannot serialize access". Es como lo hubiera implementado yo: no detectar DeadLocks (abrazos mortales, entiendo), sino que T1 cambia row1.data1 y T2 trata de usar ese valor o trata de modificarlo. Es decir, no se basa en el lockeo, sino en estar "aware" de que cosas se fueron cambiando en T1 cuando T2 esta corriendo y viceversa. El articulo sigue mas adelante:

Application developers should take into account the cost of rolling back and retrying transactions when using serializable mode. As with read-locking systems, where deadlocks occur frequently, use of serializable mode requires rolling back the work done by terminated transactions and retrying them. In a high contention environment, this activity can use significant resources.

Es decir (sigo con mi interpretacion) en el Serializable de Oracle no usan locks (como en read-locking systems, ellos no son sistemas de ese tipo, por lo menos para Serializable), sino que detectan que dos transacciones se superponen en algun row1.data1.

Alguien de Oracle por aca?

Curiosamente, hay implementaciones de transacciones serializables en objetos en memoria. Esta el concepto de Software Transactional Memory. 

http://en.wikipedia.org/wiki/Software_transactional_memory

In computer sciencesoftware transactional memory (STM) is a concurrency control mechanism analogous to database transactions for controlling access to shared memory in concurrent computing. It is an alternative to lock-based synchronization. A transaction in this context is a piece of code that executes a series of reads and writes to shared memory. These reads and writes logically occur at a single instant in time; intermediate states are not visible to other (successful) transactions.


La implementacion de Clojure 

por ejemplo, no se basa en lockeos, sino que (me parece) es mas tipo Oracle: maneja versiones, detecta la superposicion de acceso, mas que manejar lockeos y detectar dead locks. Y tienen un operador que ejecuta todo en una transaccion Y SI FALLA (por concurrencia con otra) automaticamente trata de nuevo y de nuevo y asi.... Y estan chochos los de Clojure con eso ;-)

Mis pinitos en el tema:

;-)
2011/11/22 Hernan Garat <garat....@gmail.com>

Carlos Peix

unread,
Nov 23, 2011, 4:24:09 AM11/23/11
to altnet-a...@googlegroups.com
Gracias por postear la solucion Hernan

----------------------------------
Carlos Peix

2011/11/22 Hernan Garat <garat....@gmail.com>

Reply all
Reply to author
Forward
0 new messages