|
There are two queries that are in play in these examples and I want to be clear about which I'm talking about (this is already a confusing problem....I don't want to add confusion! :-)). The first query involved is the query that I generate and execute. The second is the query that actually gets saved in the query cache. (Perhaps there are more queries behind the scenes as DevForce does the constantization but I don't think they come into play so I'll ignore them)
The one that gets saved to the query cache should never have hard-links to a class instance....that is pretty much guaranteeing a memory leak. Also (a bit of a sidebar), I have a feeling that could compromise the query cache (I don't know all the details with how it's implemented so I don't know if it's actually a problem or not). For example, if the cached query is closing over some variable and then the variable changes after the query executes (maybe it's some global static variable that anybody can change), then the cache has now been compromised. I might execute something like em.Things.Where(t => t.Prop == Globals.TheString)....and when I first executed it, Globals.TheString has the value of "foo" but then I later change TheString to "bar". Now the query cache thinks that it's seen a query such as Things.Where(t => t.Prop == "bar").
<<end sidebar>>
The query that I build and execute is fine to have the closure since I only expect it to be short-lived.
In the examples above, I can see that the expression wrapper of the first query has a hard-link to my class. As I said, this is totally acceptable. And that is what you'd expect, right? When you asked me if I could reproduce this, where you talking about for this first query or if I could reproduce it for the query that made it into the query cache? I can't reproduce the cached query having a hard link. If I could, that would be great. However, it's trivial to reproduce the hard-link for the first query.
Here is the exact output of the EntityQuery._expressionWrapper.Expression when I look at it in the debugger after executing the query:
(note that I've switched my app to execute against the NorthwindIb so it's now "Region" instead of Menu)
{value(IdeaBlade.EntityModel.EntityQueryProxy`1[MyNamespace.Region]).Where(m => (m.RegionDescription == value(MyNamespace.MainPage+BigClass).SomeString))} |
You can see that it captured the BigClass instance via the closure.
Note, this expression wrapper is compiled (ExpressionWrapper.IsCompiled == true). And so _expressionWrapper._compiledFunc is non-null and also has a reference (via a closure) to the BigClass as well. In our original profiling from our real app, it is this hard-link in the _compiledFunc that we saw for sure when doing our memory profiling. The memory link went like this: EntityManager.QueryCache[21] --> ServerEntityQuery.UnderlyingEntityQuery --> EntityQuery<Region>._expressionWrapper --> ExpressionWrapper._compiledFunc --> closure --> [the big class]
Also, using a local variable (e.g. "X") for SomeString doesn't exactly solve the problem. The expression still closes over an outer variable. The only thing that changes is that the compiler auto-generates a class and then it's an instance of that class that gets a hard-link. In a simple case that might be fine since you are only leaking a small class.....but in a more realistic scenario, that can be even worse because then you end up having a memory leak for any other variables that may be closed over. For example, if I change DoAQuery, to the following I'll end up leaking a different BigClass instance:
public void DoAQuery(){ var em = new NorthwindIBEntities();
var otherBigClass = new BigClass("Number 2");
//Try to avoid closures by explicitly getting the value of SomeString. But this doesn't // avoid the closure...it just changes it. var x = SomeString; em.Regions .Where(m => m.RegionDescription == x) .ExecuteAsync(op => { //I'll reference 'otherBigClass' in the callback function which means it is also captured by a // closure. Now 'x' and 'otherBigClass' are both closed over so the compiler generates // one class to hold them. DevForce has a hard link because it cares about // 'x' but then that means we also leak otherBigClass :-( otherBigClass.DoSomethingElse(); }); } |
Thanks for the response.
|