Search code examples
asp.net-mvc-3asynccontroller

Remove event listener from async controller on timeout


I have a problem with a MVC3 asynccontroller, When the controller asyc method runs I increment the asyncontroller and also starts to listen to backednd events

AsyncManager.OutstandingOperations.Increment();
eventAggregator.Subscribe(this);

And then when the backend event fires I decrement and unsubscribes to backend events

AsyncManager.OutstandingOperations.Decrement(); 
eventAggregator.Unsubscribe(this);

This works, but if the controller timeouts the eventAggregator will hold a subscription to the controller (Its a Weakrefence list but for somereason the old controller is not removed) and when a backend event triggers the old controller will steal the event first and dequeue the events meaning when the real controller gets the message the queue is empty and nothing is sent to the view, also this is a memory leak so we will have lots of controllers in the Weakreference list afer a couple of minutes... How can i unsubscribe the controller from the eventaggregator when it timesout?

edit: Code for the event aggregator, we have unit tests for this code that proves there are no memory leaks. The strange thing is that if I create a empty standard controller without any references to anything its deconstructor is not run either... We use IoC (Ninject) can that be the problem?

public class EventAggregator : IEventAggregator
{
    private readonly IConfig config;
    private readonly WeakReferenceList<object> subscribers = new WeakReferenceList<object>(); 

    public EventAggregator(IConfig config)
    {
        this.config = config;
    }

    public void Subscribe(object subsriber)
    {
        subscribers.Add(subsriber);
    }

    public void Unsubscribe(object subscriber)
    {
        subscribers.Remove(subscriber);
    }

    public void Publish<T>(T message) where T : class
    {
        var lookupType = typeof (IHandle<T>);

        if (config.EnableEventAggregator)
            subscribers.Where(lookupType.IsInstanceOfType)
            .Select(s => s as IHandle<T>)
            .ForEach(s => s.Handle(message));
    }
}

public class WeakReferenceList<T> : ICollection<T> where T : class
{
    private readonly List<WeakReference> list = new List<WeakReference>(); 

    public IEnumerator<T> GetEnumerator()
    {
        return GetAlive().Select(item => item.Target as T).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Add(T item)
    {
        CleanDeadReferences();
        list.Add(new WeakReference(item));
    }

    public void Clear()
    {
        list.Clear();
    }

    public bool Contains(T item)
    {
        return GetAlive().Any(weakItem => weakItem.Target.Equals(item));
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public bool Remove(T item)
    {
        CleanDeadReferences();
        return list.RemoveAll(weakItem => weakItem.Target.Equals(item)) > 0;
    }

    public int Count
    {
        get { return GetAlive().Count(); }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public int IndexOf(T item)
    {
        var weakItem = list.First(x => x.Target == item);
        return list.IndexOf(weakItem);
    }

    public void Insert(int index, T item)
    {
        CleanDeadReferences();
        list.Insert(index, new WeakReference(item));
    }

    private IEnumerable<WeakReference> GetAlive()
    {
        return list.ToList().Where(item => item.IsAlive);
    }

    private void CleanDeadReferences()
    {
        list.RemoveAll(item => !item.IsAlive);
    }
}

Solution

  • You could override the EndExecute method:

    protected override void EndExecute(IAsyncResult asyncResult)
    {
        try
        {
            base.EndExecute(asyncResult);
        }
        catch(TimeoutException ex)
        {
            // Clean up your references here
        }
    }
    

    And to define the timeout period you could use the [AsyncTimeout] attribute.