12. Finalization

It is sometimes necessary to perform actions when a block of memory dies. For example, a block may represent the acquisition of an external resource such as a file handle or a network connection. When the block dies, the corresponding resource must be released. This procedure is known as finalization.

A block requiring finalization must be registered by calling mps_finalize():

mps_addr_t ref = block_requiring_finalization;
mps_finalize(arena, &ref);

A block that been registered for finalization becomes finalizable as soon as the garbage collector observes that it would otherwise be reclaimed (that is, the only thing keeping it alive is the fact that it needs to be finalized). If a block is finalizable the MPS may choose to finalize it (by posting a finalization message: see below) at any future time.

Note

This means that a block that was determined to be finalizable, but then became unconditionally live by the creation of a new strong reference to it, may still be finalized.

Weak references (1) do not prevent blocks from being finalized. At the point that a block is finalized, weak references will still validly refer to the block. The fact that a block is registered for finalization prevents weak references to that block from being splatted. See Weak references.

The Memory Pool System finalizes a block by posting a finalization message to the message queue of the arena in which the block was allocated.

Note

This design avoids the problems that can result from the garbage collector calling a function in the client program to do the finalization. In such an implementation, the client program’s finalization code may end up running concurrently with other code that accesses the underlying resource, and so access to the resource need to be guarded with a lock, but then an unlucky scheduling of finalization can result in deadlock. See Boehm (2002) for a detailed discussion of this issue.

The message type of finalization messages is mps_message_type_finalization(), and the client program must enable the posting of these messages by calling mps_message_type_enable() before any block becomes finalizable:

mps_message_type_enable(arena, mps_message_type_finalization());

When a finalization message has been retrieved from the message queue by calling mps_message_get(), the finalization reference may be accessed by calling mps_message_finalization_ref(). The finalization message keeps the block alive until it is discarded by calling mps_message_discard().

Note

The client program may choose to keep the finalized block alive by keeping a strong reference to the finalized object after discarding the finalization message.

This process is known as resurrection and in some finalization systems requires special handling, but in the MPS this just is just the usual result of the rule that strong references keep objects alive.

It is fine to re-register a block for finalization after retrieving its finalization message from the message queue. This will cause it to be finalized again should all strong references disappear again.

See Messages for details of the message mechanism.

12.1. Multiple finalizations

A block may be registered for finalization multiple times. A block that has been registered for finalization n times will be finalized at most n times.

This may mean that there are multiple finalization messages on the queue at the same time, or it may not (it may be necessary for the client program to discard previous finalization messages for a block before a new finalization messages for that block are posted to the message queue). The MPS provides no guarantees either way: a client program that registers the same block multiple times must cope with either behaviour.

12.2. Cautions

  1. Don’t rely on finalization for your program to work. Treat it as an optimization that enables the freeing of resources that the garbage collector can prove are unreachable.

  2. The MPS provides no guarantees about the promptness of finalization. The MPS does not finalize a block until it determines that the block is finalizable, which may require a full garbage collection in the worst case, and such a collection may not scheduled for some time. Or the block may never become finalizable because it is incorrectly determined to be reachable due to an ambiguous reference pointing to it. Or the block may never become finalizable because it remains reachable through a reference, even if that reference might never be used.

  3. Even when blocks are finalized in a reasonably timely fashion, the client needs to process the finalization messages in time to avoid the resource running out. For example, in the Scheme interpreter, finalization messages are only processed at the end of the read–eval–print loop, so a program that opens many files may run out of handles even though the associated objects are all finalizable, as shown here:

    MPS Toy Scheme Example
    9960, 0> (define (repeat n f _) (if (eqv? n 0) '() (repeat (- n 1) f (f))))
    repeat
    10840, 0> (repeat 300 (lambda () (open-input-file "scheme.c")) 0)
    open-input-file: cannot open input file
    

    A less naïve interpreter might process finalization messages on a more regular schedule, or might take emergency action in the event of running out of open file handles by carrying out a full garbage collection and processing any finalization messages that are posted as a result.

    If you are designing a programming language then it is generally a good idea to provide the programmer with a mechanism for ensuring prompt release of scarce resources. For example, Scheme provides the (with-input-from-file) procedure which specifies that the created port has dynamic extent (and so can be closed as soon as the procedure exits).

  4. The MPS does not finalize objects in the context of mps_arena_destroy() or mps_pool_destroy(). mps_pool_destroy() should therefore not be invoked on pools containing objects registered for finalization.

    Note

    Under normal circumstances, finalization code can assume that objects referenced by the object being finalized (“object F”) have themselves not yet been finalized. (Because object F is keeping them alive.) If finalization code is run at program exit, this assumption is no longer true. It is much more difficult to write correct code if it has to run under both circumstances.

    This is why Java’s System.runFinalizersOnExit is deprecated. See Appendix A of Boehm (2002) for a discussion of this problem.

  1. Not all pool classes support finalization. In general, only pools that manage objects whose liveness is determined by garbage collection do so. See the Pool reference.

12.3. Finalization interface

mps_res_t mps_finalize(mps_arena_t arena, mps_addr_t *ref_p)

Register a block for finalization.

arena is the arena in which the block lives.

ref_p points to a reference to the block to be registered for finalization.

Returns MPS_RES_OK if successful, or another result code if not.

This function registers the block pointed to by *ref_p for finalization. This block must have been allocated from a pool in arena. Violations of this constraint may not be checked by the MPS, and may be unsafe, causing the MPS to crash in undefined ways.

Note

This function receives a pointer to a reference. This is to avoid placing the restriction on the client program that the C call stack be a root.

mps_res_t mps_definalize(mps_arena_t arena, mps_addr_t *ref_p)

Deregister a block for finalization.

arena is the arena in which the block lives.

ref_p points to a reference to the block to be deregistered for finalization.

Returns MPS_RES_OK if successful, or MPS_RES_FAIL if the block was not previously registered for finalization.

Note

This function receives a pointer to a reference. This is to avoid placing the restriction on the client program that the C call stack be a root.

12.4. Finalization messages

mps_message_type_t mps_message_type_finalization(void)

Return the message type of finalization messages.

Finalization messages are used by the MPS to implement finalization. When the MPS detects that a block that has been registered for finalization (by calling mps_finalize()) is finalizable, it finalizes it by posting a message of this type.

Note that there might be delays between the block becoming finalizable, the MPS detecting that, and the message being posted.

In addition to the usual methods applicable to messages, finalization messages support the mps_message_finalization_ref() method which returns a reference to the block that was registered for finalization.

See also

Messages.

void mps_message_finalization_ref(mps_addr_t *ref_o, mps_arena_t arena, mps_message_t message)

Returns the finalization reference for a finalization message.

ref_o points to a location that will hold the finalization reference.

arena is the arena which posted the message.

message is a message retrieved by mps_message_get() and not yet discarded. It must be a finalization message: see mps_message_type_finalization().

The reference returned by this method is a reference to the block that was originally registered for finalization by a call to mps_finalize().

Note

The reference returned is subject to the normal constraints, such as might be imposed by a moving collection, if appropriate. For this reason, it is stored into the location pointed to by ref_o in order to enable the client program to place it directly into scanned memory, without imposing the restriction that the C stack be a root.

The message itself is not affected by invoking this method. Until the client program calls mps_message_discard() to discard the message, it will refer to the object and prevent its reclamation.

See also

Messages.