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 finalizer property is set using the Duktape.fin()
method with the object and the finalizer function as call arguments.
The current finalizer is read back by calling Duktape.fin()
with only the object as a call argument. A finalizer can also be set/get from
C code using the duk_get_finalizer()
and duk_set_finalizer()
API calls. A finalizer can be either an Ecmascript function or a Duktape/C function.
A finalizer is triggered when an unreachable object is detected by reference counting or mark-and-sweep. Finalizers are also executed for all remaining objects (regardless of their reachability status) when a heap is destroyed. This guarantees 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.
The finalizer function is called with the target object as its sole argument. The finalizer may rescue the object by creating a live reference to the object before returning. The return value is ignored, and any errors thrown by the finalizer are silently ignored. A finalizer may be called multiple times (this may happen in special cases even when the object is not rescued by the finalizer). A finalizer should be careful to avoid e.g. freeing a native resource twice in such cases.
Finalizers cannot currently yield. The context executing the finalization can currently be any coroutine in the heap. (This will be fixed in the future.)
Finalization example:
// finalize.js var a; function init() { a = { foo: 123 }; Duktape.fin(a, function (x) { try { print('finalizer, foo ->', x.foo); } catch (e) { print('WARNING: finalizer failed (ignoring): ' + e); } }); } // create object, reference it through 'a' init(); // delete reference, refcount triggers finalization immediately print('refcount finalizer'); a = null; // mark-and-sweep finalizing happens here (at the latest) if // refcounting is disabled print('mark-and-sweep finalizer') Duktape.gc();
The try-catch
wrapper inside the finalizer of the above
example is strongly recommended. An uncaught finalizer error is silently
ignored which can be confusing, as it may seem like the finalizer is not
getting executed at all.
If you run this with the Duktape command line tool (with the default Duktape profile), you'll get:
$ duk finalize.js refcount finalizer finalizer, foo -> 123 mark-and-sweep finalizer Cleaning up...
If you have many objects of the same type, you can add a finalizer to the prototype to minimize the property count of object instances:
// Example of a hypothetical Socket object which is associated with a // platform specific file descriptor. function Socket(host, port) { this.host = host; this.port = port; this.fd = Platform.openSocket(host, port); } Duktape.fin(Socket.prototype, function (x) { if (x === Socket.prototype) { return; // called for the prototype itself } if (typeof x.fd !== 'number') { return; // already freed } try { Platform.closeSocket(x.fd); } catch (e) { print('WARNING: finalizer failed for fd ' + x.fd + ' (ignoring): ' + e); } delete x.fd; }); // Any Socket instances are now finalized without registering explicit // finalizers for them: var sock = new Socket('localhost', 8080);