Is UnitOfWork2 threadsafe?

Posts   
 
    
TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 09:02:37   

Hello

I have some strange behavior, let me try to explain.

I have a windows service which hold several objects (named Em3xx). These objects are used by some manager class hosted by the windows service, which handles operations on those objects. This is done on several threads (handled by threadpooling). But the objects theirselves are threadsafe, meaning that the manager class only handles 1 operation at a time for a single object.

The windows service is subscribed to an event of those Em3xx objects. The eventhandler receives some entitycollection used inside such Em3xx object which needs to be saved. The eventhandler in the windows service handles this. (This to avoid database access in business logic objects).

I sometimes have a very strange behavior and this makes me think that the UnitOfWork2 is not thread safe and it makes me think that it handles several threads at the same time.

I have added some logging in the eventhandler to know for which object the eventhandler is triggered and howmany dirty objects are inside the entitycollection. After some period of time I have following log entry: "dirty entities: 1 - saved : 2". This is logged for Em3xx with ID 117. At the very same time I also had a log entry in my trace file for another Em3xx object with ID 115, trying to save also 1 dirty entity. But this operation fails due to a OutOfSync exception. So the object used by Em3xx with ID 115 failed to be saved, but when I verify this in the database, the save operation was succeeded as my lastsaved audit column has changed.

This makes me think that the dirty entity of Em3xx with ID 115 was saved in the eventhandling of Em3xx with ID 117 (remember the saved numer of 2 records).

So I was wondering if the UnitOfWork2 could somehow combine these two save operations making my logic go wrong. I also wanted to know if this UnitOfWork2 is thread safe. I searched the forum but did not found anything about this.

I hope I explained everything, if not, please don't hesitate to request more info!

Kind regards, TomV

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 13-Apr-2010 10:32:07   

UoW is not thread safe.

Are you sharing the same UnitOfWork object which contain the same entities among threads?

Why don't you instantiate a different UoW for each thread?

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 10:36:38   

Walaa wrote:

UoW is not thread safe.

Are you sharing the same UnitOfWork object which contain the same entities among threads?

Why don't you instantiate a different UoW for each thread?

Hi Walaa,

In my eventhandler I have following code.


            #region Create unit of work
            UnitOfWork2 uow = new UnitOfWork2(DataServer.GetUow2WorkOrder());
            uow.AddCollectionForSave(e.Entities, true, true);
            if (e.Entities.RemovedEntitiesTracker != null)
               uow.AddCollectionForDelete(e.Entities.RemovedEntitiesTracker);
            #endregion

            ....

           //Save dirty entities
           saved = uow.Commit(DataServer.Adapter, true);


So when this eventhandler is used several times on several threads, I assume I have different instances of uow?

Regards, Tom

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 13-Apr-2010 11:01:55   

But the Em3xx is shared among threads, and so is the e.Entities passed to the eventHandler, am I correct?

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 11:09:03   

I have a manager object which is hosted by the windows service that holds a reference to 3 different Em3xx objects. The manager object gets the request to send data to those 3 Em3xx objects. This happens in several threads. So in every of the 3 threads I try to save an entitycollection but those collections have nothing in common. As this happens in 3 different threads, I assume I have 3 different eventhandlers. As I create the UnitOfWork in that eventhandler, it's not shared.

I see that the first save operation also saves the objects from the other threads... => dirty objects 1 => saved 3 confused

Here follows my live trace file from the customer his server


