Managed Properties and Memory Leak

Managed Properties and Memory Leak

Old forum URL: forums.lhotka.net/forums/t/10833.aspx


jconway posted on Monday, October 31, 2011

Recently we upgraded our code base to CSLA.NET 4.1. In doing so we introduced managed properties to most of our business objects. In particular:

 

 public static PropertyInfo<TextEncodingInfo> TextEncodingProperty = RegisterProperty<TextEncodingInfo>(c => c.TextEncoding);

public TextEncodingInfo TextEncoding
{
           get
           {

                if (!FieldManager.FieldExists(TextEncodingProperty))
                         LoadProperty(TextEncodingProperty, TextEncodingInfo.GetReadOnlyItem(TextEncodingId));                    return GetProperty(TextEncodingProperty);
           }
           set { SetProperty(TextEncodingProperty, value); }
}

Using a memory profiler we managed to track a memory leak to this property. What we had were a large number of TextEncodingInfo BOs in memory and not being garbage collected. The service memory usage continually grew until the service crashed.

So we changed this property to use a standard private backing field. The problem went away.

A few details:

Can anybody explain this behaviour? Why is this happening? Have i approached this in the wrong way?

RockfordLhotka replied on Monday, October 31, 2011

TextEncodingInfo is a child object? I don't know if this will help the memory issue, but the RegisterProperty call should be marking it as a child object so CSLA knows to treat it correctly.

geordiepaul replied on Tuesday, November 01, 2011

We tried your recommendation is this didn't fix the issue.

I created a simple test console app that invoked the factory method over and over again, then descoped the object. With the managed property the object hung around and the app eventually crashed. With a private backing field the memory use remained constant. The console app looked like this...

for (int i = 0; i < 10000; i++)
            {
                var email = Factory.GetObjectl(Id);
                var info = email.TextEncoding;
 
                Process proc = Process.GetCurrentProcess();
                Console.WriteLine("Memory use: {0}", proc.PrivateMemorySize64);
                
                //System.Threading.Thread.Sleep(100);
            }

It appear if the managed property references an object that remains in memory the root object also remains in memory. Technically our code is wrong as we're lazy loading and object that is already cached however does it highlight a potential bug in the CSLA?

geordiepaul replied on Tuesday, November 01, 2011

I have isolated the problem to a test application.....it's very crude but it does run out of memory using the managed backing field.

 

 

class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 1000000; i++)
            {
                var email = TestObject.Get();
 
                Process proc = Process.GetCurrentProcess();
                Console.WriteLine("Memory use: {0}", proc.PrivateMemorySize64);
                
                //System.Threading.Thread.Sleep(100);
            }
 
            Console.ReadLine();
        }
    }
 
    public class TestObject : BusinessBase<TestObject>
    {
        private TestObject()
        {
        }
 
        public static readonly PropertyInfo<CachedListItem> ItemProperty = RegisterProperty<CachedListItem>(c => c.Item, RelationshipTypes.Child);
        public CachedListItem Item
        {
            get { return GetProperty(ItemProperty); }
            private set { LoadProperty(ItemProperty, value); }
        }
 
        //private CachedListItem _item;
        //public CachedListItem Item
        //{
        //    get { return _item; }
        //    private set { _item = value; }
        //}
 
        public static readonly PropertyInfo<string> LongStringProperty = RegisterProperty<string>(c => c.LongString);
        public string LongString
        {
            get { return GetProperty(LongStringProperty); }
            private set { LoadProperty(LongStringProperty, value); }
        }
        
 
        public static TestObject Get()
        {
            return DataPortal.Fetch<TestObject>();
        }
 
        private void DataPortal_Fetch()
        {
            Item = CachedListItem.Get(1);
            LongString = new string('?', 512 * 1024);
        }
    }
 
    public class CachedList : ReadOnlyListBase<CachedListCachedListItem>
    {
        private CachedList()
        {
        }
 
 
        public CachedListItem GetItem(int Id)
        {
            return this.Where(i => i.Id == Id).SingleOrDefault();
        }
 
        private static CachedList _cache;
        public static CachedList Get()
        {
            if (_cache == null)
                _cache = DataPortal.Fetch<CachedList>();
            return _cache;
        }
 
        private void DataPortal_Fetch()
        {
            this.IsReadOnly = false;
            this.Add(new CachedListItem(1, "Name1"));
            this.Add(new CachedListItem(2, "Name2"));
            this.Add(new CachedListItem(3, "Name3"));
            this.Add(new CachedListItem(4, "Name4"));
            this.Add(new CachedListItem(5, "Name5"));
            this.IsReadOnly = true;
        }
    }
 
    public class CachedListItem : ReadOnlyBase<CachedListItem>
    {
 
        private CachedListItem()
        {
        }
 
        internal CachedListItem(int id, string name)
        {
            Id = id;
            Name = name;
        }
 
        public int Id { getprivate set; }
        public string Name { getprivate set; }
 
        public static CachedListItem Get(int id)
        {
            return CachedList.Get().GetItem(id);
        }
    }

 

RockfordLhotka replied on Tuesday, November 01, 2011

geordiepaul

It appear if the managed property references an object that remains in memory the root object also remains in memory. Technically our code is wrong as we're lazy loading and object that is already cached however does it highlight a potential bug in the CSLA?

How is that object cached?

CSLA does establish event handling between parent and child objects. It is absolutely not legal to cache a child object, especially in some static field or other permanent container (which is obviously what you'd do with a cache).

This is because the parent of a child does handle the child's collection/property changed event(s) to implement the ChildChanged event support.

This is not a bug in CSLA. CSLA assumes that an object graph is a self-contained concept, and that within an object graph (parent and children, grandchildren, etc) we can establish as many internal circular references as necessary to make the object graph work in a rich manner.

If you want to cache a child object, you should attach a clone of that object as a child of any object graph. The clone won't be maintained in memory by a static field, and so can be garbage collected.

Or you should implement a using relationship, so the "child" isn't part of the object graph at all, but is just used by the object graph. This is done using a private backing field that is marked as notundoable and nonserialized, and registerproperty would not mark it as a child.

geordiepaul replied on Tuesday, November 01, 2011

Thanks for the reply. That's cleared everything up.
Ultimate there was no need to cache the value and that's how we will fix the code. I just wanted to check the if the behaviour was expected.

The value was cached with a SQL Cache Dependancy but we've moved that to AppFabric now but I don't think the method matters in this case. We were using a managed property to retrieve and persist a value that was already cached. A simple non persisted non managed field would have sufficed in this case.

Thanks again. 

Copyright (c) Marimer LLC