Merge branch 'count-the-things' into we-dont-need-no-backup
diff --git a/src/core/iomgr/iomgr.c b/src/core/iomgr/iomgr.c
index ec31de2..8266b92 100644
--- a/src/core/iomgr/iomgr.c
+++ b/src/core/iomgr/iomgr.c
@@ -37,6 +37,7 @@
 
 #include "src/core/iomgr/iomgr_internal.h"
 #include "src/core/iomgr/alarm_internal.h"
+#include "src/core/support/string.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/thd.h>
@@ -54,8 +55,8 @@
 static delayed_callback *g_cbs_head = NULL;
 static delayed_callback *g_cbs_tail = NULL;
 static int g_shutdown;
-static int g_refs;
 static gpr_event g_background_callback_executor_done;
+static grpc_iomgr_object g_root_object;
 
 /* Execute followup callbacks continuously.
    Other threads may check in and help during pollset_work() */
@@ -96,40 +97,60 @@
   gpr_mu_init(&g_mu);
   gpr_cv_init(&g_rcv);
   grpc_alarm_list_init(gpr_now());
-  g_refs = 0;
+  g_root_object.next = g_root_object.prev = &g_root_object;
+  g_root_object.name = "root";
   grpc_iomgr_platform_init();
   gpr_event_init(&g_background_callback_executor_done);
   gpr_thd_new(&id, background_callback_executor, NULL, NULL);
 }
 
+static size_t count_objects(void) {
+  grpc_iomgr_object *obj;
+  size_t n = 0;
+  for (obj = g_root_object.next; obj != &g_root_object; obj = obj->next) {
+    n++;
+  }
+  return n;
+}
+
 void grpc_iomgr_shutdown(void) {
   delayed_callback *cb;
+  grpc_iomgr_object *obj;
   gpr_timespec shutdown_deadline =
       gpr_time_add(gpr_now(), gpr_time_from_seconds(10));
 
-  grpc_alarm_list_shutdown();
-
   gpr_mu_lock(&g_mu);
   g_shutdown = 1;
-  while (g_cbs_head != NULL || g_refs > 0) {
-    if (g_cbs_head != NULL && g_refs > 0) {
-      gpr_log(GPR_DEBUG, "Waiting for %d iomgr objects to be destroyed and executing final callbacks", g_refs);
+  while (g_cbs_head != NULL || g_root_object.next != &g_root_object) {
+    if (g_cbs_head != NULL && g_root_object.next != &g_root_object) {
+      gpr_log(GPR_DEBUG,
+              "Waiting for %d iomgr objects to be destroyed and executing "
+              "final callbacks",
+              count_objects());
     } else if (g_cbs_head != NULL) {
       gpr_log(GPR_DEBUG, "Executing final iomgr callbacks");
     } else {
-      gpr_log(GPR_DEBUG, "Waiting for %d iomgr objects to be destroyed", g_refs);
+      gpr_log(GPR_DEBUG, "Waiting for %d iomgr objects to be destroyed",
+              count_objects());
     }
-    while (g_cbs_head) {
-      cb = g_cbs_head;
-      g_cbs_head = cb->next;
-      if (!g_cbs_head) g_cbs_tail = NULL;
-      gpr_mu_unlock(&g_mu);
+    if (g_cbs_head) {
+      do {
+        cb = g_cbs_head;
+        g_cbs_head = cb->next;
+        if (!g_cbs_head) g_cbs_tail = NULL;
+        gpr_mu_unlock(&g_mu);
 
-      cb->cb(cb->cb_arg, 0);
-      gpr_free(cb);
-      gpr_mu_lock(&g_mu);
+        cb->cb(cb->cb_arg, 0);
+        gpr_free(cb);
+        gpr_mu_lock(&g_mu);
+      } while (g_cbs_head);
+      continue;
     }
-    if (g_refs) {
+    if (grpc_alarm_check(&g_mu, gpr_inf_future, NULL)) {
+      gpr_log(GPR_DEBUG, "got late alarm");
+      continue;
+    }
+    if (g_root_object.next != &g_root_object) {
       int timeout = 0;
       gpr_timespec short_deadline = gpr_time_add(gpr_now(),
                                                  gpr_time_from_millis(100));
@@ -143,7 +164,10 @@
         gpr_log(GPR_DEBUG,
                 "Failed to free %d iomgr objects before shutdown deadline: "
                 "memory leaks are likely",
-                g_refs);
+                count_objects());
+        for (obj = g_root_object.next; obj != &g_root_object; obj = obj->next) {
+          gpr_log(GPR_DEBUG, "LEAKED OBJECT: %s", obj->name);
+        }
         break;
       }
     }
@@ -153,22 +177,28 @@
   grpc_kick_poller();
   gpr_event_wait(&g_background_callback_executor_done, gpr_inf_future);
 
+  grpc_alarm_list_shutdown();
+
   grpc_iomgr_platform_shutdown();
   gpr_mu_destroy(&g_mu);
   gpr_cv_destroy(&g_rcv);
 }
 
-void grpc_iomgr_ref(void) {
+void grpc_iomgr_register_object(grpc_iomgr_object *obj, const char *name) {
   gpr_mu_lock(&g_mu);
-  ++g_refs;
+  obj->name = gpr_strdup(name);
+  obj->next = &g_root_object;
+  obj->prev = obj->next->prev;
+  obj->next->prev = obj->prev->next = obj;
   gpr_mu_unlock(&g_mu);
 }
 
-void grpc_iomgr_unref(void) {
+void grpc_iomgr_unregister_object(grpc_iomgr_object *obj) {
   gpr_mu_lock(&g_mu);
-  if (0 == --g_refs) {
-    gpr_cv_signal(&g_rcv);
-  }
+  obj->next->prev = obj->prev;
+  obj->prev->next = obj->next;
+  gpr_free(obj->name);
+  gpr_cv_signal(&g_rcv);
   gpr_mu_unlock(&g_mu);
 }