Commits

Fuzhou Chen committed 53ef695

[Fix] Add test coverage for recycle(). Fixed a bunch of bugs.

  • Participants
  • Parent commits 7154779

Comments (0)

Files changed (8)

File src/esch_alloc.c

     ESCH_CHECK_PARAM_INTERNAL(log != NULL);
 
     old_buffer = in;
+    /* TODO Make sure realloc always set new allocated memory to NULL */
     if (in != NULL) {
         esch_alloc** obj = (esch_alloc**)((esch_byte*)in - sizeof(esch_alloc*));
         ESCH_CHECK_1((*obj) == alloc, log,

File src/esch_gc.c

 #include "esch_debug.h"
 #include <string.h>
 
-#define ESCH_GC_MARK_INUSE(gc, obj) { \
-    gc->inuse_flags[((size_t)((obj)->gc_id) >> 3)] |= \
-                            (0x1 << ((size_t)((obj)->gc_id) & 0xF));\
+#define ROOT_INDEX 0
+#define MOD8(n) ((size_t)n - (((size_t)n >> 3) << 3))
+#define DIV8(n) ((size_t)n >> 3)
+
+#define ESCH_GC_MARK_INUSE(gc, idx) { \
+    gc->inuse_flags[DIV8(idx)] |= (0x1 << MOD8(idx));\
 }
 
-#define ESCH_GC_IS_MARKED(gc, gc_id) \
-    (gc->inuse_flags[((size_t)(gc_id) >> 3)] & \
-                              (0x1 << ((size_t)(gc_id) & 0xF)))
+#define ESCH_GC_IS_MARKED(gc, idx) \
+    ((gc->inuse_flags[DIV8(idx)] & (0x1 << MOD8(idx))))
 
 const int ESCH_GC_NAIVE_DEFAULT_SLOTS = 4096;
 static esch_error
     ESCH_ASSERT(alloc != NULL && ESCH_IS_VALID_ALLOC(alloc));
     ESCH_ASSERT(log != NULL && ESCH_IS_VALID_LOG(log));
 
-    /* Make sure every managed object is deleted. */
-    for (i = 0; i < gc->slot_count; ++i) {
-        if (ESCH_GC_IS_MARKED(gc, i)) {
-            esch_log_info(log, "delete object[%d] = %x", i,
-                          gc->slots[i].obj);
-            ESCH_ASSERT(ESCH_IS_VALID_OBJECT(gc->slots[i].obj));
-            ESCH_ASSERT(gc->slots[i].obj->gc == gc);
-            gc->slots[i].obj->gc = NULL;
-            gc->slots[i].obj->gc_id = 0;
-            ret = esch_object_delete_i(gc->slots[i].obj);
-            if (ret != ESCH_OK) {
-                esch_log_warn(log,
-                        "gc:recycle: Can't delete object: %x (ignore)",
-                        gc->slots[i].obj);
-            }
-        }
-    }
-
-    ESCH_CHECK_1(ret == ESCH_OK, log,
-                 "gc:attach: FATAL: Can't delete root: %x", obj, ret);
+    /* Perform recycle: Simply remove root and make */
+    ESCH_ASSERT(gc->root == gc->slots[ROOT_INDEX].obj);
+    gc->root = NULL;
+    ret = esch_gc_naive_mark_sweep_recycle_i(gc);
+    ESCH_CHECK(ret == ESCH_OK, log,
+                 "gc:dtor: FATAL: Can't clear objects.", ret);
     /* Always delete root has been deleted. */
-    gc->root->gc = NULL;
-    gc->root->gc_id = 0;
 
     (void)esch_alloc_free(alloc, gc->slots);
     (void)esch_alloc_free(alloc, gc->inuse_flags);
     union esch_object_or_next* new_slots = NULL;
     esch_byte* new_flags = NULL;
     size_t new_offset = 0;
+    esch_object** new_recycle_stack = NULL;
 
     ESCH_ASSERT(gc != NULL);
     ESCH_ASSERT(ESCH_IS_VALID_GC(gc));
     /* Do now allow object switch from one GC system to another.
      * We do this because there's no cheap and clean way to remove an
      * object from esch's GC system. */
-    ret = ESCH_ERROR_INVALID_STATE;
-    ESCH_ASSERT(obj->gc != NULL && obj->gc != gc);
-    ESCH_CHECK_1(obj->gc != NULL && obj->gc != gc, log,
-            "gc:attach: FATAL: switch GC system: obj: %x", obj, ret);
+    ESCH_ASSERT(obj->gc == NULL);
+    ESCH_CHECK_1(obj->gc == NULL, log,
+            "gc:attach: FATAL: switch GC system: obj: %x", obj,
+            ESCH_ERROR_INVALID_STATE);
 
-    if (gc->slots[gc->available_slot_offset].obj == gc->root) {
+    if (gc->slots[gc->usable_slot].obj == gc->root) {
         /* Running out of slots. Need to allocate new */
         int i = 0;
         (void)esch_log_info(log,
                                    (void**)&new_flags);
         ESCH_CHECK(ret == ESCH_OK, log,
                 "gc:attach: FATAL: Can't allocate new flags", ret);
+        ret = esch_alloc_realloc_i(alloc, gc->recycle_stack,
+                                   sizeof(esch_object*) * (new_count + 1),
+                                   (void**)&new_recycle_stack);
+        ESCH_CHECK(ret == ESCH_OK, log,
+                   "GC:attach:Can't allocate new recycle stack", ret);
+
         /* Content of original buffer has been moved to new buffer,
          * Now update availability slot list. */
         new_slots[gc->slot_count].obj = gc->root;
         gc->slot_count = new_count;
         gc->slots = new_slots;
         gc->inuse_flags = new_flags;
+        gc->recycle_stack = new_recycle_stack;
         /* Always count availability chain from last element */
-        gc->available_slot_offset = new_count -1;
+        gc->usable_slot = new_count -1;
 
         new_slots = NULL;
         new_flags = NULL;
     }
-    new_offset = gc->available_slot_offset;
+    new_offset = gc->usable_slot;
     (void)esch_log_info(log,
             "gc:attach: Allocate new slot: id: %d", new_offset);
 
-    gc->available_slot_offset = gc->slots[gc->available_slot_offset].next;
+    gc->usable_slot = gc->slots[gc->usable_slot].next;
     allocated_slot = &(gc->slots[new_offset].obj);
     (*allocated_slot) = obj;
 
 Exit:
     esch_alloc_free_i(alloc, new_slots);
     esch_alloc_free_i(alloc, new_flags);
+    esch_alloc_free_i(alloc, new_recycle_stack);
     return ret;
 }
 
 
     /* Please also note that we don't allocate anything here, because
      * recycle() happens only when system is running out of memory. */
-    esch_log_info(log, "gc:recycle: Trigger GC on root: %x", gc->root);
-    ESCH_ASSERT(ESCH_TYPE_IS_CONTAINER(ESCH_OBJECT_GET_TYPE(gc->root)));
 
     /* This is a classical (and slow) mark-and-sweep algorithm.
      * 1. Mark all objects as deletable, then
      * Meanwhile, recycle_stack is also used to store unused objects
      */
     /* Step 1: Mark every object as deletable. */
-    memset(gc->inuse_flags, 0, sizeof(esch_byte) / 8 * gc->slot_count);
+    memset(gc->inuse_flags, 0, gc->slot_count / 8);
+    /* Step 1.5: Keep non-allocated slots out of deletable list. This
+     * may happen when user triggers recycle() before we are running
+     * out of slots.
+     */
+    if (gc->usable_slot != ROOT_INDEX) {
+        size_t each_index = gc->usable_slot;
+        size_t unused_id = 0;
+        while(each_index != ROOT_INDEX) {
+            unused_id = (&gc->slots[each_index] - &gc->slots[0]);
+            ESCH_GC_MARK_INUSE(gc, unused_id);
+            each_index = gc->slots[each_index].next;
+        }
+    }
     /* 
      * Step 2: Search objects from root node, and mark reachable object
      * as in-use.
      */
-    stack_ptr = &(gc->recycle_stack[0]);
-    (*(stack_ptr++)) = gc->root; /* Root is always in use */
-    do
-    {
-        ret = esch_object_get_iterator_i(*(stack_ptr - 1), &iter);
-        ESCH_ASSERT(ret == ESCH_OK);
-        ESCH_GC_MARK_INUSE(gc, *(stack_ptr - 1));
-        while(ESCH_TRUE) {
-            ret = iter.get_value(&iter, &element);
+    if (gc->root == NULL) {
+        esch_log_info(log, "gc:recycle: Trigger GC from dtor");
+        ESCH_ASSERT(!ESCH_GC_IS_MARKED(gc, 0));
+    } else {
+        esch_log_info(log, "gc:recycle: Trigger GC on root: %x", gc->root);
+        ESCH_ASSERT(ESCH_TYPE_IS_CONTAINER(ESCH_OBJECT_GET_TYPE(gc->root)));
+        stack_ptr = &(gc->recycle_stack[0]);
+        (*stack_ptr) = gc->root; /* Root is always in use */
+        do {
+            ret = esch_object_get_iterator_i(*stack_ptr, &iter);
             ESCH_ASSERT(ret == ESCH_OK);
-            if (element.type == ESCH_ELEMENT_TYPE_END) {
-                esch_log_info(log, "gc:recycle: end of objects.");
-                break;
-            } else if (element.type != ESCH_ELEMENT_TYPE_OBJECT) {
-                esch_log_info(log, "gc:recycle: primitive type, skip.");
-                continue;
+            ESCH_GC_MARK_INUSE(gc, (*stack_ptr)->gc_id);
+            while(ESCH_TRUE) {
+                ret = iter.get_value(&iter, &element);
+                ESCH_ASSERT(ret == ESCH_OK);
+                if (element.type == ESCH_ELEMENT_TYPE_END) {
+                    esch_log_info(log, "gc:recycle: end of objects.");
+                    break;
+                } else if (element.type != ESCH_ELEMENT_TYPE_OBJECT) {
+                    esch_log_info(log, "gc:recycle: primitive type, skip.");
+                    ret = iter.get_next(&iter);
+                    continue;
+                }
+                /* IMPORTANT
+                 * I set assertion because I can't find a way to behave
+                 * correctly if I mix two GC systems in one object system,
+                 * without causing semantic problems.
+                 *
+                 * If anyone know how to do it, please ping me.
+                 */
+                child = element.val.o;
+                ESCH_ASSERT(child != NULL);
+                ESCH_ASSERT(child->gc == gc);
+                element_type = ESCH_OBJECT_GET_TYPE(child);
+                ESCH_ASSERT(element_type != NULL);
+                ESCH_ASSERT(ESCH_IS_VALID_TYPE(element_type));
+                /* Never mark twice so we won't fall into endless
+                 * loop if we hit a reference circle. */
+                if (!ESCH_GC_IS_MARKED(gc, child->gc_id)) {
+                    ESCH_GC_MARK_INUSE(gc, child->gc_id);
+                }
+                if (ESCH_TYPE_IS_CONTAINER(element_type)) {
+                    esch_log_info(log, "gc:recycle: container:stack.");
+                    (*(++stack_ptr)) = child;
+                } else {
+                    esch_log_info(log, "gc:recycle: non-container:mark.");
+                }
+                ret = iter.get_next(&iter);
             }
-            /* IMPORTANT
-             * I set assertion because I can't find a way to behave
-             * correctly if I mix two GC systems in one object system,
-             * without causing semantic problems.
-             *
-             * If anyone know how to do it, please ping me.
-             */
-            child = element.val.o;
-            ESCH_ASSERT(child != NULL);
-            ESCH_ASSERT(child->gc == gc);
-            element_type = ESCH_OBJECT_GET_TYPE(child);
-            ESCH_ASSERT(element_type != NULL);
-            ESCH_ASSERT(ESCH_IS_VALID_TYPE(element_type));
-            if (ESCH_TYPE_IS_CONTAINER(element_type)) {
-                esch_log_info(log, "gc:recycle: container. Mark/stack.");
-                /* Never mark twice so we won't fall into endless loop
-                 * if we hit a reference circle. */
-                if (!ESCH_GC_IS_MARKED(gc, child->gc_id)) {
-                    ESCH_GC_MARK_INUSE(gc, child);
-                    (*(stack_ptr++)) = child;
-                }
-            } else {
-                esch_log_info(log, "gc:recycle: non-container. Mark.");
-                if (!ESCH_GC_IS_MARKED(gc, child->gc_id)) {
-                    ESCH_GC_MARK_INUSE(gc, child);
-                }
-            }
-        }
-    } while(stack_ptr != &(gc->recycle_stack[0]));
-    /* By now we have marked all required objects, free the rest and
-     * rebuild availability slot list. */
-    ESCH_ASSERT(gc->slots[0].obj == gc->root);
-    ESCH_GC_MARK_INUSE(gc, gc->slots[0].obj);
-
-    /* Step 3: Delete all objects marked as deletable. */
-    /* gc->available_slot_offset = 0; */
-    for (free_objs = 0, i = 1; i < gc->slot_count; ++i) {
+            (*(stack_ptr--)) = NULL; // Done for current node.
+        } while(stack_ptr != &(gc->recycle_stack[0]));
+        /* TODO Optimization: Don't always trigger GC so fast. We may
+         * consider cancel GC if the memory slot is enough. But it does
+         * not have to be implemented in GC.
+         */
+        /* By now we have marked all required objects, free the rest and
+         * rebuild availability slot list. */
+        ESCH_ASSERT(gc->slots[ROOT_INDEX].obj == gc->root);
+        ESCH_GC_MARK_INUSE(gc, gc->slots[ROOT_INDEX].obj->gc_id);
+    }
+    /*
+     * Step 3: Delete all objects marked as deletable.
+     * NOTE: When destructor calls recycle(), it directly comes to here,
+     * so all objects are freed, including root.
+     */
+    for (free_objs = 0, i = 0; i < gc->slot_count; ++i) {
         /* Skip i = 0 because first element is root, which is always
          * in use.
          */
                         "gc:recycle: Can't delete object: %x (ignore)",
                         gc->slots[i].obj);
             }
-            gc->slots[i].next = gc->available_slot_offset;
-            gc->available_slot_offset = i;
+            gc->slots[i].next = gc->usable_slot;
+            gc->usable_slot = i;
             ++free_objs;
         }
     }
     log = ESCH_CAST_FROM_OBJECT(ESCH_CONFIG_GET_LOG(config), esch_log);
     root = ESCH_CONFIG_GET_GC_NAIVE_ROOT(config);
 
