Merge pull request #3634 from TeBoring/ruby-bug

Cherry pick bug fix for ruby
diff --git a/ruby/ext/google/protobuf_c/encode_decode.c b/ruby/ext/google/protobuf_c/encode_decode.c
index 6ce6d08..edbbe6a 100644
--- a/ruby/ext/google/protobuf_c/encode_decode.c
+++ b/ruby/ext/google/protobuf_c/encode_decode.c
@@ -280,11 +280,6 @@
   { MapParseFrame_mark, MapParseFrame_free, NULL },
 };
 
-// Array of Ruby objects wrapping map_parse_frame_t.
-// We don't allow multiple concurrent decodes, so we assume that this global
-// variable is specific to the "current" decode.
-VALUE map_parse_frames;
-
 static map_parse_frame_t* map_push_frame(VALUE map,
                                          const map_handlerdata_t* handlerdata) {
   map_parse_frame_t* frame = ALLOC(map_parse_frame_t);
@@ -293,16 +288,12 @@
   native_slot_init(handlerdata->key_field_type, &frame->key_storage);
   native_slot_init(handlerdata->value_field_type, &frame->value_storage);
 
-  rb_ary_push(map_parse_frames,
+  Map_set_frame(map,
               TypedData_Wrap_Struct(rb_cObject, &MapParseFrame_type, frame));
 
   return frame;
 }
 
-static void map_pop_frame() {
-  rb_ary_pop(map_parse_frames);
-}
-
 // Handler to begin a map entry: allocates a temporary frame. This is the
 // 'startsubmsg' handler on the msgdef that contains the map field.
 static void *startmapentry_handler(void *closure, const void *hd) {
@@ -336,7 +327,7 @@
       &frame->value_storage);
 
   Map_index_set(frame->map, key, value);
-  map_pop_frame();
+  Map_set_frame(frame->map, Qnil);
 
   return true;
 }
@@ -775,10 +766,6 @@
   msg_rb = rb_class_new_instance(0, NULL, msgklass);
   TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg);
 
-  // We generally expect this to be clear already, but clear it in case parsing
-  // previously got interrupted somehow.
-  rb_ary_clear(map_parse_frames);
-
   {
     const upb_pbdecodermethod* method = msgdef_decodermethod(desc);
     const upb_handlers* h = upb_pbdecodermethod_desthandlers(method);
@@ -823,10 +810,6 @@
   msg_rb = rb_class_new_instance(0, NULL, msgklass);
   TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg);
 
-  // We generally expect this to be clear already, but clear it in case parsing
-  // previously got interrupted somehow.
-  rb_ary_clear(map_parse_frames);
-
   {
     const upb_json_parsermethod* method = msgdef_jsonparsermethod(desc);
     stackenv se;
diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c
index 4be54c3..26e22dc 100644
--- a/ruby/ext/google/protobuf_c/map.c
+++ b/ruby/ext/google/protobuf_c/map.c
@@ -146,6 +146,7 @@
   Map* self = _self;
 
   rb_gc_mark(self->value_type_class);
+  rb_gc_mark(self->parse_frame);
 
   if (self->value_type == UPB_TYPE_STRING ||
       self->value_type == UPB_TYPE_BYTES ||
@@ -174,6 +175,12 @@
   return TypedData_Wrap_Struct(klass, &Map_type, self);
 }
 
+VALUE Map_set_frame(VALUE map, VALUE val) {
+  Map* self = ruby_to_Map(map);
+  self->parse_frame = val;
+  return val;
+}
+
 static bool needs_typeclass(upb_fieldtype_t type) {
   switch (type) {
     case UPB_TYPE_MESSAGE:
@@ -227,6 +234,7 @@
 
   self->key_type = ruby_to_fieldtype(argv[0]);
   self->value_type = ruby_to_fieldtype(argv[1]);
+  self->parse_frame = Qnil;
 
   // Check that the key type is an allowed type.
   switch (self->key_type) {
diff --git a/ruby/ext/google/protobuf_c/protobuf.c b/ruby/ext/google/protobuf_c/protobuf.c
index 9896366..7cde4ae 100644
--- a/ruby/ext/google/protobuf_c/protobuf.c
+++ b/ruby/ext/google/protobuf_c/protobuf.c
@@ -112,6 +112,4 @@
 
   upb_def_to_ruby_obj_map = rb_hash_new();
   rb_gc_register_address(&upb_def_to_ruby_obj_map);
-  map_parse_frames = rb_ary_new();
-  rb_gc_register_address(&map_parse_frames);
 }
diff --git a/ruby/ext/google/protobuf_c/protobuf.h b/ruby/ext/google/protobuf_c/protobuf.h
index 520e9d9..f4b110f 100644
--- a/ruby/ext/google/protobuf_c/protobuf.h
+++ b/ruby/ext/google/protobuf_c/protobuf.h
@@ -166,8 +166,6 @@
 extern VALUE cError;
 extern VALUE cParseError;
 
-extern VALUE map_parse_frames;
-
 // We forward-declare all of the Ruby method implementations here because we
 // sometimes call the methods directly across .c files, rather than going
 // through Ruby's method dispatching (e.g. during message parse). It's cleaner
@@ -397,6 +395,7 @@
   upb_fieldtype_t key_type;
   upb_fieldtype_t value_type;
   VALUE value_type_class;
+  VALUE parse_frame;
   upb_strtable table;
 } Map;
 
@@ -405,6 +404,7 @@
 VALUE Map_alloc(VALUE klass);
 VALUE Map_init(int argc, VALUE* argv, VALUE self);
 void Map_register(VALUE module);
+VALUE Map_set_frame(VALUE self, VALUE val);
 
 extern const rb_data_type_t Map_type;
 extern VALUE cMap;
diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb
index 020effb..94071ca 100644
--- a/ruby/tests/basic.rb
+++ b/ruby/tests/basic.rb
@@ -96,8 +96,18 @@
         optional :d, :enum, 4, "TestEnum"
       end
     end
+
+    add_message "repro.Outer" do
+      map :items, :int32, :message, 1, "repro.Inner"
+    end
+
+    add_message "repro.Inner" do
+    end
   end
 
+
+  Outer = pool.lookup("repro.Outer").msgclass
+  Inner = pool.lookup("repro.Inner").msgclass
   Foo = pool.lookup("Foo").msgclass
   Bar = pool.lookup("Bar").msgclass
   Baz = pool.lookup("Baz").msgclass
@@ -675,6 +685,21 @@
       m.map_string_int32['aaa'] = 3
     end
 
+    def test_concurrent_decoding
+      o = Outer.new
+      o.items[0] = Inner.new
+      raw = Outer.encode(o)
+
+      thds = 2.times.map do
+        Thread.new do
+          100000.times do
+            assert_equal o, Outer.decode(raw)
+          end
+        end
+      end
+      thds.map(&:join)
+    end
+
     def test_map_encode_decode
       m = MapMessage.new(
         :map_string_int32 => {"a" => 1, "b" => 2},