A funny thing happened on the way to the OpenNTF Webinar

In preparation for this morning’s webinar on the OpenNTF Domino API, I spent some time writing some comparison code between the IBM supported API and the open source API. As I was working on this, it occurred to me just how hard properly coping with .recycle() truly is. The truth is that while you might be diligently recycling your lotus.domino objects where ever you create them, there are times that you might be creating them and you don’t even know it. Conversely, there are times when recycling your lotus.domino object is a terrible idea because of the side-effects of doing so. Here are some examples…


Let’s say you’re iterating some documents in an NSF looking for a particular string.

public lotus.domino.DocumentCollection getTermCollection(final lotus.domino.Database db, final String searchTerm) {
  lotus.domino.DocumentCollection allDocs = null;
  lotus.domino.Document curDoc = null;
  lotus.domino.Document nextDoc = null;
  lotus.domino.DocumentCollection result = null;
  try {
    result = db.createDocumentCollection();
    allDocs = db.getAllDocuments();
    curDoc = allDocs.getFirstDocument();
    while (curDoc != null) {
      nextDoc = allDocs.getNextDocument(curDoc);
      if (hasTerm(curDoc, searchTerm)) {
        result.addDocument(curDoc);
      }
      curDoc.recycle();
      curDoc = nextDoc;
    }
  } catch (lotus.domino.NotesException ne) {
    ne.printStackTrace();
  } finally {
    if (curDoc != null) {
      try {
        curDoc.recycle();
      } catch (lotus.domino.NotesException ne) {
        // do what, then?
      }
    }
    if (nextDoc != null) {
      try {
        nextDoc.recycle();
      } catch (lotus.domino.NotesException ne) {
        // do what, then?
      }
    }
    if (allDocs != null) {
      try {
        allDocs.recycle();
      } catch (lotus.domino.NotesException ne) {
        // do what, then?
      }
    }
  }
  return result;
}
 
public boolean hasTerm(final lotus.domino.Document doc, final String searchTerm) {
  boolean result = false;
  try {
    java.util.Vector<?> items = doc.getItems();
    for (Object o : items) {
      lotus.domino.Item item = (lotus.domino.Item) o;
      try {
        java.util.Vector<?> values = item.getValues();
        for (Object vo : values) {
          if (vo != null && vo instanceof String) {
            if (((String) vo).equalsIgnoreCase(searchTerm)) {
              item.recycle(); // don't forget to recycle if we're going to break the loop and return early!
              return true; // now we can return safely
            }
          }
        }
      } catch (lotus.domino.NotesException ne1) {
        // yes, this can happen. There's no guarantee that the Java API can read the contents of every item successfully.
      }
      item.recycle(); // we're good green citizens!
    }
  } catch (lotus.domino.NotesException ne) {
    ne.printStackTrace();
  }
  return result;
}

The first thing most of you are probably thinking is “ARGH!!! YUCK!! Look at all that defensive framing code. It’s confusing as hell!” And yes, I agree that’s a problem. But I want to look closely at the hasTerm() method. You’ll notice that it gets all the Items and iterates over them, looking at all the values and iterating over them. If it encounters the term we’re looking for in a text item, it returns true so that the Document is added to our collection.

(Please don’t respond with a comment explaining that I should use a different method to search for a term, or that I left out Rich Text or MIME contents… this is a concept demonstration, not a coding contest.)

There are two basic problems with this routine: 1) what happens if we encounter the term in the 2nd Item on a Document with 200 Items? 2) what happens when we get to the $Revisions item on each Document?

The first situation isn’t TOO bad. Because the calling routine will .recycle the Document that was being searched, that will in turn .recycle all the Items that were parented to that Document. So our 198 remaining Item handles will still be .recycled.

But the second situation is a bit more complicated. When we get to the $Revisions item, we get a Vector of DateTime objects. Because these aren’t Strings, we pay no attention to them and simply move along in our code. After all, stuff that’s parented to the Document will be .recycled along with the Document, right?

Ah, but no… DateTime objects aren’t parented to the Document. They are parented to the Session. So if we get the Vector of values out of the Item and then don’t end up using them, then all those DateTime objects are left dangling off our Session. If the average Document has 10 entries in $Revisions, we’ll run out of object handles in 32-bit Domino somewhere Document 15,000 (assuming there aren’t any other date/time fields on them, of course.)

As I was writing this example, something occurred to me: The existing API provides a pretty reasonable way to deal with this, but I have never seen anyone use it anywhere.

