2
votes

In our SL4 application built on Caliburn.Micro, we've come across a (another) memory leak.

Stripped out, it seems that the problem is caused by ItemsControl with custom DataTemplate bound to an IEnumerable collection of objects that implement INotifyPropertyChanged interface.

When the source collection is changed (another collection is assigned to the ViewModel's property that the ItemsControl's ItemsSource is bound to), the entites in the original collection and bound DataTemplates are not garbage collected. Although the event handling of NotifyPropertyChanged seems to be done internally via WeakReference, it is like SL is keeping another reference to these objects. So every time we refresh data from the server, memory consumption increases.

Do you have any idea how to solve this problem? I really cannot understand how can this kind of bug happen in SL4!

Some experiments suggested that calling ItemsControl.Items.Clear() could help. Any tip how to simply call this every time the ItemsSource is changed? The only thing that comes to my mind would be to override ItemsSourceProperty and add a handler there.

EDIT: It turned out that the leak occurs in this situation:

  • load entities through RIA services context and store a collection of them in a property of viewmodel
  • bind a listview with custom data template to the property with collection of entites
  • refresh the entites via RIA services context

What happens is that although the entities get refreshed, which can be seen in the view, memory consumption is rising.

If there is no binding, refreshing the entities does not eat more memory (it might but the memory consumption level eventually returns back as GC does its job).

If you clear the context or simply create a new one, the memory is also eventually collected.

It seems that the problem is connected with RIA services.

I can provide a simple project that shows the problem if you want.

UPDATE: The memory leak seems to be caused by INotifyDataErrorInfo. Read here.

3
Thanks for alerting me to this problem! Do you know if it happens in WPF?Ed Ayers
Not yet. But I'll try to prepare simple demo for both WPF and SL to reproduce the issue so we'll see. Do you have the same problem? So far it seems that even RIA might be somehow affecting the problem...gius
Just to confirm: you are testing this in release mode without a debugger attached, right?Kent Boogaart
@Kent It does not matter. Release or debug mode, with or without debugger attached, the leak is still there.gius

3 Answers

2
votes

UPDATE:

Silverlight 4 Service Release to fix Memory Leaks: http://timheuer.com/blog/archive/2010/09/01/silverlight-service-release-september-2010-gdr1.aspx

Silverlight 4 has known memory leak issues with data templates. There is a fix on the way it's currently being tested.

Here is a thread I have been following:

The user "heuertk" is a Microsoft Silverlight Developer....he explains the issues and the status of the fix...

http://forums.silverlight.net/forums/t/171739.aspx

1
votes

It is my understanding that this particular issue is a bug in Silverlight 4 itself. However, you say you've come across another memory leak. Did you determine if that one was related to Caliburn.Micro and if so, have you posted about it in the projects forums? If the fault is in CM, I'd like to try and fix that. Thanks.

1
votes

Hey, i was playing around with your solution a bit, i came up with this workaround which works pretty well, basically you change your DataSrc class to the following, the only problem is how to apply something like that on Ria Services entity. I'm already using T4 for adjusting the code generation (for minor things) but i'm not sure if i could force something like this inbetween. Also, i wanted to ask, did you report your issue with your sample application? Extra reports never hurt:)

    public class DataSrc  :INotifyDataErrorInfo
{
    public string Name { get; set; }

    public IEnumerable GetErrors(string propertyName)
    {
        yield break;
    }

    public void InvokeEvent()
    {
        _weakLinkErrorChanged.OnEvent(this, new DataErrorsChangedEventArgs("Name"));
    }

    private WeakLinkErrorChanged<DataSrc, object> _weakLinkErrorChanged;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged
    {
        add 
        { 
             _weakLinkErrorChanged=new WeakLinkErrorChanged<DataSrc, object>(this);
            _weakLinkErrorChanged.ErrorsChanged += value;
        }

        remove { _weakLinkErrorChanged.ErrorsChanged -= value; }
    }

    public bool HasErrors
    {
        get { return false; }
    }
}


internal class WeakLinkErrorChanged<TInstance, TSource> where TInstance : class
{

    private readonly WeakReference _weakInstance;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public WeakLinkErrorChanged(TInstance instance)
    {
        if (instance == null)
        {
            throw new ArgumentNullException("instance");
        }

        _weakInstance = new WeakReference(instance);
    }

    public void OnEvent(TSource source, DataErrorsChangedEventArgs eventArgs)
    {
        var target = _weakInstance.Target as TInstance;

        if (target != null)
        {
            if(ErrorsChanged!=null)
                ErrorsChanged(target, eventArgs);
        }
        else
            ErrorsChanged = null;
    }
}