close
  • person chevron_right

    Killing Common Lisp methods and classes

    Michał "phoe" Herda – Saturday, 15 December, 2018 - 22:36

If you've been following me, I've been working on a library I called PROTEST that is a toolkit for managing protocols (think Java interfaces) and test cases for the software I'm writing.

When running the test suites (which, at this moment, contain 65 tests and 163 checks), I have noticed a leak - running the tests for the first time was instantaneous as they took two tenths of a second, but as I ran them on and on, they took more and more time. Running them for the twentieth time already took two seconds, and the issue worsened as I moved on and on.

After taking the issue to #sbcl on Freenode, I have realized that one assumption I've made about CLOS was false. Calling SETF FIND-CLASS NIL on a particular class only breaks the naming link - the class can be no longer found by its name, and that's all. All other links that fasten that class to CLOS are still there. It includes the subclass-superclass reference that the standard class STANDARD-OBJECT holds with the newly created class; all the links made by methods specializing on that class are also binding. This all means that the class object does not get finalized.

That caused me to modify PROTEST to make sure that all the methods I create that specialize on the classes (inside PROTEST's protocol part, I use a trick with a method that removes itself to ensure that it is effectively a one-shot method) are removed at the end of each test. Without that, the methods were lingering around, gathering up at the end of each test, and slowing down the generic dispatch. That fixed the slowdown when it came to methods.

However, the class objects still remained live. I discovered that calling REMOVE-DIRECT-SUBCLASS to remove that class from the list of STANDARD-OBJECT's subclasses, and then calling SETF FIND-CLASS NIL on it did not cause the object to become unreachable and therefore garbage-collected, even if I created a class and not a single method on it. I have checked that by creating a simple printing finalizer and verifying that it was not called.

It has caused me to create a bugticket on SBCL that was momentarily fixed by Stas Boukarev. It should become live in the SBCL release that comes after 1.4.14 (the current at the time of writing).


Summing up:

  • A method does not get automatically killed when the class object it specializes on loses its name via (setf (find-class 'foo) nil). To kill a method, explicitly call REMOVE-METHOD on it.
  • A class does not get automatically killed when it loses its name via (setf (find-class 'foo) nil) To kill a class, make sure that all methods specializing on it are dead, and then call REMOVE-DIRECT-SUBCLASS on STANDARD-OBJECT and the class itself, and then call SETF FIND-CLASS NIL.
  • Obviously, no other live references to the objects you want to kill must remain. There is a SBCL function called sb-ext:gc-and-search-roots that theoretically helps find the objects pointing to a particular object of interest - however, I'm still figuring out how to use it correctly.. and perhaps I just found a bug in it.