MSTest multithreaded issue: Collection was modified; enumeration operation may not execute.

MSTest multithreaded issue: Collection was modified; enumeration operation may not execute.

Old forum URL: forums.lhotka.net/forums/t/11317.aspx


decius posted on Thursday, April 19, 2012

When I run my MSTests with multithreading enabled, I experience an erratic test smell. It always occurs during BusinessRules.CheckRules(); but I can't seem to figure out what's causing this for me. All of my business rules utilize the context.AddOutValue syntax, so I'm just not sure what my problem is. Can anyone help? Stacktrace below (just one example, it seems to occur on multiple business objects erratically). 

 

Test method Aps.SaasHr.Test.M3Migration.MigrantTest.ColumnMgrs_LazyOnOldTest threw exception: 

Csla.DataPortalException: DataPortal.Update failed (Csla.DataPortalException: ChildDataPortal.Create failed on the server ---> Csla.Reflection.CallMethodException: Child_Create method call failed ---> Csla.DataPortalException: ChildDataPortal.Fetch failed on the server ---> Csla.Reflection.CallMethodException: Child_Fetch method call failed ---> System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)

   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()

   at System.Collections.Generic.List`1.Enumerator.MoveNext()

   at System.Linq.Enumerable.WhereSelectListIterator`2.MoveNext()

   at System.Linq.Enumerable.<DistinctIterator>d__81`1.MoveNext()

   at Csla.Rules.BusinessRules.CheckRules()

   at Aps.SaasHr.Library.M3Migration.CalcCodeMap.Child_Fetch(String e, String dedType) in C:\Documents and Settings\markb\My Documents\Visual Studio 2010\Projects\Aps.SaasHr\Aps.Saas.Library\M3Migration\CalcCodeMap.cs:line 789

   at lambda_method(Closure , Object , Object[] )

   at Csla.Reflection.MethodCaller.CallMethod(Object obj, DynamicMethodHandle methodHandle, Boolean hasParameters, Object[] parameters)

   --- End of inner exception stack trace ---

JonnyBee replied on Friday, April 20, 2012

Which version of CSLA are you using?

Do you use async rules?

Are you able to create a test project that replicates the issue?
This would help us identify the problem.

 

decius replied on Friday, April 20, 2012

using version 4.3.2.0. I'll see if I can recreate the problem in a different project and post if I can. 

decius replied on Friday, April 20, 2012

Having trouble recreating this, so it's likely something funky with my class. No, I'm not using async rules, should I be?

decius replied on Friday, April 20, 2012

Okay, so it looks like this has SOMETHING to do with the ContextManager. I have this child object that's in a completely different part of the object graph, but it seems that its insert is causing issues. If I remove the SubmitChanges from this unrelated object, the erratic smell goes away. Is there anything that might not be threadsafe in the ContextManager? The code below belongs to a child in a child collection of the parent. The checkrules throwing exception is in a lazy-Syncronous child of the parent. Both perform data operations off the ContextManager.

I suppose in real life this isn't as much of a problem unless the app goes completely multithreaded, but it's really screwing with my tests, so it's quite annoying. 

This is all just a hunch, so I'll see if I can spin up a simple test that recreates this in a simple project.

 

 

private void Child_Insert(DefaultConfig parent)

{

using (var mgr = Csla.Data.ContextManager<DalLinq.M3MigrationDataContext>.GetManager("M3Migration"))

{

var data = new DalLinq.DefaultDetail()

{

DefaultConfigId = parent.Id,

DisplayText = DisplayText,

PropertyName = PropertyName,

IsSelected = IsSelected,

IsRequired = IsRequired

};

 

mgr.DataContext.DefaultDetails.InsertOnSubmit(data);

 

try

{

mgr.DataContext.SubmitChanges(ConflictMode.ContinueOnConflict);

}

catch (ChangeConflictException e)

{

foreach (ObjectChangeConflict occ in mgr.DataContext.ChangeConflicts)

{

// All database values overwrite current values with 

//values from database

occ.Resolve(RefreshMode.OverwriteCurrentValues);

}

}

 

LoadProperty(IdProperty, data.DefaultDetailId);

}

}

JonnyBee replied on Saturday, April 21, 2012

Hi,

Try changing the code into

using (var mgr = Csla.Data.ContextManager<DalLinq.M3MigrationDataContext>.GetManager(System.Guid.NewGuid().ToString("N")))

When you call SubmitChanges in a child method you should always make sure that the "ContextManager" is owned by your method.
IE: If any parent code on the same thread uses that named "ContextManager" you would submit more changes to the database than you might expect.


The preferred solution to load the ID property (assuming that this is part of a "larger" transaction) would be like this:

    private void Child_Insert(DefaultConfig parent)
    {
      using (var mgr = Csla.Data.ContextManager<DalLinq.M3MigrationDataContext>.GetManager("M3Migration"))
      {
        var data = new DalLinq.DefaultDetail()
        {
          DefaultConfigId = parent.Id,
          DisplayText = DisplayText,
          PropertyName = PropertyName,
          IsSelected = IsSelected,
          IsRequired = IsRequired
        };
        
        mgr.DataContext.DefaultDetails.InsertOnSubmit(data);
 
        data.PropertyChanged += (o, e) =>
                                  {
                                    if (e.PropertyName = "DefaultDetailId")
                                      LoadProperty(IdProperty, ((DefaultDetail) o).DefaultDetailId);
                                  };
      }
    }

RockfordLhotka replied on Sunday, April 22, 2012

The context dictionaries in ApplicationContext are stored, by default, in an AppDomain level singleton. This will cause issues with multithreaded unit test runners. You can overcome this by supplying your own context provider that uses thread local storage instead.

The provider is easy to write and use, just implement an interface and tell csla to use your type. I don't remember the interface type off the top of my head, but you can look at the ApplicationContext code to find it.

JonnyBee replied on Sunday, April 22, 2012

You must implement IContextManager and you testcode must set the

ApplicationContext.ContextManager (and)
ApplicationContext.WebContextManager (can be set to null)

decius replied on Monday, April 23, 2012

Thank you both very much! Exactly what I needed 

Copyright (c) Marimer LLC