I wanted to build a tool that would help me debug code that was leaking external resources by forgetting to dispose them. Thankfully, this doesn't come up often; as C++ programmers using RAII like to note, scoped destructors solve resource management in 90% of all cases, and Factor's resource management combinators are even more flexible. However, sometimes an external resource can have a complex lifetime, because of caching, pooling, and other advanced idioms. In these cases, having tools to help track leaks down can really help.
I took inspiration from Doug Coleman's managed malloc and free implementation. Whereas some languages use finalizers to ensure that an external resource gets cleaned up if you forget to dispose of it explicitly, I take the opposite approach; all active disposable objects are stored in a central registry, explicitly preventing the GC from cleaning them up.
tools.destructorsvocabulary introduces two words,
leaks. The first word prints a tally of all active resources:
Clicking 'list instances' next to a disposable class opens an inspector window with all active instances of this resource; for example, here we can list all file descriptors that Factor has open right now:
You can even dispose of the resource right there, as if you had called the dispose word on it:
leakscombinator compares active resources before and after a quotation runs, and lists any that were not disposed of. For example, in the following, I construct a
file-reader, read a line, and never dispose of it:
Notice how a file descriptor, together with a few associated resources, was leaked as a result of this.
If I wanted to, I could click on 'show instances' and dispose of the
input-portin the inspector; this would dispose of the other two associated objects as well. Also, opening the inspector on a disposable resource that was allocated inside a
leakscall will display a stack trace, showing where in the code the resource was allocated. This can help pinpoint the offending code.
Of course, the correct way to read a line from a file in Factor is to use a combinator which cleans up the stream for you. In the following example, the resource leak has been fixed:
To define a new disposable resource, simply create a tuple class that subclasses
disposable, make sure to construct it with
new-disposable, and override the
Here is an example that manages a limited pool of "X"s, of which there are 100 total:
100 >vector xs set-global
: get-x ( -- x ) xs get pop ;
: return-x ( x -- ) xs get push ;
TUPLE: my-disposable-resource < disposable x ;
: <my-disposable-resource> ( -- disposable )
my-disposable-resource new-disposable get-x >>x ;
M: my-disposable-resource dispose* x>> return-x ;
The disposable resource machinery is built with very little code, and it is used throughout Factor already.