Saturday, January 13, 2007

How can you raise an "Out of Memory" exception when you don't have any?

Or, “How to take a simple problem and create a complex solution.“

In the early days of developing Delphi we pondered that very question.  How can you allocated an exception object on the heap when the memory manager has already just told you “Hey the memory's not here man.”?  The answer to that turned out to be remarkably easy and once we realized that, there was a big “DUH!” moment on the team.  Before I reveal the ultimate solution, let's talk about the stops along the way.  I want to highlight this early solution to demonstrate that simplicity is very often the right approach.  If you find yourself layering ever more complicated logic and architecture to solve some rare edge condition case, maybe you should first stop.  (the first step in getting yourself out of a hole is to stop digging ;-).

The original approach to this problem involved creating a separate heap that was reserved at application startup and would hold these “special case” exception object instances.  So when the memory manager would detect that it is unable to satisfy an allocation request, a runtime error would happen which is trapped by the SysUtils unit's exception hook and translate that error into an exception that it then raised.  The problem is how do you allocate a class instance on a different heap?  This is where the class function NewInstance was introduced.  If you override that virtual class method, you can control where and how the object's instance memory is allocated.  The corrollary method is not a class method but is an instance method called FreeInstance, which as you can see would return the memory to the heap.  That looks simple, right?  Just override those methods on the EOutOfMemory exception class and you're sure to always be able to allocate an instance of that object.  In the early days, this was how it was done and it worked fine.

The problem was that we'd set aside this memory at startup, but the question was how much memory?  Enough for one, or two instances?  Ten instances?  Another problem was that it meant having another memory manager that would only manage memory from this small heap off to the side.  It was becoming clear that this solution, while it worked and seemed very clever, it was just too complicated.  What is interesting is that buried within this original complicated solution, was a better solution just staring us in the face.  Can you guess what that solution is?

It turns out that it was as simple as just pre-allocating the exception class instance... DUH!!!  No need to create a separate heap with its own memory manager, no need to override NewInstance and FreeInstance.  We really had no further need for those two methods so we could remove then, right?  Not so fast.  What if someone wrote an application that actually handled the Out Of Memory exception and was able to relieve the memory pressure (say, by freeing some easily recreated cached objects) and continue executing.  The problem is that you don't want to let the EOutOfMemory exception to ever be freed.  How do you do that when you cannot control the exception processing logic which will always destroy the currently raised exception object once the the handling “except” block exits?  Remember the NewInstance and FreeInstance methods from our overly complicated solution?  What if you just overrode the FreeInstance method and it simply did... nothing?  So now the EOutOfMemory exception instance will remain for the life of the application and can be used over and over again.

So why didn't we remove the NewInstance method and just leave the FreeInstance method?  This is probably best characterized as a classic case of serendipity.  It turns out that being able to control how and where an object type or a whole class of object type instances are allocated and freed can be useful in solving a very wide variety of different problems.  Rather than have lopsided methods, we kept the NewInstance and FreeInstance methods so that our ever industrious, always clever, customers would be able to do some new an interesting things.  It would have also meant a compiler change to the codegenerator to remove the call to NewInstance.  So the general usefulness of the feature along with not wanting to potentially introduce a whole slew of new codegen bugs, were a couple of reasons to leave it alone.  We did remove the mini heap manager since we were not using that anymore.

So there you have it.  A little bit more history about the early development of Delphi.  Many times the simplest solution is the right solution.  Having a complicated “clever” solution can come back to haunt you later down the road.