DataContext dictionary not thread safe?

Discussion of open issues, suggestions and bugs regarding LinqConnect – Devart's LINQ to SQL compatible ORM
Post Reply
edwin
Posts: 19
Joined: Thu 08 Oct 2009 09:15

DataContext dictionary not thread safe?

Post by edwin » Wed 31 Aug 2011 11:37

Hello again,

Using v.6.30.185 of "dcoraclepro", I sometimes get an exception, probably because the dictionary was modified while iterating it (threading issue?):

Code: Select all

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.ValueCollection.Enumerator.MoveNext()
   at Devart.Data.Linq.Provider.j.e()
   at Devart.Data.Linq.Provider.DataProvider.Dispose(Boolean disposing)
   at Devart.Data.Linq.DataContext.Dispose()
   at Jumbo.Juist.Models.AppDataContext.System.IDisposable.Dispose() 
   at Jumbo.Utilities.SessionDisposableObjectCache.UnregisterDisposableObject(IDisposable obj)
This occurs, as you can see in the stacktrace, when I dispose the DataContext.

Kind regards,

Edwin.

StanislavK
Devart Team
Posts: 1710
Joined: Thu 03 Dec 2009 10:48

Post by StanislavK » Fri 02 Sep 2011 14:50

Sorry for the delay. Could you please describe the situation in which this issue occurs? If possible, please send us a small project with which it can be reproduced.

edwin
Posts: 19
Joined: Thu 08 Oct 2009 09:15

Post by edwin » Fri 09 Sep 2011 15:35

Actually, it is quite complicated. It only occurs once in a while in our production system, so I can't send you a small test project.

Furthermore, For our MVC web application I have created several caches in which I store the DataContext. In our software, a single user Session has its own DataContext.

To cleanup after the users Session expires, I created a SessionDisposableObjectCache, which caches disposable objects and disposes them when their session has timed out, as a safety net. But in reality, you should call UnregisterDisposableObject as soon as the object isn't needed anymore, to free up resources.

Most of the time, we want the same DataContext throughout the request (so the View can still use it). Some of our webpage request can take quite long (its an internal line of business application), during which our users can open a new Internet Explorer tab. This will most likely start a new UseCase and create a new DataContext. This would mess up the old, still pending, request.

Therefor, we have another cache, the RequestDataContextCache, which caches the DataContext for at least the time of the request, so each request will get, through it's lifetime, the DataContext it started with, even if the user started another request.

The RequestDataContextCache is responsible for removing a DataContext that is associated with a request that doesn't exists anymore, when a new DataContext was requested. It must remove the DataContext by calling the SessionDisposableObjectCache.UnregisterDisposableObject, so the SessionDisposableObjectCache will not try to dispose the DataContext at the Session End. The SessionDisposableObjectCache.UnregisterDisposableObject disposes the DataContext by calling its IDisposable.Dispose() interface

I don't know if any of this has something to do with the exception. It seems to me that Devart.Data.Linq.Provider.j.e() accesses a Dictionary at the same time it is accessed somewhere else, without both accesses being synchronised using a lock {}.

Or maybe it is because the DataContext has already been disposed, although in general multiple times disposing works (as it should when conforming to the IDisposable contract).

Apart for the already quoted stacktrace, it also happens when a new DataContext is created and an DataContext should be disposed:

Code: Select all

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.ValueCollection.Enumerator.MoveNext()
   at Devart.Data.Linq.Provider.j.e()
   at Devart.Data.Linq.Provider.DataProvider.Dispose(Boolean disposing)
   at Devart.Data.Linq.DataContext.Dispose()
   at Jumbo.Juist.Models.AppDataContext.System.IDisposable.Dispose() in D:\Projects\JUIST\Juist\Juist\Models\AppDataContextPartial.cs:line 65
   at Jumbo.Utilities.SessionDisposableObjectCache.DisposeAndRemoveTimedOutObjects()
   at Jumbo.Utilities.SessionDisposableObjectCache.RegisterDisposableObject(IDisposable obj)
In general, the code in UnregisterDisposableObject is quite simple (removed try/catch):

Code: Select all

                obj.Dispose();
                lock (_lock)
                {
                    Remove(obj);
                }
Remove removes the object from the Dictionary cache. By the way, the object is stored in a WeakReference, so the cache isn't preventing it from being garbage collected.

Please let me know, if I can provide you with other information or help in any way.

Kind regards,

Edwin.

StanislavK
Devart Team
Posts: 1710
Joined: Thu 03 Dec 2009 10:48

Post by StanislavK » Wed 14 Sep 2011 14:17

Please contact us via email; please specify your license number in the letter. We will send you a test build which should help us in localizing the issue.

Post Reply