+    ESCH_ASSERT(ESCH_CONFIG_GET_GC(config) == NULL);
     ESCH_ASSERT(alloc != NULL && ESCH_IS_VALID_ALLOC(alloc));
     ESCH_ASSERT(log != NULL && ESCH_IS_VALID_LOG(log));
     ESCH_ASSERT(root != NULL && ESCH_IS_VALID_OBJECT(root));
     ESCH_CHECK(ret == ESCH_OK, log, "GC:naive_new:Can't create slots", ret);
 
     ret = esch_alloc_realloc_i(alloc, NULL,
-                    sizeof(esch_object*) * initial_slots,
+                    sizeof(esch_object*) * (initial_slots + 1),
                     (void**)&recycle_stack);
     ESCH_CHECK(ret == ESCH_OK, log, "GC:naive_new:Can't create stack", ret);
 
      * slots can be returned in different order when it's returned. */
 
     /* First object is reversed as END_OF_LIST mark. Others are linked. */
-    slots[0].obj = root;
+    slots[ROOT_INDEX].obj = root;
     for(i = 1; i < initial_slots - 1; ++i) {
         slots[i + 1].next = i;
     }
 
     /* Always start from last slot */
-    new_gc->available_slot_offset = (initial_slots - 1);
+    new_gc->usable_slot = (initial_slots - 1);
     new_gc->attach = esch_gc_naive_mark_sweep_attach_i;
     new_gc->recycle = esch_gc_naive_mark_sweep_recycle_i;
     new_gc->inuse_flags = inuse_flags;
     new_gc->slot_count = initial_slots;
     /* Let GC manage root */
     root->gc = new_gc;
