DUK_LOCAL duk_ret_t duk__finalize_helper(duk_context *ctx, void *udata) { duk_hthread *thr; DUK_ASSERT(ctx != NULL); thr = (duk_hthread *) ctx; DUK_UNREF(udata); DUK_DDD(DUK_DDDPRINT("protected finalization helper running")); /* [... obj] */ /* XXX: Finalizer lookup should traverse the prototype chain (to allow * inherited finalizers) but should not invoke accessors or proxy object * behavior. At the moment this lookup will invoke proxy behavior, so * caller must ensure that this function is not called if the target is * a Proxy. */ duk_get_prop_stridx_short(ctx, -1, DUK_STRIDX_INT_FINALIZER); /* -> [... obj finalizer] */ if (!duk_is_callable(ctx, -1)) { DUK_DDD(DUK_DDDPRINT("-> no finalizer or finalizer not callable")); return 0; } duk_dup_m2(ctx); duk_push_boolean(ctx, DUK_HEAP_HAS_FINALIZER_NORESCUE(thr->heap)); DUK_DDD(DUK_DDDPRINT("-> finalizer found, calling finalizer")); duk_call(ctx, 2); /* [ ... obj finalizer obj heapDestruct ] -> [ ... obj retval ] */ DUK_DDD(DUK_DDDPRINT("finalizer finished successfully")); return 0; /* Note: we rely on duk_safe_call() to fix up the stack for the caller, * so we don't need to pop stuff here. There is no return value; * caller determines rescued status based on object refcount. */ }
DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) { duk_hthread *thr; duk_heaphdr *curr; duk_uint_t round_no; duk_size_t count_all; duk_size_t count_finalized; duk_size_t curr_limit; DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); #if defined(DUK_USE_REFERENCE_COUNTING) DUK_ASSERT(heap->refzero_list == NULL); /* refzero not running -> must be empty */ #endif #if defined(DUK_USE_MARK_AND_SWEEP) DUK_ASSERT(heap->finalize_list == NULL); /* mark-and-sweep not running -> must be empty */ #endif /* XXX: here again finalizer thread is the heap_thread which needs * to be coordinated with finalizer thread fixes. */ thr = heap->heap_thread; DUK_ASSERT(thr != NULL); /* Prevent mark-and-sweep for the pending finalizers, also prevents * refzero handling from moving objects away from the heap_allocated * list. (The flag meaning is slightly abused here.) */ DUK_ASSERT(!DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); DUK_HEAP_SET_MARKANDSWEEP_RUNNING(heap); curr_limit = 0; /* suppress warning, not used */ for (round_no = 0; ; round_no++) { curr = heap->heap_allocated; count_all = 0; count_finalized = 0; while (curr) { count_all++; if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) { /* Only objects in heap_allocated may have finalizers. Check that * the object itself has a _Finalizer property (own or inherited) * so that we don't execute finalizers for e.g. Proxy objects. */ DUK_ASSERT(thr != NULL); DUK_ASSERT(curr != NULL); if (duk_hobject_hasprop_raw(thr, (duk_hobject *) curr, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) { if (!DUK_HEAPHDR_HAS_FINALIZED((duk_heaphdr *) curr)) { DUK_ASSERT(DUK_HEAP_HAS_FINALIZER_NORESCUE(heap)); /* maps to finalizer 2nd argument */ duk_hobject_run_finalizer(thr, (duk_hobject *) curr); count_finalized++; } } } curr = DUK_HEAPHDR_GET_NEXT(heap, curr); } /* Each round of finalizer execution may spawn new finalizable objects * which is normal behavior for some applications. Allow multiple * rounds of finalization, but use a shrinking limit based on the * first round to detect the case where a runaway finalizer creates * an unbounded amount of new finalizable objects. Finalizer rescue * is not supported: the semantics are unclear because most of the * objects being finalized here are already reachable. The finalizer * is given a boolean to indicate that rescue is not possible. * * See discussion in: https://github.com/svaarala/duktape/pull/473 */ if (round_no == 0) { /* Cannot wrap: each object is at least 8 bytes so count is * at most 1/8 of that. */ curr_limit = count_all * 2; } else { curr_limit = (curr_limit * 3) / 4; /* Decrease by 25% every round */ } DUK_D(DUK_DPRINT("finalizer round %ld complete, %ld objects, tried to execute %ld finalizers, current limit is %ld", (long) round_no, (long) count_all, (long) count_finalized, (long) curr_limit)); if (count_finalized == 0) { DUK_D(DUK_DPRINT("no more finalizable objects, forced finalization finished")); break; } if (count_finalized >= curr_limit) { DUK_D(DUK_DPRINT("finalizer count above limit, potentially runaway finalizer; skip remaining finalizers")); break; } } DUK_ASSERT(DUK_HEAP_HAS_MARKANDSWEEP_RUNNING(heap)); DUK_HEAP_CLEAR_MARKANDSWEEP_RUNNING(heap); }