Cracking down on heap abuse (part 1)

Last week during the Mono Summit, we discovered a few memory and performance issues. Apparently every System.Type returned by System.Reflection.Assembly.GetTypes() is effectively leaked, never to be GCed. Making this call on assemblies with many types can be quite a detrimental operation (such as reflecting against mscorlib).

Update: I knew I’d get corrected on the above statement. By “leaked” I apparently mean that System.Type is just retained forever by the runtime. It’s a special structure that must be manually managed by the runtime, and is allocated in a special way.

Joe looked into Beagle’s usage of Assembly.GetTypes() and managed to reduce leakage on the heap by a whopping 7MB simply by only reflecting against Beagle assemblies (vs. all currently loaded in the app domain, including mscorlib).

On the flight back from Boston on Friday, I decided to look into the issue in Banshee as well. In a few places, I was making the offending call against Banshee.Base (looking for custom branding implementations and Banshee.IO backend implementations). Our plugin system was also making the call on plugin assemblies to actually find plugins, and taglib-sharp was using it to find format implementations in the assembly. I was able to replace the call very easily with a static type table in a few cases where the types I’m looking for are already known at compile time, but it does take some of the “elegance” out of the design.

The plugins case is different, where you don’t know types until they’re found via reflection at runtime (using Assembly.GetTypes()). Miguel and I discussed a solution for this, and it’s working out well. Basically, each plugin assembly should provide a known static class with a known static method that returns an array of the types that should be loaded:

public static class PluginModuleEntry
    public static Type [] GetTypes()
        return new Type [] {

If this implementation cannot be found in the assembly, Banshee’s plugin factory will fall back on the Assembly.GetTypes() method so older plugins are of course still compatible, but plugin authors are encouraged to drop this code in their assemblies to keep heap abuse down.

Now, in terms of the actual problem (Assembly.GetTypes() leaking to begin with), I’m not sure what that’s about or if/when it will be fixed. Nevertheless, Beagle uses 7MB less memory (I hope that’s right), and Banshee uses 1MB less memory when we avoid the calls.