Addressed PR comments and fixed a bug.

We now hold the mutex for both map insertions, to protect
against a concurrent GC that removes from the seconary map
before we can insert into the weak map.
diff --git a/ruby/ext/google/protobuf_c/protobuf.c b/ruby/ext/google/protobuf_c/protobuf.c
index 850f5e9..65263a4 100644
--- a/ruby/ext/google/protobuf_c/protobuf.c
+++ b/ruby/ext/google/protobuf_c/protobuf.c
@@ -259,17 +259,17 @@
 
 // Lambda that will GC entries from the secondary map that are no longer present
 // in the primary map.
-VALUE gc_secondary_map = Qnil;
+VALUE gc_secondary_map_lambda = Qnil;
 ID length;
 
 extern VALUE weak_obj_cache;
 
 static void SecondaryMap_Init() {
   rb_gc_register_address(&secondary_map);
-  rb_gc_register_address(&gc_secondary_map);
+  rb_gc_register_address(&gc_secondary_map_lambda);
   rb_gc_register_address(&secondary_map_mutex);
   secondary_map = rb_hash_new();
-  gc_secondary_map = rb_eval_string(
+  gc_secondary_map_lambda = rb_eval_string(
       "->(secondary, weak) {\n"
       "  secondary.delete_if { |k, v| !weak.key?(v) }\n"
       "}\n");
@@ -287,43 +287,61 @@
 // secondary map that are no longer present in the WeakMap. The logic of
 // how often to perform this GC is an artbirary tuning parameter that
 // represents a straightforward CPU/memory tradeoff.
+//
+// Requires: secondary_map_mutex is held.
 static void SecondaryMap_MaybeGC() {
+  PBRUBY_ASSERT(rb_mutex_locked_p(secondary_map_mutex) == Qtrue);
   size_t weak_len = NUM2ULL(rb_funcall(weak_obj_cache, length, 0));
   size_t secondary_len = RHASH_SIZE(secondary_map);
+  if (secondary_len < weak_len) {
+    // Logically this case should not be possible: a valid entry cannot exist in
+    // the weak table unless there is a corresponding entry in the secondary
+    // table. It should *always* be the case that secondary_len >= weak_len.
+    //
+    // However ObjectSpace::WeakMap#length (and therefore weak_len) is
+    // unreliable: it overreports its true length by including non-live objects.
+    // However these non-live objects are not yielded in iteration, so we may
+    // have previously deleted them from the secondary map in a previous
+    // invocation of SecondaryMap_MaybeGC().
+    //
+    // In this case, we can't measure any waste, so we just return.
+    return;
+  }
   size_t waste = secondary_len - weak_len;
-  PBRUBY_ASSERT(secondary_len >= weak_len);
   // GC if we could remove at least 2000 entries or 20% of the table size
   // (whichever is greater).  Since the cost of the GC pass is O(N), we
   // want to make sure that we condition this on overall table size, to
   // avoid O(N^2) CPU costs.
   size_t threshold = PBRUBY_MAX(secondary_len * 0.2, 2000);
   if (waste > threshold) {
-    rb_funcall(gc_secondary_map, rb_intern("call"), 2, secondary_map, weak_obj_cache);
+    rb_funcall(gc_secondary_map_lambda, rb_intern("call"), 2,
+               secondary_map, weak_obj_cache);
   }
 }
 
-static VALUE SecondaryMap_Get(VALUE key) {
+// Requires: secondary_map_mutex is held by this thread iff create == true.
+static VALUE SecondaryMap_Get(VALUE key, bool create) {
+  PBRUBY_ASSERT(!create || rb_mutex_locked_p(secondary_map_mutex) == Qtrue);
   VALUE ret = rb_hash_lookup(secondary_map, key);
-  if (ret == Qnil) {
-    rb_mutex_lock(secondary_map_mutex);
+  if (ret == Qnil && create) {
     SecondaryMap_MaybeGC();
     ret = rb_eval_string("Object.new");
     rb_hash_aset(secondary_map, key, ret);
-    rb_mutex_unlock(secondary_map_mutex);
   }
   return ret;
 }
 
 #endif
 
-static VALUE ObjectCache_GetKey(const void* key) {
+// Requires: secondary_map_mutex is held by this thread iff create == true.
+static VALUE ObjectCache_GetKey(const void* key, bool create) {
   char buf[sizeof(key)];
   memcpy(&buf, &key, sizeof(key));
   intptr_t key_int = (intptr_t)key;
   PBRUBY_ASSERT((key_int & 3) == 0);
   VALUE ret = LL2NUM(key_int >> 2);
 #if USE_SECONDARY_MAP
-  ret = SecondaryMap_Get(ret);
+  ret = SecondaryMap_Get(ret, create);
 #endif
   return ret;
 }
@@ -347,14 +365,20 @@
 
 void ObjectCache_Add(const void* key, VALUE val) {
   PBRUBY_ASSERT(ObjectCache_Get(key) == Qnil);
-  VALUE key_rb = ObjectCache_GetKey(key);
+#if USE_SECONDARY_MAP
+  rb_mutex_lock(secondary_map_mutex);
+#endif
+  VALUE key_rb = ObjectCache_GetKey(key, true);
   rb_funcall(weak_obj_cache, item_set, 2, key_rb, val);
+#if USE_SECONDARY_MAP
+  rb_mutex_unlock(secondary_map_mutex);
+#endif
   PBRUBY_ASSERT(ObjectCache_Get(key) == val);
 }
 
 // Returns the cached object for this key, if any. Otherwise returns Qnil.
 VALUE ObjectCache_Get(const void* key) {
-  VALUE key_rb = ObjectCache_GetKey(key);
+  VALUE key_rb = ObjectCache_GetKey(key, false);
   return rb_funcall(weak_obj_cache, item_get, 1, key_rb);
 }