2010/04/13 10:56:46 Thread 3: Device (116 / EM3xx) - : Number of dirty entities != saved entities, here follows list of dirty entities: 14550,=> Dirties 1 != saved 3
StackFrames: HandleDatabaseRequest => OnDatabaseRequested => CurrentTransaction_Committing => OnCommitting => Commit => CommitTransaction => SetMessagesDelegate => SetMessagesDelegate => SetResourceDelegate => WriteResourceDelegate => Send => WriteResource => _InvokeMethodFast => InvokeMethodFast => Invoke => Invoke => CommunicateDetail => Communicate => WaitCallback_Context => Run => PerformWaitCallbackInternal => PerformWaitCallback => 
2010/04/13 10:56:46 Thread 3: Device (116 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 3
2010/04/13 10:56:46 Thread 17: Device (115 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 0 - entities to delete: 0
2010/04/13 10:56:46 Thread 16: Device (117 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 0 - entities to delete: 0

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 11:34:58   

Extra information:

The trace information from the above thread was AFTER I added a lock over all code in my eventhandler. The first execution of the eventhandler still saves 3 instances allthough only 1 entity is dirty but in the two other threads, the number of dirty entities is now 0.

Here follows the trace log from BEFORE the lock statement:


2010/04/13 10:44:44 Thread 13: Device (116 / EM3xx) - : Number of dirty entities != saved entities, here follows list of dirty entities: 14550,=> Dirties 1 != saved 3
StackFrames: HandleDatabaseRequest => OnDatabaseRequested => CurrentTransaction_Committing => OnCommitting => Commit => CommitTransaction => SetMessagesDelegate => SetMessagesDelegate => SetResourceDelegate => WriteResourceDelegate => Send => WriteResource => _InvokeMethodFast => InvokeMethodFast => Invoke => Invoke => CommunicateDetail => Communicate => WaitCallback_Context => Run => PerformWaitCallbackInternal => PerformWaitCallback => 
2010/04/13 10:44:44 Thread 13: Device (116 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 3
2010/04/13 10:44:44 Saved <= 0 => saving failed in database?
2010/04/13 10:44:44 Thread 4: Device (115 / EM3xx) - : Number of dirty entities != saved entities, here follows list of dirty entities: 14540,=> Dirties 1 != saved 0
StackFrames: HandleDatabaseRequest => OnDatabaseRequested => CurrentTransaction_Committing => OnCommitting => Commit => CommitTransaction => SetMessagesDelegate => SetMessagesDelegate => SetResourceDelegate => WriteResourceDelegate => Send => WriteResource => _InvokeMethodFast => InvokeMethodFast => Invoke => Invoke => CommunicateDetail => Communicate => WaitCallback_Context => Run => PerformWaitCallbackInternal => PerformWaitCallback => 
2010/04/13 10:44:44 Thread 4: Device (115 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 0
2010/04/13 10:44:44 Saved <= 0 => saving failed in database?
2010/04/13 10:44:44 Thread 15: Device (117 / EM3xx) - : Number of dirty entities != saved entities, here follows list of dirty entities: 13388,=> Dirties 1 != saved 0
StackFrames: HandleDatabaseRequest => OnDatabaseRequested => CurrentTransaction_Committing => OnCommitting => Commit => CommitTransaction => SetMessagesDelegate => SetMessagesDelegate => SetResourceDelegate => WriteResourceDelegate => Send => WriteResource => _InvokeMethodFast => InvokeMethodFast => Invoke => Invoke => CommunicateDetail => Communicate => WaitCallback_Context => Run => PerformWaitCallbackInternal => PerformWaitCallback => 
2010/04/13 10:44:44 Thread 15: Device (117 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 0


Important remark The first time this occurs, the 3 records are saved in the database. I can verify my lastchangeddate audit column in the database. The first entity collection with resides in Em3xx 1 is still valid. The entitycollections in Em3xx 2 and 3 have a dirty entity, but I can't save those collections because this results in a Concurrency exception. By this these entitycollections can't be used any longer as they contain dirty out of sync data. Do you understand what I mean.

By adding that lock, the collection seems to behave ok. You can see that in PREVIOUS trace for Em3xx 2 and 3 there are no dirty entities and so nothing has to be saved.

But I can't understand why the first occurence would save 3 records when only 1 dirty was present in the collection.

I hope you understand what I mean.

If not, don't hesitate to ask for more info!

Regards, TomV

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 13-Apr-2010 11:35:00   

So you are syaing that you only have 3 threads (no more), one for each Em3xx object, correct?

What about saving the UoW, are you using a shared DataAccessAdapter object for all threads?

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 11:41:29   

you are correct. Only 3 threads, one for each Em3xx object.

The saving part in the eventhandler of the windows service looks like this


               //Save dirty entities
               saved = uow.Commit(DataServer.Adapter, true);

Code behind the DataServer.Adapter is


      /// <summary>
      /// Gets access to the internal Adapter. This is only for internal usage for the communication code.
      /// </summary>
      public IDataAccessAdapter Adapter
      {
         get { return DataAccessFactory.GetAdapter(); }
      }

which is infact this code


      /// <summary>
      /// Creates an DataAccessAdapter for the correct database based on the connectionstring 'Galantis.Core.ConnectionString'.
      /// </summary>
      /// <returns>A <see cref="SD.LLBLGen.Pro.ORMSupportClasses.IDataAccessAdapter"/> IDataAccessAdapter</returns>
      /// <exception cref="EMDatabaseException">Thrown when no connection could be obtained to database</exception>
      public static IDataAccessAdapter GetAdapter()
      {
         try
         {
            ConnectionStringSettings set = ConfigurationManager.ConnectionStrings[GALANTIS_CONNECTION_STRING];

            switch (set.ProviderName)
            {
               case "SqlServer":
                  EMGroup.Galantis.Dal.Core.SQLServer.DatabaseSpecific.DataAccessAdapter adapter =
                     new EMGroup.Galantis.Dal.Core.SQLServer.DatabaseSpecific.DataAccessAdapter(set.ConnectionString);

                  #region Override CATALOGNAME
                   ... code for handling catalog override, not relevant
                  #endregion

                  adapter.CommandTimeOut = 300;
                  return adapter;

               case "Oracle":
                   ...

               default:
                  throw new ArgumentException("ProviderName isn't specified in connectionstring.");
            }
         }
         catch (Exception ex)
         {
            throw new EMDatabaseException("dbconnectionerror", ex);
         }
      }

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 13-Apr-2010 12:14:00   

Could you please post the stack trace of the OutOfSync exception?

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 12:19:44   

Since I added the locking things are looking better. Well, I don't get errors any longer, but the strange behavior is still present confused

The stack trace is not very clarifying to me, as it only says that the Commit method failed. And indeed, it's correct,

This is my trace logging when the last error occured at customer site:

  • first you see the first occurrence of the 1 dirty object and the saved == 3
  • at this point I printed the stackFrames
  • then you see some logings which don't have dirty entities (which is possible)
  • then you see an error for Em3xx with ID 117 because Em3xx with ID 116 saved an entity out of the wrong entitycollection.

From this point the collection inside Em3xx with ID 117 is useless...


2010/04/13 10:20:47 Thread 23: Device (116 / EM3xx) - : Number of dirty entities != saved entities, here follows list of dirty entities: 14570,=> Dirties 1 != saved 3
StackFrames: HandleDatabaseRequest => OnDatabaseRequested => CurrentTransaction_Committing => OnCommitting => Commit => CommitTransaction => SetMessagesDelegate => SetMessagesDelegate => SetResourceDelegate => WriteResourceDelegate => Send => WriteResource => _InvokeMethodFast => InvokeMethodFast => Invoke => Invoke => CommunicateDetail => Communicate => WaitCallback_Context => Run => PerformWaitCallbackInternal => PerformWaitCallback => 
2010/04/13 10:20:47 Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 3
2010/04/13 10:20:48 Memory - MemoryEm3xxMessageEntity - dirty entities: 0 - entities to delete: 0
2010/04/13 10:20:48 Memory - MemoryEm3xxMessageEntity - dirty entities: 0 - entities to delete: 0
2010/04/13 10:28:35 Memory - MemoryEm3xxResourceEntity - dirty entities: 0 - entities to delete: 0
2010/04/13 10:28:35 Memory - MemoryEm3xxResourceEntity - dirty entities: 0 - entities to delete: 0
2010/04/13 10:28:35 Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 1
2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) - Exception log is BEFORE the real trace log which is located in finally!
2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) - : During a save action an entity's update action failed. The entity which failed is enclosed.
   at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
   at EMGroup.Galantis.UI.CommunicationService.CommunicationServerHost.HandleDatabaseRequest(Object sender, DatabaseRequestEventArgs e)
2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) - Message   : During a save action an entity's update action failed. The entity which failed is enclosed.
Source   : SD.LLBLGen.Pro.ORMSupportClasses.NET20
TargetSite : Int32 Commit(SD.LLBLGen.Pro.ORMSupportClasses.IDataAccessAdapter, Boolean)
StackTrace :    at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
   at EMGroup.Galantis.UI.CommunicationService.CommunicationServerHost.HandleDatabaseRequest(Object sender, DatabaseRequestEventArgs e)

2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) - EntityWhichFailed: EMGroup.Galantis.BL.Core.EntityClasses.MemoryEm3xxMessageEntity
2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) -  BaseException: Message   : During a save action an entity's update action failed. The entity which failed is enclosed.
Source   : SD.LLBLGen.Pro.ORMSupportClasses.NET20
TargetSite : Int32 Commit(SD.LLBLGen.Pro.ORMSupportClasses.IDataAccessAdapter, Boolean)
StackTrace :    at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
   at EMGroup.Galantis.UI.CommunicationService.CommunicationServerHost.HandleDatabaseRequest(Object sender, DatabaseRequestEventArgs e)

2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) -  Exception; dump of sbDirties:

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 13-Apr-2010 12:26:43   

