Skip to content

Commit

Permalink
Moved ObjectCache into an internal module.
Browse files Browse the repository at this point in the history
This type has always been internal-only, but moving it to a module named `Internal` will make this clearer.

PiperOrigin-RevId: 606649281
  • Loading branch information
haberman authored and zhangskz committed Feb 14, 2024
1 parent 62e7a56 commit 9087337
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 115 deletions.
11 changes: 6 additions & 5 deletions ruby/ext/google/protobuf_c/protobuf.c
Expand Up @@ -241,16 +241,17 @@ static void ObjectCache_Init(VALUE protobuf) {
item_try_add = rb_intern("try_add");

rb_gc_register_address(&weak_obj_cache);
VALUE internal = rb_const_get(protobuf, rb_intern("Internal"));
#if SIZEOF_LONG >= SIZEOF_VALUE
VALUE cache_class = rb_const_get(protobuf, rb_intern("ObjectCache"));
VALUE cache_class = rb_const_get(internal, rb_intern("ObjectCache"));
#else
VALUE cache_class = rb_const_get(protobuf, rb_intern("LegacyObjectCache"));
VALUE cache_class = rb_const_get(internal, rb_intern("LegacyObjectCache"));
#endif

weak_obj_cache = rb_class_new_instance(0, NULL, cache_class);
rb_const_set(protobuf, rb_intern("OBJECT_CACHE"), weak_obj_cache);
rb_const_set(protobuf, rb_intern("SIZEOF_LONG"), INT2NUM(SIZEOF_LONG));
rb_const_set(protobuf, rb_intern("SIZEOF_VALUE"), INT2NUM(SIZEOF_VALUE));
rb_const_set(internal, rb_intern("OBJECT_CACHE"), weak_obj_cache);
rb_const_set(internal, rb_intern("SIZEOF_LONG"), INT2NUM(SIZEOF_LONG));
rb_const_set(internal, rb_intern("SIZEOF_VALUE"), INT2NUM(SIZEOF_VALUE));
}

