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:
- 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:
newval – An instance of
<boolean>
.
- 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 callsdrain-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 whendrain-finalization-queue
is called. If the call todrain-finalization-queue
is aborted via a non-local exit during a call tofinalize
, the finalization queue retains all the objects that had been added to it but which had not been passed tofinalize
.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:
object – An instance of
<object>
.
- Values:
object – An instance of
<object>
.
- 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 functionfinalize
on it.The function returns its argument.
- See also:
- finalize Generic function#
Finalizes an object.
- Signature:
finalize object => ()
- Parameters:
object – An instance of
<object>
.
- 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 callsfinalize
on each object awaiting finalization. Objects join the finalization queue if they become unreachable after being registered for finalization withfinalize-when-unreachable
. However, you can callfinalize
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: