Nathan Evans' Nemesis of the Moment

Memory leaks with an infinite-lifetime instance of MarshalByRefObject

Posted in .NET Framework, Software Design by Nathan B. Evans on April 17, 2011

Recently we discovered an issue with the way our product performs AppDomain sandboxing. We were leaking small amounts of memory, quite badly, from each sandbox and every sandbox operation ever created over the lifetime of the process. After much investigation using CLR Profiler, it transpired that our subclasses of MarshalByRefObject which were using “null” as the return from their InitializeLifetimeService() will cause a permanent reference to be held open by some fairly low level area in the .NET remoting stack, specifically System.Runtime.Remoting.ServerIdentity (which is marked internal). This was preventing any of our object(s) derived from MarshalByRefObject from being garbage collected, and thus the memory footprint would keep growing and growing. Fortunately we run our servers with rather huge page files so it never got to a point where customers were affected, but obviously it was something we needed to fix fairly urgently.

After playing around a lot with all the lifetime services stuff like the ISponsor interface to do sponsorship with nasty hacky timeout values etc (which would have had side-affects for our product, but which we were about to resign ourselves too!) we came across a much better alternative solution in the form of RemotingServices.Disconnect(). Hoorah. Is it just me or does the whole remoting story in .NET need a damn good overhaul? Cross-AppDomain communications deserves something better.

With this discovery, I came up with a useful class that improves MarshalByRefObject  by adding deterministic disposal of both itself and any such nested objects (which is more an implementation detail for us but I’m sure could be useful for anyone).

/// <summary>
/// Enables access to objects across application domain boundaries.
/// This type differs from <see cref="MarshalByRefObject"/> by ensuring that the
/// service lifetime is managed deterministically by the consumer.
/// </summary>
public abstract class CrossAppDomainObject : MarshalByRefObject, IDisposable {

    private bool _disposed; 

    /// <summary>
    /// Gets an enumeration of nested <see cref="MarshalByRefObject"/> objects.
    /// </summary>
    protected virtual IEnumerable<MarshalByRefObject> NestedMarshalByRefObjects {
        get { yield break; }
    }

    ~CrossAppDomainObject() {
        Dispose(false);
    }

    /// <summary>
    /// Disconnects the remoting channel(s) of this object and all nested objects.
    /// </summary>
    private void Disconnect() {
        RemotingServices.Disconnect(this);

        foreach (var tmp in NestedMarshalByRefObjects)
            RemotingServices.Disconnect(tmp);
    }

    public sealed override object InitializeLifetimeService() {
        //
        // Returning null designates an infinite non-expiring lease.
        // We must therefore ensure that RemotingServices.Disconnect() is called when
        // it's no longer needed otherwise there will be a memory leak.
        //
        return null;
    }

    public void Dispose() {
        GC.SuppressFinalize(this);
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing) {
        if (_disposed)
            return;

        Disconnect();
        _disposed = true;
    }

}

It was then just a case of modifying a few of our classes to derive from this instead of MarshalByRefObject and then update a couple other locations in our codebase by ensuring that Dispose() was called during clean-up.

7 Responses

Subscribe to comments with RSS.

  1. Jabe said, on July 23, 2011 at 11:12 PM

    I am in exactly the same situation: AppDomain isolation, communication using MarshalByRefObject, memory leaks. Communication is pretty simple, so I only had to modify a few places, yet a lot of objects are instantiated causing a nice big leak over time. I already went through SO MUCH trouble with MarshalByRefObject…
    My tests showed a significant reduction in memory usage… Thank you so much!

  2. Hamish Gunn said, on August 11, 2011 at 12:47 PM

    Nathan – thanks for this. We’d like to use this in production code. Could you mail me and I can make a more formal request. Thanks.

    • Nathan Evans said, on December 7, 2011 at 2:15 PM

      Go ahead and use it. This code is public domain.
      🙂

      • Vesela said, on December 10, 2013 at 1:29 PM

        Thank you for this solution. There is something that I don’t understand indeed: Dispose(bool disposing) – why do you need this ?

  3. Dozi said, on January 10, 2012 at 5:58 AM

    public void Dispose() {
    Dispose(true);
    GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
    if (!_disposed)
    {
    if (disposing)
    {
    Disconnect();
    }
    _disposed = true;
    }
    }

    ~CrossAppDomainObject()
    {
    Dispose(false);
    }

  4. […] 从MarshalByRefObject继承的对象销毁时将会在跨域环境中引起内存泄露。程序域必须被free来释放所有的资源。你应该从CrossAppDomainObject类继承,而不是MarshalByRefObject。 Nathan B. Evans编写了CrossAppDomainObject类并将其公开在 https://nbevans.wordpress.com/2011/04/17/memory-leaks-with-an-infinite-lifetime-instance-of-marshalby&#8230; […]

  5. Elvis Ligu said, on November 13, 2016 at 11:55 AM

    Thank you very much. You saved me several hours of research working with ILeasing and ISponsor.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: