DUK_LOCAL void duk__run_object_finalizers(duk_heap *heap, duk_small_uint_t flags) { duk_heaphdr *curr; duk_heaphdr *next; #if defined(DUK_USE_DEBUG) duk_size_t count = 0; #endif duk_hthread *thr; DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap)); thr = duk__get_temp_hthread(heap); DUK_ASSERT(thr != NULL); curr = heap->finalize_list; while (curr) { DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* only objects have finalizers */ DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); /* flags have been already cleared */ DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_READONLY(curr)); /* No finalizers for ROM objects */ if (DUK_LIKELY((flags & DUK_MS_FLAG_SKIP_FINALIZERS) == 0)) { /* Run the finalizer, duk_hobject_run_finalizer() sets FINALIZED. * Next mark-and-sweep will collect the object unless it has * become reachable (i.e. rescued). FINALIZED prevents the * finalizer from being executed again before that. */ duk_hobject_run_finalizer(thr, (duk_hobject *) curr); /* must never longjmp */ DUK_ASSERT(DUK_HEAPHDR_HAS_FINALIZED(curr)); } else { /* Used during heap destruction: don't actually run finalizers * because we're heading into forced finalization. Instead, * queue finalizable objects back to the heap_allocated list. */ DUK_D(DUK_DPRINT("skip finalizers flag set, queue object to heap_allocated without finalizing")); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); } /* queue back to heap_allocated */ next = DUK_HEAPHDR_GET_NEXT(heap, curr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr); curr = next; #if defined(DUK_USE_DEBUG) count++; #endif } /* finalize_list will always be processed completely */ heap->finalize_list = NULL; #if defined(DUK_USE_DEBUG) DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %ld finalizers called", (long) count)); #endif }
static void duk__run_object_finalizers(duk_heap *heap) { duk_heaphdr *curr; duk_heaphdr *next; #ifdef DUK_USE_DEBUG int count = 0; #endif duk_hthread *thr; DUK_DD(DUK_DDPRINT("duk__run_object_finalizers: %p", (void *) heap)); thr = duk__get_temp_hthread(heap); DUK_ASSERT(thr != NULL); curr = heap->finalize_list; while (curr) { DUK_DDD(DUK_DDDPRINT("mark-and-sweep finalize: %p", (void *) curr)); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT); /* only objects have finalizers */ DUK_ASSERT(!DUK_HEAPHDR_HAS_REACHABLE(curr)); /* flags have been already cleared */ DUK_ASSERT(!DUK_HEAPHDR_HAS_TEMPROOT(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZABLE(curr)); DUK_ASSERT(!DUK_HEAPHDR_HAS_FINALIZED(curr)); /* run the finalizer */ duk_hobject_run_finalizer(thr, (duk_hobject *) curr); /* must never longjmp */ /* mark FINALIZED, for next mark-and-sweep (will collect unless has become reachable; * prevent running finalizer again if reachable) */ DUK_HEAPHDR_SET_FINALIZED(curr); /* queue back to heap_allocated */ next = DUK_HEAPHDR_GET_NEXT(curr); DUK_HEAP_INSERT_INTO_HEAP_ALLOCATED(heap, curr); curr = next; #ifdef DUK_USE_DEBUG count++; #endif } /* finalize_list will always be processed completely */ heap->finalize_list = NULL; #ifdef DUK_USE_DEBUG DUK_D(DUK_DPRINT("mark-and-sweep finalize objects: %d finalizers called", count)); #endif }
DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) { duk_hthread *thr; duk_heaphdr *curr; #ifdef DUK_USE_DEBUG duk_size_t count_obj = 0; #endif DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); #ifdef DUK_USE_REFERENCE_COUNTING DUK_ASSERT(heap->refzero_list == NULL); /* refzero not running -> must be empty */ #endif #ifdef 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); curr = heap->heap_allocated; while (curr) { 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 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))) { duk_hobject_run_finalizer(thr, (duk_hobject *) curr); } #ifdef DUK_USE_DEBUG count_obj++; #endif } curr = DUK_HEAPHDR_GET_NEXT(curr); } /* Note: count includes all objects, not only those with an actual finalizer. */ #ifdef DUK_USE_DEBUG DUK_D(DUK_DPRINT("checked %ld objects for finalizers before freeing heap", (long) count_obj)); #endif }
static void duk__free_run_finalizers(duk_heap *heap) { duk_heaphdr *curr; #ifdef DUK_USE_DEBUG duk_size_t count_obj = 0; #endif DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); #ifdef DUK_USE_REFERENCE_COUNTING DUK_ASSERT(heap->refzero_list == NULL); /* refzero not running -> must be empty */ #endif #ifdef DUK_USE_MARK_AND_SWEEP DUK_ASSERT(heap->finalize_list == NULL); /* mark-and-sweep not running -> must be empty */ #endif /* FIXME: here again finalizer thread is the heap_thread which needs * to be coordinated with finalizer thread fixes. */ curr = heap->heap_allocated; while (curr) { if (DUK_HEAPHDR_GET_TYPE(curr) == DUK_HTYPE_OBJECT) { /* Only objects in heap_allocated may have finalizers. */ DUK_ASSERT(heap->heap_thread != NULL); DUK_ASSERT(curr != NULL); duk_hobject_run_finalizer(heap->heap_thread, (duk_hobject *) curr); #ifdef DUK_USE_DEBUG count_obj++; #endif } curr = DUK_HEAPHDR_GET_NEXT(curr); } /* Note: count includes all objects, not only those with an actual finalizer. */ #ifdef DUK_USE_DEBUG DUK_D(DUK_DPRINT("checked %d objects for finalizers before freeing heap", (int) count_obj)); #endif }
static void duk__refzero_free_pending(duk_hthread *thr) { duk_heaphdr *h1, *h2; duk_heap *heap; duk_int_t count = 0; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); heap = thr->heap; DUK_ASSERT(heap != NULL); /* * Detect recursive invocation */ if (DUK_HEAP_HAS_REFZERO_FREE_RUNNING(heap)) { DUK_DDD(DUK_DDDPRINT("refzero free running, skip run")); return; } /* * Churn refzero_list until empty */ DUK_HEAP_SET_REFZERO_FREE_RUNNING(heap); while (heap->refzero_list) { duk_hobject *obj; duk_bool_t rescued = 0; /* * Pick an object from the head (don't remove yet). */ h1 = heap->refzero_list; obj = (duk_hobject *) h1; DUK_DD(DUK_DDPRINT("refzero processing %p: %!O", (void *) h1, (duk_heaphdr *) h1)); DUK_ASSERT(DUK_HEAPHDR_GET_PREV(h1) == NULL); DUK_ASSERT(DUK_HEAPHDR_GET_TYPE(h1) == DUK_HTYPE_OBJECT); /* currently, always the case */ /* * Finalizer check. * * Note: running a finalizer may have arbitrary side effects, e.g. * queue more objects on refzero_list (tail), or even trigger a * mark-and-sweep. * * Note: quick reject check should match vast majority of * objects and must be safe (not throw any errors, ever). */ /* XXX: If object has FINALIZED, it was finalized by mark-and-sweep on * its previous run. Any point in running finalizer again here? If * finalization semantics is changed so that finalizer is only run once, * checking for FINALIZED would happen here. */ /* A finalizer is looked up from the object and up its prototype chain * (which allows inherited finalizers). */ if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_FINALIZER(thr))) { DUK_DDD(DUK_DDDPRINT("object has a finalizer, run it")); DUK_ASSERT(h1->h_refcount == 0); h1->h_refcount++; /* bump refcount to prevent refzero during finalizer processing */ duk_hobject_run_finalizer(thr, obj); /* must never longjmp */ h1->h_refcount--; /* remove artificial bump */ DUK_ASSERT_DISABLE(h1->h_refcount >= 0); /* refcount is unsigned, so always true */ if (h1->h_refcount != 0) { DUK_DDD(DUK_DDDPRINT("-> object refcount after finalization non-zero, object will be rescued")); rescued = 1; } else { DUK_DDD(DUK_DDDPRINT("-> object refcount still zero after finalization, object will be freed")); } } /* Refzero head is still the same. This is the case even if finalizer * inserted more refzero objects; they are inserted to the tail. */ DUK_ASSERT(h1 == heap->refzero_list); /* * Remove the object from the refzero list. This cannot be done * before a possible finalizer has been executed; the finalizer * may trigger a mark-and-sweep, and mark-and-sweep must be able * to traverse a complete refzero_list. */ h2 = DUK_HEAPHDR_GET_NEXT(h1); if (h2) { DUK_HEAPHDR_SET_PREV(h2, NULL); /* not strictly necessary */ heap->refzero_list = h2; } else { heap->refzero_list = NULL; heap->refzero_list_tail = NULL; } /* * Rescue or free. */ if (rescued) { /* yes -> move back to heap allocated */ DUK_DD(DUK_DDPRINT("object rescued during refcount finalization: %p", (void *) h1)); DUK_HEAPHDR_SET_PREV(h1, NULL); DUK_HEAPHDR_SET_NEXT(h1, heap->heap_allocated); heap->heap_allocated = h1; } else { /* no -> decref members, then free */ duk__refcount_finalize_hobject(thr, obj); duk_heap_free_heaphdr_raw(heap, h1); } count++; } DUK_HEAP_CLEAR_REFZERO_FREE_RUNNING(heap); DUK_DDD(DUK_DDDPRINT("refzero processed %ld objects", (long) count)); /* * Once the whole refzero cascade has been freed, check for * a voluntary mark-and-sweep. */ #if defined(DUK_USE_MARK_AND_SWEEP) && defined(DUK_USE_VOLUNTARY_GC) /* 'count' is more or less comparable to normal trigger counter update * which happens in memory block (re)allocation. */ heap->mark_and_sweep_trigger_counter -= count; if (heap->mark_and_sweep_trigger_counter <= 0) { duk_bool_t rc; duk_small_uint_t flags = 0; /* not emergency */ DUK_D(DUK_DPRINT("refcount triggering mark-and-sweep")); rc = duk_heap_mark_and_sweep(heap, flags); DUK_UNREF(rc); DUK_D(DUK_DPRINT("refcount triggered mark-and-sweep => rc %ld", (long) rc)); } #endif /* DUK_USE_MARK_AND_SWEEP && DUK_USE_VOLUNTARY_GC */ }
DUK_LOCAL void duk__free_stringtable(duk_heap *heap) { duk_uint_fast32_t i; /* strings are only tracked by stringtable */ #if defined(DUK_USE_HEAPPTR16) if (heap->strtable16) { #else if (heap->strtable) { #endif for (i = 0; i < (duk_uint_fast32_t) heap->st_size; i++) { duk_hstring *e; #if defined(DUK_USE_HEAPPTR16) e = (duk_hstring *) DUK_USE_HEAPPTR_DEC16(heap->strtable16[i]); #else e = heap->strtable[i]; #endif if (e == NULL || e == DUK_STRTAB_DELETED_MARKER(heap)) { continue; } DUK_ASSERT(e != NULL); /* strings may have inner refs (extdata) in some cases */ duk_free_hstring_inner(heap, (duk_hstring *) e); DUK_DDD(DUK_DDDPRINT("FINALFREE (string): %!iO", (duk_heaphdr *) e)); DUK_FREE(heap, e); #if 0 /* not strictly necessary */ heap->strtable[i] = NULL; #endif } #if defined(DUK_USE_HEAPPTR16) DUK_FREE(heap, heap->strtable16); #else DUK_FREE(heap, heap->strtable); #endif #if 0 /* not strictly necessary */ heap->strtable = NULL; #endif } } DUK_LOCAL void duk__free_run_finalizers(duk_heap *heap) { duk_hthread *thr; duk_heaphdr *curr; #ifdef DUK_USE_DEBUG duk_size_t count_obj = 0; #endif DUK_ASSERT(heap != NULL); DUK_ASSERT(heap->heap_thread != NULL); #ifdef DUK_USE_REFERENCE_COUNTING DUK_ASSERT(heap->refzero_list == NULL); /* refzero not running -> must be empty */ #endif #ifdef 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); curr = heap->heap_allocated; while (curr) { 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 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))) { duk_hobject_run_finalizer(thr, (duk_hobject *) curr); } #ifdef DUK_USE_DEBUG count_obj++; #endif } curr = DUK_HEAPHDR_GET_NEXT(curr); } /* Note: count includes all objects, not only those with an actual finalizer. */ #ifdef DUK_USE_DEBUG DUK_D(DUK_DPRINT("checked %ld objects for finalizers before freeing heap", (long) count_obj)); #endif }
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); }