static VALUE ObjectCache_GetKey(const void *key) {
Expand Down
2 changes: 1 addition & 1 deletion ruby/lib/google/protobuf.rb
Expand Up @@ -7,7 +7,7 @@

# require mixins before we hook them into the java & c code
require 'google/protobuf/message_exts'
require 'google/protobuf/object_cache'
require 'google/protobuf/internal/object_cache'

# We define these before requiring the platform-specific modules.
# That way the module init can grab references to these.
Expand Down
6 changes: 3 additions & 3 deletions ruby/lib/google/protobuf/ffi/object_cache.rb
Expand Up @@ -18,13 +18,13 @@ def self.interpreter_supports_non_finalized_keys_in_weak_map?

def self.cache_implementation
if interpreter_supports_non_finalized_keys_in_weak_map? and SIZEOF_LONG >= SIZEOF_VALUE
Google::Protobuf::ObjectCache
Google::Protobuf::Internal::ObjectCache
else
Google::Protobuf::LegacyObjectCache
Google::Protobuf::Internal::LegacyObjectCache
end
end

public
OBJECT_CACHE = cache_implementation.new
end
end
end
99 changes: 99 additions & 0 deletions ruby/lib/google/protobuf/internal/object_cache.rb
@@ -0,0 +1,99 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2023 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd

module Google
module Protobuf
module Internal
# A pointer -> Ruby Object cache that keeps references to Ruby wrapper
# objects. This allows us to look up any Ruby wrapper object by the address
# of the object it is wrapping. That way we can avoid ever creating two
# different wrapper objects for the same C object, which saves memory and
# preserves object identity.
#
# We use WeakMap for the cache. If sizeof(long) > sizeof(VALUE), we also
# need a secondary Hash to store WeakMap keys, because our pointer keys may
# need to be stored as Bignum instead of Fixnum. Since WeakMap is weak for
# both keys and values, a Bignum key will cause the WeakMap entry to be
# collected immediately unless there is another reference to the Bignum.
# This happens on 64-bit Windows, on which pointers are 64 bits but longs
# are 32 bits. In this case, we enable the secondary Hash to hold the keys
# and prevent them from being collected.
class ObjectCache
def initialize
@map = ObjectSpace::WeakMap.new
@mutex = Mutex.new
end

def get(key)
@map[key]
end

def try_add(key, value)
@map[key] || @mutex.synchronize do
@map[key] ||= value
end
end
end

class LegacyObjectCache
def initialize
@secondary_map = {}
@map = ObjectSpace::WeakMap.new
@mutex = Mutex.new
end

def get(key)
value = if secondary_key = @secondary_map[key]
@map[secondary_key]
else
@mutex.synchronize do
@map[(@secondary_map[key] ||= Object.new)]
end
end

# 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.
cutoff = (@secondary_map.size * 0.2).ceil
cutoff = 2_000 if cutoff < 2_000
if (@secondary_map.size - @map.size) > cutoff
purge
end

value
end

def try_add(key, value)
if secondary_key = @secondary_map[key]
if old_value = @map[secondary_key]
return old_value
end
end

@mutex.synchronize do
secondary_key ||= (@secondary_map[key] ||= Object.new)
@map[secondary_key] ||= value
end
end

private

def purge
@mutex.synchronize do
@secondary_map.each do |key, secondary_key|
unless @map.key?(secondary_key)
@secondary_map.delete(key)
end
end
end
nil
end
end
end
end
end
97 changes: 0 additions & 97 deletions ruby/lib/google/protobuf/object_cache.rb

This file was deleted.

17 changes: 8 additions & 9 deletions ruby/tests/object_cache_test.rb
Expand Up @@ -3,11 +3,11 @@

class PlatformTest < Test::Unit::TestCase
def test_correct_implementation_for_platform
omit('OBJECT_CACHE not defined') unless defined? Google::Protobuf::OBJECT_CACHE
if Google::Protobuf::SIZEOF_LONG >= Google::Protobuf::SIZEOF_VALUE and not defined? JRUBY_VERSION
assert_instance_of Google::Protobuf::ObjectCache, Google::Protobuf::OBJECT_CACHE
omit('OBJECT_CACHE not defined') unless defined? Google::Protobuf::Internal::OBJECT_CACHE
if Google::Protobuf::Internal::SIZEOF_LONG >= Google::Protobuf::Internal::SIZEOF_VALUE and not defined? JRUBY_VERSION
assert_instance_of Google::Protobuf::Internal::ObjectCache, Google::Protobuf::Internal::OBJECT_CACHE
else
assert_instance_of Google::Protobuf::LegacyObjectCache, Google::Protobuf::OBJECT_CACHE
assert_instance_of Google::Protobuf::Internal::LegacyObjectCache, Google::Protobuf::Internal::OBJECT_CACHE
end
end
end
Expand Down Expand Up @@ -47,19 +47,18 @@ def test_multithreaded_access

class ObjectCacheTest < Test::Unit::TestCase
def create
omit('OBJECT_CACHE not defined') unless defined? Google::Protobuf::OBJECT_CACHE
Google::Protobuf::ObjectCache.new
omit('OBJECT_CACHE not defined') unless defined? Google::Protobuf::Internal::OBJECT_CACHE
Google::Protobuf::Internal::ObjectCache.new
end

include ObjectCacheTestModule
end

class LegacyObjectCacheTest < Test::Unit::TestCase
def create
omit('OBJECT_CACHE not defined') unless defined? Google::Protobuf::OBJECT_CACHE
Google::Protobuf::LegacyObjectCache.new
omit('OBJECT_CACHE not defined') unless defined? Google::Protobuf::Internal::OBJECT_CACHE
Google::Protobuf::Internal::LegacyObjectCache.new
end

include ObjectCacheTestModule
end

0 comments on commit 9087337

Please sign in to comment.