4

Closed

Memory leak in case of usage an external connection

description

EF6 has a memory leak in some specific situations. Please look at following code.
            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();

                using (var transaction = connection.BeginTransaction())
                {
                    // need to use a lot of small updates instead of one update, because the count of affected records is very big and MemoryOverflow exception can be thrown
                    for (int i = 0; i < 100; i++)
                    {
                        using (var context = new DbContext(connection, false))
                        {
                            context.Database.UseTransaction(transaction);

                            // some updates and inserts

                            context.SaveChanges();

                            context.Database.UseTransaction(null);
                        }

                        // there is a memory leak.
                        // all disposed context are stayed in the memory
                        // the cause of this behavior is attached event handler for the event StateChange on connection
                    }

                    transaction.Commit();
                }
            }
If DbContext is created with external connection, then such context will not be disposed, because EntityConnection attached an event handler to the StateChange event in the external connection and this handler is not detached during disposing of DbContext. Therefore GC cannot collect this instance of context.

I use following workaround for this situation.
        private static void ContextDisposingWorkaround(DbContext context)
        {
            var connection = context.Database.Connection;
            var objectContext = ((IObjectContextAdapter) saveContext).ObjectContext;

            ReflectionHelper.RemoveEventHandlers(typeof (DbConnection), "StateChange", "_stateChangeEventHandler", connection,
                objectContext.Connection);
        }
Need to call above method before disposing of context.
Closed Dec 12, 2013 at 12:37 AM by jemartti

comments

jimmyzimms wrote Nov 8, 2013 at 2:08 PM

I'll vote yet on this as the dispose method SHOULD disconnect any eventhandlers par course. However it's important to note that the leak only should last as long as the connection hangs around.

lajones wrote Nov 15, 2013 at 11:03 PM

Fixed with #8af8e25.

ObjectContext was not calling Dispose() on its EntityConnection because DbCompiledModel (which was creating both of them) was not telling ObjectContext that it owned the EntityConnection. Updated so it did tell it that plus needed to update the EntityConnection Dispose() which was assuming that if it was being disposed then it should close the underlying store connection regardless of whether it "owned" that connection.

Fix all that and added a test to check that the event subscription no longer exists once the context (and hence it's EntityConnection) have been disposed.

vkarabuva wrote Nov 18, 2013 at 1:17 PM

I just tested 6.0.2-nightly-21117 and it hasn't fixed the issue.

If I explicitly dispose the EntityConnection that was passed into the DbContext constructor after disposing the context itself, ie:

var entityConnection = ((IObjectContextAdapter)dbContext).ObjectContext.Connection;
dbContext.Dispose();
entityConnection.Dispose();

Then the memory is freed. If i skip the entityConnection.Dipose() then it is not, and the memory usage grows as each new dbConext is constructed.

lajones wrote Nov 18, 2013 at 5:13 PM

@vkarabuva - could you include the full example please? I have a test which does the example posted above and for me the EntityConnection is disposed when the DbContext is disposed i.e. at the end of its using statement. So either my fix did not get into the build (I can see no evidence of this in the build logs - they all look fine) or we have a slightly different setup.

vkarabuva wrote Nov 19, 2013 at 12:54 AM

I believe your fix did make it in, as the _stateChangeEventHandler is now null after the DbContext is disposed, previously this was not the case.

But the EntityConnection is definately not disposed when the DbContext is. Other than my observation of memory usage, I'm basing this on the fact that certain properties on the EntityConnection (eg ConnectionString) are populated following dbContext.Dispose(), but then cleared after I make the explicit call to entityConnection.Dipose().

Unfortunately I cannot send my entire source, but I suspect that you are correct that our setups are different.

The setup we're using is somewhat perculiar. We have a single DbConnection in this scenario, each time we iterate we create a new EntityConnection based on this DbConnection:

var entityConnection = new EntityConnection(MetadataWorkspace, dbConnection))

then contstruct a new DbContext using the EntityConnection, eg

new DbContext(entityConnection)

at the end of the iteration the dbConext is disposed (as is the EntityConnection explicitly as a workaround), but the DbConnection lives on.

The reason we do this is so that we can do all this using a single SqlConnection there it can be done transactionally without the need for DTC.

I hope this explanation helps.

lajones wrote Nov 19, 2013 at 5:50 PM

@vkarabuva You're correct that one of the last things the EntityConnection does in Dispose() is set the connection string to empty.

Underlying DbContext is an ObjectContext and there is a flag (previously called _createdConnection, with my fix now called _contextOwnsConnection) which controls when ObjectContext is disposed whether it calls Dispose on it's underlying EntityConnection.

DbContext doesn't have a constructor that takes just an EntityConnection - so my suspicion is you're actually calling YourContext(entityConnection). This constructor is then probably calling base(entityConnection, false). The 2nd parameter sets whether the context owns the connection. Since you're supplying a new EntityConnection each time and you want it disposed I suspect you need to set the 2nd parameter to true.

The EntityConnection also has a flag saying whether it should dispose its underlying store connection. You want this set to false. But fortunately the default setting for the constructor you're using sets it to false anyway. There is another constructor where you get to set that flag explicitly if you prefer.

Also on another thread I recently sent out some example code on how to use EntityConnections and local transactions. It's not in the public docs yet but if you email me I can send you that example. Contact me at lajones at Microsoft dot com.

Mugdhak wrote Nov 20, 2013 at 5:38 PM

I tested to make sure the DbContext dispose not calls entity connection dispose and then disposes the entity connection if owned by the dbcontext. Please reopen, if you think this is still not fixed for you.

** Closed by Mugdhak 11/20/2013 10:38AM

vkarabuva wrote Nov 25, 2013 at 1:00 AM

@lajones You're correct. We are using DbContext(entityConnectio, false), which would explain the connection not being disposed. I'm emailing you now regarding that transaction code. Thank you.

ajcvickers wrote Dec 3, 2013 at 8:07 PM

Reactivating for 6.0.2 per conversation with Rowan and Diego since Glimpse has reported issues. There may not be anything that needs to be fixed, but if this change ends up breaking apps that were working before even if they were doing something wrong then we should consider if that is acceptable. The bottom line is that we should at least understand what is causing the Glimpse issues before we release 6.0.2.

lajones wrote Dec 9, 2013 at 10:21 PM

Found that in LazyInternalContext.DisposeContext() we use to dispose the InternalConnection before the underlying ObjectContext.

For non-EF connection strings the InternalConnection is responsible for disposing the underlying connection. But ObjectContext disposes EntityConnection and, per the fix earlier in this issue, when EntityConnection is disposed it now unsubscribes itself from the underlying connection's StateChange event.

So the order was wrong - we were calling Dispose on the InternalConnection and through it the underlying connection and only after that would ObjectContext.Dispose() be called and EntityConnection would attempt to unsubscribe.

Fixed with #eded319 by reversing the order of the two Dispose() calls.

jemartti wrote Dec 12, 2013 at 12:37 AM

Glimpse has reported that Lawrence's fix has resolved their issue. I've also verified that the original situation is still resolved.