-    root->gc_id = (void*)0;
+    root->gc_id = (void*)ROOT_INDEX;
     new_gc->root = root;
-    ESCH_GC_MARK_INUSE(new_gc, root);
+    ESCH_GC_MARK_INUSE(new_gc, root->gc_id);
 
     (*gc) = new_obj;
 
 }
 
 esch_error
+esch_gc_recycle(esch_gc* gc)
+{
+    esch_error ret = ESCH_OK;
+    ESCH_CHECK_PARAM_PUBLIC(gc != NULL);
+    ESCH_CHECK_PARAM_PUBLIC(ESCH_IS_VALID_GC(gc));
+
+    ret = esch_gc_naive_mark_sweep_recycle_i(gc);
+Exit:
+    return ret;
+}
+
+esch_error
 esch_gc_recycle_i(esch_gc* gc)
 {
     esch_error ret = ESCH_OK;

File src/esch_gc.h

 union esch_object_or_next
 {
     esch_object* obj;
-    size_t next;
+    size_t next; /* Index to next object */
 };
 
 /*
  * Step 3 starts after step 2. It visits every bit of inuse_flags, and
  * delete the corresponding objects in `slots' array, if it's not marked
  * as in-use. After the object is deleted, the slot it takes is returned
- * to linked list head by `available_slot_offset'.
+ * to linked list head by `usable_slot'.
  *
  * Data structure used in steps:
  *
  *
  * The `slots' array is not full all the time. To manage the unallocated
  * slots without resizing/moving, the available slots are constructed as
- * an linked list, refereneced by its index. The `available_slot_offset'
+ * an linked list, refereneced by its index. The `usable_slot'
  * field is introduced, indicate the beginning of available slots. The
- * value of `available_slot_offset' always starts from last element in
+ * value of `usable_slot' always starts from last element in
  * `slots' array.
  *
  * NOTE: the first element in `slots' array is always root.
     union esch_object_or_next* slots;
     esch_object** recycle_stack;
     esch_object*  root;
-    size_t available_slot_offset;
+    size_t usable_slot;
     size_t slot_count;
 };
 
      (gc)->inuse_flags != NULL && \
      (gc)->slots != NULL && \
      (gc)->recycle_stack != NULL && \
-     (gc)->available_slot_offset > 0 && \
-     (gc)->available_slot_offset < (gc)->slot_count)
+     (gc)->usable_slot > 0 && \
+     (gc)->usable_slot < (gc)->slot_count)
 
 #ifdef __cplusplus
 }

File src/esch_object.c

      */
     obj_size = sizeof(esch_object) + ESCH_TYPE_GET_OBJECT_SIZE(type);
     ret = esch_alloc_realloc(alloc, NULL, obj_size, (void**)&new_object);
+
     if (gc != NULL)
     {
         ESCH_CHECK_1((ret == ESCH_OK || ret == ESCH_ERROR_OUT_OF_MEMORY),
                          "FATAL: Can't malloc after GC. type: 0x%x",
                          type, ret);
         }
+        ESCH_OBJECT_GET_TYPE(new_object)  = type;
+        ESCH_OBJECT_GET_ALLOC(new_object) = alloc;
+        ESCH_OBJECT_GET_LOG(new_object)   = log;
         (void)esch_log_info(log, "object:new: Try attach GC.");
         ret = esch_gc_attach_i(gc, new_object);
         ESCH_CHECK_1(ret == ESCH_OK, log,
         ESCH_CHECK_1(ret == ESCH_OK, log,
                 "object:new: Can't malloc (without GC). type: 0x%x",
                 type, ret);
+        ESCH_OBJECT_GET_TYPE(new_object)  = type;
+        ESCH_OBJECT_GET_ALLOC(new_object) = alloc;
+        ESCH_OBJECT_GET_LOG(new_object)   = log;
     }
 
     /*
      * Final assignment. Make sure the object is initialized without any
      * risks of partial initialization.
      */
-    ESCH_OBJECT_GET_TYPE(new_object)    = type;
-    ESCH_OBJECT_GET_ALLOC(new_object)   = alloc;
-    ESCH_OBJECT_GET_LOG(new_object)     = log;
     /* ESCH_OBJECT_GET_GC(new_object) is already assigned. */
     /* ESCH_OBJECT_GET_GC_ID(new_object) is already assigned. */
-
     (*obj) = new_object;
     new_object = NULL;
 Exit:

File src/esch_type.c

         NULL,
     },
     {
+        ESCH_VERSION,
         sizeof(esch_type),
-        ESCH_VERSION,
         esch_type_new_s,
         esch_type_delete_s,
         esch_type_default_non_copiable,
     log = ESCH_CAST_FROM_OBJECT(log_obj, esch_log);
 
     ret = esch_object_new_i(config, &(esch_meta_type.type), &new_obj);
-    ESCH_CHECK(new_type != NULL, log, "Can't alloc new type.", ret);
+    ESCH_CHECK(new_obj != NULL, log, "Can't alloc new type", ret);
     new_type = ESCH_CAST_FROM_OBJECT(new_obj, esch_type);
 
     ESCH_TYPE_GET_OBJECT_SIZE(new_type)          = sizeof(esch_type);

File src/utest/esch_t_gc.c

 #include "esch_utest.h"
 #include "esch_gc.h"
 #include "esch_vector.h"
+#include "esch_config.h"
 
 esch_error test_gcCreateDelete(esch_config* config)
 {
 Exit:
     return ret;
 }
+esch_error test_gcRecycleLogic(esch_config* config)
+{
+    esch_error ret = ESCH_OK;
+    esch_gc* gc = NULL;
+    esch_gc* bad_gc = NULL;
+    esch_vector* root_scope = NULL;
+    esch_vector* child_scope_1_1 = NULL;
+    esch_vector* child_scope_1_2 = NULL;
+    esch_vector* child_scope_2_1 = NULL;
+    esch_string* str1 = NULL;
+    esch_type* tt = NULL;
+    esch_type* tt2 = NULL;
+    esch_vector* circle1 = NULL;
+    esch_vector* circle2 = NULL;
+    esch_vector* circle3 = NULL;
+    esch_vector* circle4 = NULL;
+
+    /*
+     * Test scenario:
+     * 0. esch_object should honor gc object in config.
+     * 1. gc should never be managed by another gc.
+     * 2. A scope may hold non-container object. Not be freed.
+     * 3. A scope may hold child container. Must be visited.
+     * 4. A child scope may hold child container. Must be visited.
+     * 5. Reference circle should not affect releasing.
+     */
+    ret = esch_vector_new(config, &root_scope);
+    ESCH_TEST_CHECK(ret == ESCH_OK && root_scope,
+                    "Failed to create gc root", ret);
+    ret = esch_config_set_obj(config, ESCH_CONFIG_KEY_GC_NAIVE_ROOT,
+                              ESCH_CAST_TO_OBJECT(root_scope));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to set gc root", ret);
+
+    ret = esch_gc_new_naive_mark_sweep(config, &gc);
+    ESCH_TEST_CHECK(ret == ESCH_OK && gc, "Failed to create gc", ret);
+
+    ret = esch_config_set_obj(config, ESCH_CONFIG_KEY_GC,
+                              ESCH_CAST_TO_OBJECT(gc));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to set gc", ret);
+
+    ret = esch_gc_new_naive_mark_sweep(config, &bad_gc);
+    ESCH_TEST_CHECK(ret == ESCH_ERROR_OBJECT_UNEXPECTED_GC_ATTACHED
+                        && bad_gc == NULL,
+                    "Expect a failure when creating GC with GC", ret);
+
+    /* Prepare environment. */
+
+    /* NOTE: This is safe because public interface will not expose
+     * definition of esch_object.
+     */
+    esch_log_info(g_testLog,
+            "0. GC setting shall be picked up with new object.");
+    ret = esch_string_new_from_utf8(config, "hello", 0, -1, &str1);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create string", ret);
+    ESCH_TEST_CHECK(ESCH_CAST_TO_OBJECT(str1)->gc == gc,
+                    "GC is not attached", ret);
+
+    esch_log_info(g_testLog, "2. Scope may hold non-conainer types.");
+    ret = esch_type_new(config, &tt);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create tt", ret);
+    ret = esch_vector_append(root_scope, ESCH_CAST_TO_OBJECT(tt));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to touch tt to scope", ret);
+    ret = esch_type_new(config, &tt2);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create tt2", ret);
+    /* Structure:
+     *
+     * root_scope -
+     *     tt
+     *     child_scope_1_1 - (scope with container, but no children)
+     *         str1
+     *     child_scope_1_2 - (scepe with container, and has children)
+     *         child_scope_2_1 -
+     *             circle1 and circle2 form a refernece circle.
+     * circle3 and circle4 form a refernece circle.
+     * tt2
+     */
+    esch_log_info(g_testLog, "3. Scope may hold container objects.");
+    ret = esch_vector_new(config, &child_scope_1_1);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create scope11", ret);
+    ret = esch_vector_append(root_scope, ESCH_CAST_TO_OBJECT(child_scope_1_1));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append scope11", ret);
+
+    ret = esch_vector_new(config, &child_scope_1_2);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create scope12", ret);
+    ret = esch_vector_append(root_scope, ESCH_CAST_TO_OBJECT(child_scope_1_2));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append scope12", ret);
+
+    ret = esch_vector_new(config, &child_scope_2_1);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create scope21", ret);
+    ret = esch_vector_append(child_scope_1_1, ESCH_CAST_TO_OBJECT(str1));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append scope12", ret);
+
+
+    esch_log_info(g_testLog, "4. A child scope may hold child container.");
+    ret = esch_vector_append(child_scope_1_2,
+                             ESCH_CAST_TO_OBJECT(child_scope_2_1));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append scope21", ret);
+
+
+    esch_log_info(g_testLog,
+            "5. Reference circle should not affect releasing.");
+    ret = esch_vector_new(config, &circle1);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create circle1", ret);
+    ret = esch_vector_new(config, &circle2);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create circle2", ret);
+    ret = esch_vector_new(config, &circle3);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create circle3", ret);
+    ret = esch_vector_new(config, &circle4);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to create circle4", ret);
+
+    ret = esch_vector_append(child_scope_2_1, ESCH_CAST_TO_OBJECT(circle1));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append circle2", ret);
+
+    ret = esch_vector_append(circle1, ESCH_CAST_TO_OBJECT(circle2));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append circle2", ret);
+    ret = esch_vector_append(circle2, ESCH_CAST_TO_OBJECT(circle1));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append circle1", ret);
+
+    ret = esch_vector_append(circle3, ESCH_CAST_TO_OBJECT(circle4));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append circle4", ret);
+    ret = esch_vector_append(circle4, ESCH_CAST_TO_OBJECT(circle3));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to append circle3", ret);
+
+    /* Now we can really start. */
+    ret = esch_gc_recycle(gc);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to do recycle", ret);
+    /* After here, we should verify:
+     * str1 is deleted.
+     * circle3 and circle4 are deleted.
+     */
+    ret = esch_object_delete(ESCH_CAST_TO_OBJECT(gc));
+    ESCH_TEST_CHECK(ret == ESCH_OK, "Failed to delete GC", ret);
+
+    gc = NULL;
+
+    /* TODO Need a callback system in GC to verify object deleting. */
+    /* TODO Need method for vector to clear or remove single element. */
+    /* TODO Make string become container. */
+    /* TODO We should add case when list object is implemented.  */
+    /* TODO Stack full.  */
+Exit:
+    if (gc != NULL)
+    {
+        (void)esch_config_set_obj(config, ESCH_CONFIG_KEY_GC, NULL);
+        (void)esch_config_set_obj(config, ESCH_CONFIG_KEY_GC_NAIVE_ROOT, NULL);
+        ret = esch_object_delete(ESCH_CAST_TO_OBJECT(gc));
+    }
+    return ret;
+}

File src/utest/esch_utest.c

     ESCH_TEST_CHECK(ret == ESCH_OK, "test_gcCreateDelete() failed", ret);
     esch_log_info(g_testLog, "[PASSED] test_gcCreateDelete()");
 
+    esch_log_info(testLog, "Start: test_gcRecycleLogic()");
+    ret = test_gcRecycleLogic(config);
+    ESCH_TEST_CHECK(ret == ESCH_OK, "test_gcRecycleLogic() failed", ret);
+    esch_log_info(testLog, "[PASSED]: test_gcRecycleLogic()");
+
     /*
     ret = test_config();
     ESCH_TEST_CHECK(ret == ESCH_OK, "test_config() failed", ret);

File src/utest/esch_utest.h

 extern esch_error test_vectorIteration(esch_config* config);
 extern esch_error test_integer();
 extern esch_error test_gcCreateDelete(esch_config* config);
+extern esch_error test_gcRecycleLogic(esch_config* config);
 
 #ifdef __cplusplus
 }