Eric J. Smith's Weblog

CodeSmith, .NET, etc...

Help Needed! PropertyGrid and Remoted Objects

Alright, I am having a really hard time trying to solve an issue in CodeSmith and I am hoping that you guys can help me out.

The basic problem is that currently every time you compile a template in CodeSmith it creates a dynamic assembly which is then loaded into the current AppDomain and the template class inside of that assembly is instantiated and attached to the property grid control so that the template properties can be set before execution of the template.  As you might imagine, over time all of these dynamic assemblies being loaded into the AppDomain and never unloaded (can't unload an assembly) is causing a memory leak in CodeSmith and locks all of the referenced assemblies until CodeSmith is shut down.

The obvious answer to this problem is to move load and execution of templates into a separate AppDomain that can be unloaded, right?  Well, I've run into some issues with that approach that I am so far unable to resolve.  A remoted object can't be used in the property grid control.  I've tried implementing ICustomTypeDescriptor thinking that would be the answer that I was looking for, but I was unsuccessful.  There are a couple other ideas that I am tossing around, but I'm not happy with any of them.

If anyone out there knows how to solve this issue, I would be extremely grateful for a little nudge in the right direction.  I will be offering some free CodeSmith Professional licenses to the person(s) that help me solve this problem.

Thanks,
Eric J. Smith

Posted: May 06 2004, 10:22 AM by ejsmith | with 12 comment(s)
Filed under: ,

Comments

Metro Sauper said:

Eric,

I do not fully appreciate the problem you are having. However, it seems like you need some type of execution proxy that takes the values from the properties collection and then starts the template in another AppDomain and passes the values to it.

Best regards,

Metro.
# May 6, 2004 1:42 PM

Jason G said:

Metro,

The problem is: How do you get the list of properties?

Unless the type/assembly is in your domain, then the PropertyGrid can't get the list of properties.

And then, you have to deal with serializing/remoting values back to the other domain.

# May 6, 2004 9:36 PM

Philip Laureano said:

If you can't attach the property grid to a proxied object and you can only attach it to a local (i.e. same appdomain) object, then the solution is to add a layer of indirection by putting the state of your templates (such as the properties) in a local class that has a reference to the remoted template, and then have the property grid attach to the local class itself. If the property grid references the local class instead of the remoted template, then it will behave as if the remoted template were a local object. Everything can be solved by adding a layer of indirection, Eric. :)

p.s. I'm currently working on a code generator very similar to CodeSmith and in it I've already solved such issues such as subtemplates, and batch templates, so if you want to swap a few notes, let me know. :)

HTH

-Phil
# May 7, 2004 2:06 AM

Philip Laureano (marttub _ATsymbol_ hotmailDOTcom) said:

One more thing. I just realized that there is one slight problem to that previous approach I mentioned--it doesn't scale very well because the you would have to create a local 'proxy' state class for each template that you need to attach to the property grid--so, in short, it doesn't scale very well. However, another approach to get around this is to dynamically create these proxies at runtime using either Reflection.Emit, or your own code generation engine. The slight trade-off, of course, is that instead of having multiple assemblies loaded into the same app domain, you'll have a bunch of value-typed state classes that will be floating around in memory. The memory footprint of these state classes, however, (which I'd assume to be simple structs) would be orders of magnitude less than having a full-blown assembly in the same appdomain, so that slight tradeoff (IMHO) is well worth it. :)
# May 7, 2004 5:03 AM

Eric J. Smith said:

Priority #1 has to be to avoid loading dynamic assemblies into the current AppDomain because from my experience it seems that having a ton of loaded assemblies in the current AppDomain is not just a memory leak, but it also really starts to slow down the current AppDomain. I like the path that Jason has pointed me toward (http://www.ericjsmith.net/codesmith/forum/?f=11&m=3494) in the forums and I really hope that if I continue down that path it will work out.

Also, priority #2 has to be that a custom property author should not have to jump through hoops to get their object to work in CodeSmith. They should just be able to create an object, a designer, and make the object XML serializable to be able to fully work with CodeSmith. Currently I fall short in the XML serialization part, but I intend to correct that.

Please keep this discussion going and post more ideas. They are very helpful.

Thanks,
Eric J. Smith
# May 7, 2004 11:40 AM

Philip Laureano said:

Another idea--I just figured this out over the weekend--how about putting the UI (the property grid) and the templates in the same (external) appdomain so that you won't have to make any changes to the property grid code at all?

Now, at first, this sounds like we're just moving the problem from the default appdomain to an external appdomain, but, in fact, there's a very, very interesting factor about this that I haven't mentioned just yet--MarshalByRefObject. The most interesting thing about MarshalByRefObject is (as I'm sure you're probably aware) the fact that you can actually determine the lifetime of each object by deriving from MarshalByRefObj and overriding its InitializeLifetimeService method and providing your own, as with this example from MSDN:

public override Object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
}
return lease;
}

So the trick here is to derive your templates from MBR and set their lifetimes to a very short lifetime (i.e. 30 seconds) so that if they're not being used, they'll quickly be disposed of by the garbage collector. The next question you might be asking is "If its only alive for say...30 seconds, then how do I wire it up to a property grid and make it useful?"

That's where the ISponsor interface comes in.
To extend the lifetime of any MBR-derived object, the only thing you have to do is register it with an object that implements that interface (there's a default implementation in the .NET framework called clientsponsor) and the sponsor will keep the object alive so long as it is registered with the sponsor. Once you unregister it and its lease expires, it'll be marked for garbage collection, and (eventually) it will be freed by the GC. This might sound a little complicated, but, in fact, its no more than a few lines of code (in addition to overriding InitializeLifetimeService from above).

In practice, when you wire up the template with the property grid (which shouldn't be a problem since they're in the same appdomain), all you have to do is do something like the following pseudo code:

#1. create your template object
#2. hook it up to the property grid,
#3. create a sponsor and register the template object with that sponsor so it doesn't die while being connected to the property grid
#4. let the user change a few properties
#5. (user clicks on 'run' and the template executes)
#6. unregister the template object from the sponsor, and let it expire.

In this scenario, you won't have to worry about changing much of your code because the property grid and the template are in the same app domain. At the same time, you won't have to worry about memory issues because #1) these are performed in a separate appdomain and #2) since you're deriving your templates from MBR and giving it a very short lifespan (i.e. 10 seconds is what i have), you can immediately reclaim resources (if the templates are not being used or cached) by letting the objects expire. What do you think, Eric?
# May 11, 2004 12:58 AM

TrackBack said:

# June 1, 2004 1:53 AM

S Harrison said:

Eric
I don't know if this will help but I spotted it while looking at a utility called FXCop which mentioned something called Introspection and not locking assemblies.
http://www.gotdotnet.com/team/fxcop look at the readme. It states 'We no longer load assemblies via Reflection into the primary AppDomain and release all locks when FxCop is sent to the background'.


# June 10, 2004 11:14 AM

Philip Laureano said:

Hi Eric,

Did you ever manage to solve the AppDomain issue?
I thought of (yet another, heh) approach to this problem: How about remoting both the property grid and the template into the same (yet separate from the default) appdomain? Templates will still work the same way since they're not being remoted to the property grid (because they're in the same appdomain), and you won't have to worry about memory issues anymore because you can just simply unload the appdomain that held both the property grid and the template once you're done. That way, you won't even have to worry about having to write any type descriptors, etc. What do you think?


# June 15, 2004 7:11 AM

Eric J. Smith said:

As far as I know, you can't remote a UI control and have it rendered in a seperate AppDomain.
# June 15, 2004 12:01 PM

Philip Laureano said:

Well, if you can't remote a UI control, then just remote the entire form that it's on. :)

For example, I noticed that in the freeware version of CodeSmith you have two forms: The template explorer to display the list of templates, and another form that holds the property grid and runs/compiles the actual template. How about loading the second form into another appdomain? All you have to do is create the form in a remote appdomain and unload that appdomain once you're done and you can instantly reclaim the memory it used. It's simple, effective, and (IMHO) the changes you'd have to make to your existing code base are minimal. :)

Oh, and one more thing. You can remote *any* windows form control because all UI controls in .NET are derived from System.Windows.Forms.Control--which, in turn, is derived from MarshalByRefObject. Happy coding. :)

HTH

-Phil
# June 16, 2004 8:55 PM

Memmorium said:

     Good idea!

P.S. A U realy girl?

# April 11, 2008 10:36 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)