26 Memory Management: Finalize

The finalization facility gives the programmer the ability to process an Oz value when it is discovered (during garbage collection) that it has otherwise become unreachable. This is often used to release native resources held or encapsulated by said value.

Native functors and extension classes make it very easy to extend Oz with new interfaces to arbitrary resources. Typically, the access to a resource will be encapsulated into a data-structure that is an instance of an extension class and we would like the resource to be automatically released when the data-structure is no longer being referenced and can be garbage collected. For example:

This is the purpose of finalization: to execute a cleanup action when a computational object is no longer referenced.

Mozart proposes two finalization mechanisms, one being pre-mortem, and the other one being post-mortem. The pre-mortem finalization facility was inspired by the article Guardians in a Generation-Based Garbage Collector (R. Kent Dybvig, Carl Bruggeman, David Eby, June 1993). It is built on top of weak dictionaries (Section 9.4 of ``The Oz Base Environment''): each weak dictionary is associated with a finalization stream on which pairs Key#Value appear when Value becomes unreachable except through one or more weak dictionaries.

We recommend to use the post-mortem finalization facility when possible. The reason is that it gives stronger guarantees: upon finalization, there is no possibility for a value to remain in memory, it is already gone. Except, of course, if the value is brought back in memory via distribution or unpickling (Chapter 22).

Note that since instances of the same stateless value are indistinguishable, we cannot tell the difference between one instance of the integer 7 and another one. Thus, the question of whether such an instance is unreachable or whether it is an identical copy of it which is unreachable can usually not be answered. Also, since these values are indistinguishable, the engine is in principle licensed to either duplicate or merge them at its discretion (duplication actually occurs when such values are distributed to other sites). For this reason, finalization only makes sense for datatypes with token equality, i. e. where the identity of an instance is unique. Stateless values with structural equality are treated by the finalization mechanism as if they were unreachable (i. e. as if the weak dictionary contained a copy) and will be subjected to finalization at the very next garbage collection. One exception are atoms: since they remain in the global atom table, they are currently considered to always be reachable. This might change if we implement garbage collection of the atom table.

26.1 Pre-mortem finalization facilities

Finalize.guardian

{Finalize.guardian +Finalizer ?Register}

This takes as input a 1-ary procedure Finalizer and returns a 1-ary procedure Register representing a new guardian. A value X can be registered in the guardian using:

{Register X}

Thereafter, when X becomes unreachable except through a weak dictionary (e. g. a guardian), at the next garbage collection X is removed from the guardian and the following is eventually executed:

{Finalizer X}

We say eventually because the finalization thread is subject to the same fair scheduling as any other thread. Note that the value X has still not been garbage collected; only at the next garbage collection after the call to Finalizer has returned, and assuming Value is no longer referenced at all, not even by the guardian, will it really be garbage collected.

Danger

The Register procedure cannot be used in a (subordinated) space other than where Finalize.guardian has been called. Keep in mind that it probably doesn't make sense to create a guardian in a non-toplevel space because a space can become failed. When this happens, everything in the space is discarded, including the guardians, which means the values registered in them will never be finalized.

Finalize.register

{Finalize.register +Value +Handler}

This is a slightly different interface that allows to register simultaneously a Value and a corresponding finalizer procedure Handler. After Value becomes otherwise unreachable, {Handler Value} is eventually executed.

Finalize.everyGC

{Finalize.everyGC +P/0}

This simply registers a 0-ary procedure to be invoked after each garbage collection. Note that you cannot rely on how soon after the garbage collection this procedure will really be invoked: it is in principle possible that the call may only be scheduled several garbage collections later if the system has an incredibly large number of live threads and generates tons of garbage. It is instructive to look at the definition of EveryGC:

proc {EveryGC P}
   proc {DO _} {P} {Finalize.register DO DO} end 
in {Finalize.register DO DO} end

in other words, we create a procedure DO and register it using itself as its own handler. When invoked, it calls P and registers itself again.

26.2 Post-mortem finalization facilities

Finalize.postmortem

{Finalize.postmortem X +P Y}

This statement registers the value X for post-mortem finalization. X and Y can be any value or variable, while P must be a port. Once the garbage collector detects that X has become unreachable, it sends the value Y on the port P. In other words, it looks like the statement {Send P Y} is executed automatically once X disappears from memory.


Denys Duchier, Leif Kornstaedt, Martin Homik, Tobias Müller, Christian Schulte and Peter Van Roy
Version 1.4.0 (20080702)