Make sure that destructors are called when objects are garbage collected
This commit is contained in:
		| @ -588,6 +588,60 @@ static bool free_roots_foreach(void *obj, void *ignored, void *user_data) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @struct ContexAndFlag | ||||||
|  |  * #RefcountContext and a boolean flag. | ||||||
|  |  */ | ||||||
|  | struct ContextAndFlag { | ||||||
|  |     const RefcountContext *ctx; //!< The context. | ||||||
|  |     bool flag; //!< The flag. | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Call the destructors for an object. Used from #call_destructors_for_gc. | ||||||
|  |  * @param obj The object | ||||||
|  |  * @param ignored Ignored | ||||||
|  |  * @param ctx_raw A #ContexAndFlag. The flag is set to the same value as the | ||||||
|  |  * return value. | ||||||
|  |  * @return True if the object's reference count increased above 0 | ||||||
|  |  */ | ||||||
|  | static bool call_destructors_for_gc_callback(void *obj, void *ignored, | ||||||
|  |                                              void *ctx_and_flag_raw) { | ||||||
|  |     struct ContextAndFlag *ctx_and_flag = ctx_and_flag_raw; | ||||||
|  |     const RefcountContext *ctx = ctx_and_flag->ctx; | ||||||
|  |     uint64_t old_ref_count = ENTRY->impl.counted.ref_count; | ||||||
|  |     ENTRY->impl.counted.ref_count = 0; | ||||||
|  |     call_object_destructors(ctx, obj); | ||||||
|  |     ctx_and_flag->flag = ENTRY->impl.counted.ref_count; | ||||||
|  |     if (!ENTRY->impl.counted.ref_count) { | ||||||
|  |         // the object can still be saved by another object in the reference loop | ||||||
|  |         // having its refcount incremented. Therefore, we restore the original | ||||||
|  |         // reference count. | ||||||
|  |         ENTRY->impl.counted.ref_count = old_ref_count; | ||||||
|  |     } | ||||||
|  |     return ctx_and_flag->flag; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Call destructors for a hash table where the keys are objects. Stop if any | ||||||
|  |  * object had it's reference count increase past 0. | ||||||
|  |  * | ||||||
|  |  * > Calling this will set the reference count of all objects in counts to 0! | ||||||
|  |  * | ||||||
|  |  * @param ctx The #RefcountContext | ||||||
|  |  * @param counts The table for which to call destructors | ||||||
|  |  * @return True if an object had its reference count increase above 0. | ||||||
|  |  */ | ||||||
|  | static bool call_destructors_for_gc(const RefcountContext *ctx, | ||||||
|  |                                     HTTable *counts) { | ||||||
|  |     struct ContextAndFlag ctx_and_flag = { | ||||||
|  |         .ctx = ctx, | ||||||
|  |         .flag = false, | ||||||
|  |     }; | ||||||
|  |     ht_foreach(counts, call_destructors_for_gc_callback, &ctx_and_flag); | ||||||
|  |     return ctx_and_flag.flag; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Check the root pointed to by the double pointer root_ptr. After the call, |  * Check the root pointed to by the double pointer root_ptr. After the call, | ||||||
|  * root_ptr is set to the next root to be checked. |  * root_ptr is set to the next root to be checked. | ||||||
| @ -634,18 +688,25 @@ static ptrdiff_t check_gc_root(RefcountContext *ctx, RefcountList **root_ptr) { | |||||||
|             obj_held_refs(ctx, obj, &queue); |             obj_held_refs(ctx, obj, &queue); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if (seen_objects == clear_objects) { |     ptrdiff_t freed_count = 0; | ||||||
|  |     if (seen_objects == clear_objects | ||||||
|  |         && !call_destructors_for_gc(ctx, counts)) { | ||||||
|  |         // all objects still have a refcount of zero, even after calling | ||||||
|  |         // destructors, proceed with freeing them | ||||||
|         struct ContextAndRootPtr data = { |         struct ContextAndRootPtr data = { | ||||||
|             .ctx = ctx, .root_ptr = root_ptr, .did_update = false}; |             .ctx = ctx, .root_ptr = root_ptr, .did_update = false}; | ||||||
|         ht_foreach(counts, free_roots_foreach, &data); |         ht_foreach(counts, free_roots_foreach, &data); | ||||||
|         if (!data.did_update) { |         if (!data.did_update) { | ||||||
|             *root_ptr = (*root_ptr)->next; |             *root_ptr = (*root_ptr)->next; | ||||||
|         } |         } | ||||||
|  |         freed_count = seen_objects; | ||||||
|     } else { |     } else { | ||||||
|  |         // either something still had a reference, or it was re-refed by its | ||||||
|  |         // destructor. Either way, don't free anything | ||||||
|         *root_ptr = (*root_ptr)->next; |         *root_ptr = (*root_ptr)->next; | ||||||
|     } |     } | ||||||
|     ht_free(counts); |     ht_free(counts); | ||||||
|     return seen_objects == clear_objects ? seen_objects : 0; |     return freed_count; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -114,6 +114,9 @@ int main(int argc, const char **argv) { | |||||||
|     refcount_context_ref(c, a); |     refcount_context_ref(c, a); | ||||||
|     refcount_context_unref(c, a); |     refcount_context_unref(c, a); | ||||||
|     assert(refcount_context_num_refs(c, a) == 1); |     assert(refcount_context_num_refs(c, a) == 1); | ||||||
|  |     int key; | ||||||
|  |     A *a_with_destructor = a; | ||||||
|  |     assert(refcount_context_add_destructor(c, a, &key, reref_destructor, c)); | ||||||
|  |  | ||||||
|     a = NULL; |     a = NULL; | ||||||
|     for (char i = 'a'; i <= 'z'; ++i) { |     for (char i = 'a'; i <= 'z'; ++i) { | ||||||
| @ -124,6 +127,9 @@ int main(int argc, const char **argv) { | |||||||
|     assert(refcount_context_num_refs(c, a) == 0); |     assert(refcount_context_num_refs(c, a) == 0); | ||||||
|     refcount_context_unref(c, a); |     refcount_context_unref(c, a); | ||||||
|  |  | ||||||
|  |     assert(refcount_context_garbage_collect(c) == 0); | ||||||
|  |     assert(refcount_context_num_refs(c, a_with_destructor) == 1); | ||||||
|  |     assert(refcount_context_remove_destructor(c, a_with_destructor, &key)); | ||||||
|     assert(refcount_context_garbage_collect(c) == 26); |     assert(refcount_context_garbage_collect(c) == 26); | ||||||
|  |  | ||||||
|     a = make_a(c, 0, "a"); |     a = make_a(c, 0, "a"); | ||||||
| @ -162,7 +168,6 @@ int main(int argc, const char **argv) { | |||||||
|  |  | ||||||
|     a = make_a(c, 10, "test destructor"); |     a = make_a(c, 10, "test destructor"); | ||||||
|     assert(refcount_context_num_refs(c, a) == 0); |     assert(refcount_context_num_refs(c, a) == 0); | ||||||
|     int key; |  | ||||||
|     assert(refcount_context_add_destructor(c, a, &key, reref_destructor, c)); |     assert(refcount_context_add_destructor(c, a, &key, reref_destructor, c)); | ||||||
|     assert(refcount_context_num_refs(c, a) == 0); |     assert(refcount_context_num_refs(c, a) == 0); | ||||||
|     assert(!refcount_context_unref(c, a)); |     assert(!refcount_context_unref(c, a)); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user