The finalization Module#

Open Dylan provides a finalization interface in the finalization module of common-dylan. This section explains finalization, the finalization interface provided, and how to use the interface in applications. Note that you must use finalization to be able to use the interface described in this documentation.

What is finalization?#

The Memory Management Reference defines finalization as follows:

In garbage-collected languages, it is often necessary to perform actions on some objects after they are no longer in use and before their memory can be recycled. These actions are known as finalization or termination.

A common use of finalization is to release a resource when the corresponding “proxy” object dies, if the proxy object has indefinite extent and therefore more predictable tools like block () ... cleanup ... end won’t work.

For example, when interfacing Dylan code with foreign code that does not have automatic memory management, if an interface involves a Dylan object that references a foreign object it may be necessary to free the memory resources of the foreign object when the Dylan object is reclaimed.

How the finalization interface works#

The following sections give a broad overview of how finalization works and how to use the interface.

Registering objects for finalization#

Finalization works through cooperation with the garbage collector. Objects that are no longer referenced by the application that created them will eventually be discovered by Dylan’s garbage collector and are then available to be reclaimed.

By default, the garbage collector reclaims such objects without notifying your application. If it is necessary to finalize an object before it is reclaimed, your application must inform the garbage collector.

The garbage collector maintains a register of objects requiring finalization before being reclaimed. To add an object to the register, call the function finalize-when-unreachable on the object. Objects on the register are said to be finalizable.

If the garbage collector discovers that a finalizable object is no longer referenced by the application, it does not reclaim it immediately. Instead, it takes the object off its finalization register, and adds it to the finalization queue.

The finalization queue contains all the objects awaiting finalization. The garbage collector will not reclaim the objects until they have been finalized.

A simple example of registering a finalizer:

define method initialize (lock :: <recursive-lock>, #key) => ()
  drain-finalization-queue();
  next-method();
  let res = primitive-make-recursive-lock(lock,
                                          lock.synchronization-name);
  check-synchronization-creation(lock, res);
  finalize-when-unreachable(lock);
end method;

The reasons for calling drain-finalization-queue are discussed below.

Note

The library containing this code must have use finalization; in its module definition.

Draining the finalization queue#

Objects in the finalization queue wait there until the application drains it by calling the function drain-finalization-queue. This function finalizes every object in the queue.

The finalization queue is not normally drained automatically. See How can my application drain the finalization queue automatically? for details of how you can set up a thread to do so.

Note

The order in which objects in the finalization queue are finalized is not defined. Applications should not make any assumptions about finalization ordering.

Finalizers#

The drain-finalization-queue function finalizes each object in the finalization queue by calling the generic function finalize on it. You should define methods for finalize on those classes whose instances may require finalization. These methods are called finalizers.

The recommended interface to finalization is through finalize-when-unreachable and drain-finalization-queue, but calling finalize on an object directly is also permitted. If you are certain you are finished with an object, it may be desirable to do so. For example, you might want to finalize an object created in a local binding before it goes out of scope.

Note

Finalizable objects are only removed from the register if the garbage collector discovers that they are unreachable and moves them into the finalization queue. Calling finalize on an object directly does not affect its registration status.

The drain-finalization-queue function makes each call to finalize inside whatever dynamic handler environment is present when drain-finalization-queue is called. If the call to drain-finalization-queue is aborted via a non-local exit during a call to finalize, the finalization queue retains all the objects that had been added to it but which had not been passed to finalize.

There is a default method for finalize on <object>. The method does nothing. It is available so that it is safe for all finalizers to call next-method, a practice that we strongly encourage. See Writing finalizers.

After finalization#

Once an object in the finalization queue has been finalized, it typically becomes available for reclamation by the garbage collector. Because it has been taken off the garbage collector’s finalization register, it will not be queued up for finalization again.

Note

There are exceptions to this rule; see The effects of multiple registrations and The effects of resurrecting objects.

Upon application exit#

There are no guarantees that objects which are registered for finalization will actually be finalized before the application exits. This is not a problem on many operating systems, which free any resources held by a process when it exits.

Where it is necessary to guarantee an action at the time the application exits, you should use a more explicit mechanism.

The effects of multiple registrations#

Sometimes objects are registered for finalization more than once. The effects of multiple registration are defined as follows:

Calling finalize-when-unreachable on an object n times causes that object to be added to the finalization queue up to n times, where n is greater than or equal to zero. There is no guarantee that the object will be added exactly n times.

Note that this definition so general that it does not guarantee that any object will ever be added to be finalization queue. In practice, Common Dylan’s implementation guarantees that an object is added to the queue at least once whenever an object has ben determined to be unreachable by the garbage collector.

To remain robust under multiple registration, finalizers should be idempotent: that is, the effect of multiple finalize calls on an object should is the same as the effect of a single call.

The effects of resurrecting objects#

If a finalizer makes an object reachable again, by storing a reference to the object in a variable, slot, or collection, we say it has resurrected it. An object may also be resurrected if it becomes reachable again when some other object is resurrected (because it is directly or indirectly referenced by that other object).

Resurrecting objects has pitfalls, and must be done with great care. Since finalizers typically destructively modify objects when freeing their resources, it is common for finalization to render objects unusable. We do not recommend resurrection if there is any possibility of the object being left in an unusable state, or if the object references any other objects whose transitive closure might include an object left in such a state by another call to finalize.

If you do resurrect objects, note that they will not be finalized again unless you re-register them.

The effects of finalizing objects directly#

Any object that has been finalized directly, through the application itself calling finalize on it, may not yet be unreachable. Like any normal object it only becomes eligible for reclamation when it is unreachable. If such an object was also registered for finalization using finalize-when-unreachable, it can end up being finalized again via the queue mechanism.

Finalization and weak tables#

If an object is both registered for finalization and is weakly referred to from a weak table, finalization occurs first, with weak references being removed afterwards. That is, reachability is defined in terms of strong references only, as far as finalization is concerned. Weak references die only when an object’s storage is finally reclaimed.

For more on weak tables, see Weak tables.

Writing finalizers#

Because the default finalize method, on <object>, does nothing, you must define your own finalize methods to get results from the finalization interface. This section contains useful information about writing finalizers.

Class-based finalization#

If your application defines a class for which all instances require finalization, call finalize-when-unreachable in its initialize method.

Parallels with INITIALIZE methods#

The default method on <object> is provided to make it safe to call next-method in all finalizers. This situation is parallel to that for class initialize methods, which call next-method before performing their own initializations. By doing so, initialize methods guarantee that the most specific initializations occur last.

By contrast, finalizers should call next-method last, in case they depend on the superclass finalizer not being run.

Simplicity and robustness#

Write finalizers that are simple and robust. They might be called in any context, including within other threads; with careful design, your finalizers will work in most or all possible situations.

A finalizer might be called on the same object more than once. This could occur if the object was registered for finalization more than once, or if your application registered the object for finalization and also called finalize on it directly. To account for this, write finalizers that are idempotent: that is, the effect of multiple calls is the same as the effect of a single call. See The effects of multiple registrations for more on the effects of multiple registrations.

Remember that the order in which the finalization queue is processed is not defined. Finalizers cannot make assumptions about ordering.

This is particularly important to note when writing finalizers for classes that are typically used to form circular or otherwise interestingly connected graphs of objects. If guarantees about finalization in graphs of objects are important, we suggest registering a root object for finalization and making its finalizer traverse the graph (in some graph-specific well-ordered fashion) and call the finalize method for each object in the graph requiring finalization.

Singleton finalizers#

Do not write singleton methods on finalize. The singleton method itself would refer to the object, and hence prevent it from becoming unreachable.

Using finalization in applications#

This section answers questions about using finalization in an application.

How can my application drain the finalization queue automatically?#

If you would prefer the queue to be drained asynchronously, use the automatic finalization interface. For more details, see automatic-finalization-enabled? and automatic-finalization-enabled?-setter.

Libraries that do not wish to depend on automatic finalization should not use those functions. They should call drain-finalization-queue synchronously at useful times, such as whenever they call finalize-when-unreachable.

Libraries that are not written to depend on automatic finalization should always behave correctly if they are used in an application that does use it.

When should my application drain the finalization queue?#

If you do not use automatic finalization, drain the queue synchronously at useful points in your application, such as whenever you call finalize-when-unreachable on an object.

This section contains a reference description for each item in the finalization interface. These items are exported from the common-dylan library in a module called finalization.

automatic-finalization-enabled? Function#

Returns true if automatic finalization is enabled, and false otherwise.

Signature:

automatic-finalization-enabled? () => enabled?

Values:
  • enabled? – An instance of <boolean>. Default value: #f.

Discussion:

Returns true if automatic finalization is enabled, and false otherwise.

See also:

automatic-finalization-enabled?-setter Function#

Sets the automatic finalization system state.

Signature:

automatic-finalization-enabled?-setter newval => ()

Parameters:
Discussion:

Sets the automatic finalization system state to newval.

The initial state is #f. If the state changes from #f to #t, a new thread is created which regularly calls drain-finalization-queue inside an empty dynamic environment (that is, no dynamic condition handlers). If the state changes from #t to #f, the thread exits.

See also:

drain-finalization-queue Function#

Calls finalize on every object in the finalization queue.

Signature:

drain-finalization-queue () => ()

Discussion:

Calls finalize on each object that is awaiting finalization.

Each call to finalize is made inside whatever dynamic handler environment is present when drain-finalization-queue is called. If the call to drain-finalization-queue is aborted via a non-local exit during a call to finalize, the finalization queue retains all the objects that had been added to it but which had not been passed to finalize.

The order in which objects in the finalization queue will be finalized is not defined. Applications should not make any assumptions about finalization ordering.

See also:

finalize-when-unreachable Function#

Registers an object for finalization.

Signature:

finalize-when-unreachable object => object

Parameters:
Values:
Discussion:

Registers object for finalization. If object becomes unreachable, it is added to the finalization queue rather than being immediately reclaimed.

Object waits in the finalization queue until the application calls drain-finalization-queue, which processes each object in the queue by calling the generic function finalize on it.

The function returns its argument.

See also:

finalize Generic function#

Finalizes an object.

Signature:

finalize object => ()

Parameters:
Discussion:

Finalizes object.

You can define methods on finalize to perform class-specific finalization procedures. These methods are called finalizers.

A default finalize method on <object> is provided.

The main interface to finalization is the function drain-finalization-queue, which calls finalize on each object awaiting finalization. Objects join the finalization queue if they become unreachable after being registered for finalization with finalize-when-unreachable. However, you can call finalize directly if you wish.

Once finalized, object is available for reclamation by the garbage collector, unless finalization made it reachable again. (This is called resurrection ; see The effects of resurrecting objects.) Because the object has been taken off the garbage collector’s finalization register, it will not be added to the finalization queue again, unless it is resurrected. However, it might still appear in the queue if it was registered more than once.

Do not write singleton methods on finalize. A singleton method would itself reference the object, and hence prevent it from becoming unreachable.

See also:

finalize(<object>) Method#

Finalizes an object.

Signature:

finalize object => ()

Parameters:
Discussion:

This method is a default finalizer for all objects. It does nothing, and is provided only to make next-method calls safe for all methods on finalize.

See also: