# Protocol Buffers - Google's data interchange format # Copyright 2023 Google Inc. All rights reserved. # https://developers.google.com/protocol-buffers/ # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. module Google module Protobuf # 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