Avoiding memory leaks when using the .Net SDK

Update

For an update on disposal in HPRM 8.2 see my latest post.

Update for 9.0

It used to be the case that code that accessed the Database object of a TrimMainObject (as seen below) would be problematic.  This is no longer the case.  The only instance of the Database object that requires Disposal is the on you instantiate and connect directly.

Console.WriteLine(myRecord.Database.CurrentUser.SortName);

Intro

Just when I start feeling complacent and good about myself as a careful/professional programmer is when I am likely to make a silly mistake or overlook some seemingly innocent code in a code review.  Working in a .Net language (typically C#) I am much safer than those who spend their lives in C++ yet using a library that contains unmanaged code (in this case the HPRM .Net SDK) provides the ideal opportunity to embarrass myself.

The HPRM .Net SDK implements the Dispose interface on almost every object in the SDK.  For example the follow code will compile:

    Location location = new Location(database, "a person");
    location.Dispose();

This is a redundant artefact of an early version of the SDK, most of these Dispose methods do nothing and need not be called.  Not having to Dispose every single object makes .Net SDK much simpler yet there are important exceptions to this 'never dispose' rule.

What must I Dispose?

There are three objects that must always be disposed to avoid memory leaks, these are:

  • Database,
  • TrimMainObjectSearch, and
  • DocumentStream.

Disposal patterns

The two best ways to Dispose are using either the 'using' statement or the try/finally pattern.  Both of these ensure that your object will be disposed even if execution terminates abnormally, for example as the result of an error. 

Using

The using statement sets a scope for the object and ensures it is disposed before going out of scope.  The Database can be instantiated in this was as seen in the below example.

    using (Database database = new Database())
    {
        database.Id = "G1";
        database.Connect();

        // do something
    }

Try/Finally

The try/finally pattern allows you to perform multiple actions at the end of the block and thus gives you a little more flexibility, it also allows you to include a 'catch' block to catch any exceptions.  The following example shows a try/finally block used to dispose both the Database and the TrimMainObjectSearch.

    Database database = null;
    TrimMainObjectSearch search = null;

    try
    {
        database = new Database();
        database.Id = "G1";
        database.Connect();

        search = new TrimMainObjectSearch(database, BaseObjectTypes.Record);
        search.SetSearchString("unkUpdated:today");

        foreach (Record record in search)
        {
          Console.WriteLine(record.Title);
        }
    }
    finally
    {
        if (search != null)
        {
            search.Dispose();
            search = null;
        }
        
        if (database != null)
        {
            database.Dispose();
            database = null;
        }
      }
    }

Order of Disposal

The Database object must always be the last object disposed.  This allows other objects to unregister themselves from the Database while it is still available.

Tracking Leaks

The .Net SDK provides facilities to track whether your code is disposing objects correctly, this is the TrimApplication Leak Tracking.  It is probably a good idea to put code similar to the example below in an '#if debug' block somewhere in your application, maybe just before it exits.  You may choose to log or assert the results.  The leak traces will return a stack trace to help you locate the object that has not been Disposed.

Leak Tracing Example

The following example is a console application but sets itself up as a server side (web service style) application.

    class Program
    {
        static void getDatabase()
        {
            Database database = new Database();
            database.Id = "F1";
            database.WorkgroupServerName = "local";
            database.Connect();
        }

        static void Main(string[] args)
        {

            TrimApplication.TrimBinariesLoadPath = @"C:\Program Files\Hewlett-Packard\HP Records Manager";
            TrimApplication.Initialize();
            TrimApplication.SetAsWebService(@"D:\junk");
            TrimApplication.EnableSdkLeakTracking();

            getDatabase();

            if (TrimApplication.GetSdkLeakCount() > 0)
            {
                var leakTraces = TrimApplication.GetSdkLeakStackTraces();
                foreach (int leakNo in leakTraces.Keys)
                {
                    Console.WriteLine(leakTraces[leakNo]);
                }
            }
        }
    }

Futures

The next major version after 8.11 will have important changes in this space, stay tuned (or post a comment) for more details.