Finalization
Overview
Duktape supports object finalization as a custom feature. A finalizer
is called when an object is about to be freed, so that application code
can e.g. free native resources associated with the object. The finalizer
can be either an ECMAScript function or a Duktape/C function. However,
ECMAScript finalizers may interact badly with script timeouts, see below.
See How to use finalization
for examples.
Getting and setting the current finalizer
An object which has an internal _Finalizer
property in its
prototype chain (or in the object itself) is subject to finalization before
being freed. The internal property should not be accessed directly, but can
be read/written using the following:
Duktape.fin(obj)
(ECMAScript) or
duk_get_finalizer()
(C)
gets the current finalizer.
Duktape.fin(obj, fn)
(ECMAScript) or
duk_set_finalizer()
(C)
sets the current finalizer.
Finalizer function arguments and return value
The finalizer function is called with two arguments:
- The object being finalized.
- A boolean flag indicating if the object is being forcibly freed as part
of heap destruction. This argument was added in Duktape 1.4.0:
- If
false
(normal case), the finalizer may rescue the object
by creating a live reference to the object before returning and the
finalizer is guaranteed to be called again later (heap destruction at
the latest).
- If
true
(forced finalization in heap destruction), the
object cannot be rescued and will be forcibly freed after the finalizer
finishes. Native resources should be freed without expecting any further
calls into the finalizer.
The return value of a finalizer is ignored. Any errors thrown by the
finalizer are also silently ignored.
Finalizer execution guarantees
The main finalizer guarantees are:
- Finalizers are executed for unreachable objects detected by reference
counting or mark-and-sweep. The finalizer may not execute immediately,
however, not even when reference counting detects that the object became
unreachable.
- Finalizers are also executed for all remaining objects, regardless of
their reachability status, when a Duktape heap is destroyed.
- A finalizer is called exactly once, at the latest when the heap is
destroyed, unless the object is rescued by making it reachable again.
An object may be rescued by its own finalizer, or by another object's
finalizer when mark-and-sweep finalizes a set of objects. For example,
if
X.ref = Y
, and both X and Y become unreachable, it's
possible for Y's finalizer to run, and later on X's finalizer to rescue
both X and Y.
- An object may be rescued an arbitrary number of times; the finalizer
is called exactly once for each "rescue cycle". Even with this
guarantee in place, it's best practice for a finalizer to be re-entrant
and carefully avoid e.g. freeing a native resource multiple times if
re-entered.
- A finalizer is not executed for a Proxy object, but is executed for
the plain target object. This ensures that a finalizer isn't executed
multiple times when Proxy objects are created.
Together these guarantee that a finalizer gets executed at some point
before a heap is destroyed, which allows native resources (such as sockets
and files) to be freed reliably. There are a few exceptions to this guarantee,
see below for more discussion:
- Heap destruction finalizer sanity limit may cause a finalizer not to
be executed.
- When a script timeout is being propagated out of the current callstack,
ECMAScript finalizers will immediately rethrow the script timeout error.
Duktape/C finalizers will execute normally.
- If Duktape runs out of memory (despite emergency GC) when trying to call a
finalizer, the call error is silently ignored and the finalizer will be
skipped.
- When an object is finalized by mark-and-sweep but becomes unreachable
before the next mark-and-sweep round has a change to detect the rescue,
the object's finalizer will not be executed.
When the Duktape heap is being destroyed there are a few limitations for
finalizer behavior:
- Finalizers are executed for all finalizable objects in the heap,
including reachable objects.
- Finalizers cannot rescue objects; the semantics for a "rescue" would be
ambiguous. The finalizer's second argument is
true
when
called during heap destruction to indicate rescue is not possible.
- A finalizer can create new finalizable objects and these objects will also
be finalized. For example, a finalizer may post a HTTP notification of
an object destruction which may use native network resources with their
own finalizers. However, there's a sanity limit to this process to ensure
runaway finalizers cannot prevent a heap from being destroyed.
- The finalizer sanity algorithm is version specific, see
How to use finalization.
The algorithm allows the number of finalizable objects to grow initially,
but it must decrease in a reasonable time or the finalization process is
aborted, which may cause some native resource leaks.
Other current limitations
- When script execution timeout
(
DUK_USE_EXEC_TIMEOUT_CHECK
)
is used and a timeout occurs, it's possible for an ECMAScript finalizer to start
running but immediately fail due to a script timeout. If this is a concrete
concern, use a Duktape/C native finalizer instead which will run normally even
when propagating a timeout.
- The context (Duktape thread) executing the finalizer can currently be any
coroutine in the heap. This must be taken into account in sandboxing.
- Finalizers cannot currently yield.