Imeant the stack of calls (method calls), you should dump the call stack when you receive an exception.

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 12:29:53   

When the error occurs I use following trace:

sb.AppendLine(tabs + "Message : " + ex.Message); sb.AppendLine(tabs + "Source : " + ex.Source); sb.AppendLine(tabs + "TargetSite : " + ex.TargetSite); sb.AppendLine(tabs + "StackTrace : " + ex.StackTrace);

Those things are available in my trace file, here follows a subset of that (but the stacktrace is not very explanatory)


During a save action an entity's update action failed. The entity which failed is enclosed.
at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
at EMGroup.Galantis.UI.CommunicationService.CommunicationServerHost.HandleDatabaseRequest(Object sender, DatabaseRequestEventArgs e)
2010/04/13 10:28:35 Thread 21: Device (117 / EM3xx) - Message   : During a save action an entity's update action failed. The entity which failed is enclosed.
Source   : SD.LLBLGen.Pro.ORMSupportClasses.NET20
TargetSite : Int32 Commit(SD.LLBLGen.Pro.ORMSupportClasses.IDataAccessAdapter, Boolean)
StackTrace :    at SD.LLBLGen.Pro.ORMSupportClasses.UnitOfWork2.Commit(IDataAccessAdapter adapterToUse, Boolean autoCommit)
at EMGroup.Galantis.UI.CommunicationService.CommunicationServerHost.HandleDatabaseRequest(Object sender, DatabaseRequestEventArgs e)


Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 13-Apr-2010 13:19:30   

Sorry I haven't seen the stack trace.

"During a save action an entity's update action failed. The entity which failed is enclosed"

This is an ORMConcurrencyException not an OutOfSync exception. This is thrown if the Update command fails.

Most probably the filter used in the Update statement was not met. So Either you are using a Concurrency Predicate which suggests the Entity was modified by another thread before this one attempts to update it. Or that another thread has deleted the entity before this one attempts to update it.

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 13:41:10   

Sorry about mixing concurrency and outOfSync.

As you say it's indeed the update statement that fails, most likely because the timestamp version column has a conflict.

Let me try to clarify once more:

The windows service hosts a object which has reference to those 3 Em3xx objects. In 3 separate threads things are done on those objects. When the operation is done, the Em3xx object throws an event to have the internal collection to be saved.

This event is handled by the manager object in the windows service. I see via my log tracing that the eventhandler is indeed 3 times called in 3 different kind of threads.

BUT the problem is that the first occurrence of the execution of the eventhandlers saves objects from the other threads. I can see that because the entity collection is passed via eventArgs and I print the count of the dirtyentities. In each threat an entitycollection with count == 1 is received. Here is the problem because when I do uow.commit the first time I receive that 3 entities are saved!!

The eventhandler which handles the other threads first says that indeed 1 dirty object is present, but nothing is saved: dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 0

This means that the dirty object is still present in the collection. I guess that the version timestamp is now different (because the first thread has changed the value in the database) and in following event handlings the collection can't be saved successfully any longer...

Now that I added the lock over all code in the eventhandler, the first execution of the eventhandler has again 1 dirty object, and saves mostly 2 objects (sometimes 3). On the second thread 0 dirty objects are received and obviously 0 are saved. And in the third thread 1 dirty object is received and 1 is saved (when first was 2 objects or 0 dirty and 0 saved when 3 were saved the first time).

Thus for now, my entitycollection is still usable, but I don't like that the first thread is saving 2 or 3 records. I want to know the exact reason. Is this maybe a bug in LLBLGen code or (more likely) am I doing something wrong cry

This is from the trace file from the customer


2010/04/13 13:17:30 Thread 18: Device (117 / EM3xx) - : Number of dirty entities != saved entities, here follows list of dirty entities: 13464,=> Dirties 1 != saved 3
StackFrames: HandleDatabaseRequest => OnDatabaseRequested => CurrentTransaction_Committing => OnCommitting => Commit => CommitTransaction => SetMessagesDelegate => SetMessagesDelegate => SetResourceDelegate => WriteResourceDelegate => Send => WriteResource => _InvokeMethodFast => InvokeMethodFast => Invoke => Invoke => CommunicateDetail => Communicate => WaitCallback_Context => runTryCode => ExecuteCodeWithGuaranteedCleanup => RunInternal => Run => PerformWaitCallbackInternal => PerformWaitCallback => 
2010/04/13 13:17:30 Thread 18: Device (117 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 1 - entities to delete: 0 (new: 0) - saved: 3
2010/04/13 13:17:30 Thread 21: Device (115 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 0 - entities to delete: 0
2010/04/13 13:17:30 Thread 14: Device (116 / EM3xx) - Memory - MemoryEm3xxMessageEntity - dirty entities: 0 - entities to delete: 0

Here you see that the first has saved 3 of them, and that the other thread don't have dirty entities

Regards, TomV

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 13-Apr-2010 15:57:09   

Update:

As this issue won't let me go I continued working on this code. I was also finally able to simulate on my own computer (up untill now it only occurred with 2 customers of us).

I looked at every line of code and then my eye dropped on this line:


               uow.AddCollectionForSave(e.Entities, true, true);

As I only want to save the first level objects in the entitiecollection I changed the recurse 'true' value into 'false' and the "error" did no longer occured.

When dirtyEntities return 1, is there a possibility to retrieve all dirty "child" records of that dirty entity. How far does this recurse saving goes?

It seems that the "I did it wrong part" is winning over the "llblgenpro bug" sunglasses

Best regards, TomV

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 13-Apr-2010 21:32:38   

Recursive saving traverses the entire graph, so all reachable entities which need saving will be saved, regardless of depth.

Matt

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 14-Apr-2010 08:41:49   

I understand that, but I don't see how the entitycollections of two different Em3xx objects can be linked to each other. I will check the uow.GetUpdateQueue() to see what entities will get saved to see why those objects are linked...

Is there any possibility to get the link between 2 objects in the graph. Because that queue will return two objects that are dirty, but I would like to know why they are in the same graph.

TomV

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 14-Apr-2010 09:34:21   

Is there a chance you are using a Context to keep unique instances of entities.

TomV
User
Posts: 76
Joined: 31-Jan-2008
# Posted on: 29-Sep-2010 15:51:33   

Very little chance that I get back to this as we no longer save recursive and thus it doesn't give any errors for now. Still it was very strange situation.

Thanks for the help!

TomV