There’s an overloaded signature for .recycle() that accepts a Vector parameter. Calling Item.recycle(Vector) doesn’t .recycle the Item, it just .recycles whatever is in the Vector. It turns out this is a very useful in a new version of hasTerm…

public boolean hasTerm2(final lotus.domino.Document doc, final String searchTerm) {
    boolean result = false;
    try {
      java.util.Vector<?> items = doc.getItems();
      for (Object o : items) {
        lotus.domino.Item item = (lotus.domino.Item) o;
        try {
          java.util.Vector<?> values = item.getValues();
          for (Object vo : values) {
            if (vo != null && vo instanceof String) {
              if (((String) vo).equalsIgnoreCase(searchTerm)) {
                item.recycle(); // don't forget to recycle if we're going to break the loop and return early!
                // Wait... we didn't recycle all the other items in the Vector!
                // ah! What we really need to do is...
                doc.recycle(items);
                // oh wait... the values too! (See below)
                doc.recycle(values);
                return true; // now we can return safely
              }
            }
          }
          // but what if our Vector has DateTime or DateRange objects? We need...
          item.recycle(values);
        } catch (lotus.domino.NotesException ne1) {
          // yes, this can happen. There's no guarantee that the Java API can read the contents of every item successfully.
        }
        item.recycle(); // we're good green citizens!
      }
    } catch (lotus.domino.NotesException ne) {
      ne.printStackTrace();
    }
    return result;
  }

There, now we’re safe. Please note that you’d also want to use this approach with the results of ViewEntry.getColumnValues() as well.

Of course, there are other situations where you really should NOT .recycle objects when you think you’re done with them. This has to do with the hierarchical structure of the object handles and the fact that calls to the same underlying NSF object return the same handles to that object, even in two different places. To demonstrate the problem with this, let’s look at simple routine…

public void setProcessedDateOld(final lotus.domino.Document doc, final java.util.Date date) {
  lotus.domino.Database db = null;
  lotus.domino.Session session = null;
  try {
    db = doc.getParentDatabase();
    session = db.getParent();
    lotus.domino.DateTime datetime = session.createDateTime(date);
    try {
      doc.replaceItemValue("processDate", datetime);
    } catch (lotus.domino.NotesException ne1) {
      ne1.printStackTrace();      // Once again, not what you actually want to do here.
    } finally {
      datetime.recycle();
    }
  } catch (lotus.domino.NotesException ne) {
    ne.printStackTrace();
  } finally {               // Apply our standard finally block recycling here
    if (db != null) {
      try {
        db.recycle();       // Uh oh...
      } catch (lotus.domino.NotesException ne) {
        ne.printStackTrace();
      }
    }
    if (session != null) {
      try {
        session.recycle();      // No, wait! I need that to live!
      } catch (lotus.domino.NotesException ne) {
        ne.printStackTrace();
      }
    }
    // Recycling the PARENTS of the argument is super-bad! We shouldn't be using our standard pattern here!
    // Welcome to Dante's ninth circle
  }
}

If we follow our standard pattern of .recycling objects we create when we’re finished with them, this code is guaranteed to cause problems. In order to properly manage .recycle, we have to be intimately aware of the hierarchy that Lotus put into place for these objects. That’s not too difficult for those of us with decades of experience on the platform, but for anyone who’s new to this API, it’s going to be positively baffling.

I hope these examples illustrate why it’s so challenging to avoid handle leaks with lotus.domino. If you’d really like to stop worrying about these things, just start using the org.openntf.domino API, where we handle garbage collection and defensive framing for you. The original term search process looks like this with the new API…

public org.openntf.domino.DocumentCollection getTermCollectionNew(org.openntf.domino.Database db, String searchTerm) {
    org.openntf.domino.DocumentCollection result = db.createDocumentCollection();
    for (org.openntf.domino.Document doc : db.getAllDocuments()) {
      for (org.openntf.domino.Item item : doc.getItems()) {
        if (item.getValues().contains(searchTerm)) {
          result.add(doc);
          break;
        }
      }
    }
    return result;
  }

Get it? So get it!

Advertisements
Posted in Uncategorized
One comment on “A funny thing happened on the way to the OpenNTF Webinar
  1. Hi Nathan

    Good lecture…. 🙂

    I think you may have added another example of the undesired recycles in your first example:

    if (hasTerm(curDoc, searchTerm)) {
    result.addDocument(curDoc);
    }
    curDoc.recycle();

    … depending on what you want to use the result documentcollection for after the loop.

    Would the recycle of curDoc not invalidate the document just put in the collection?

    /John

Take the red pill.

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: