1# Protocol Buffers - Google's data interchange format 2# Copyright 2023 Google Inc. All rights reserved. 3# 4# Use of this source code is governed by a BSD-style 5# license that can be found in the LICENSE file or at 6# https://developers.google.com/open-source/licenses/bsd 7 8module Google 9 module Protobuf 10 module Internal 11 # A pointer -> Ruby Object cache that keeps references to Ruby wrapper 12 # objects. This allows us to look up any Ruby wrapper object by the address 13 # of the object it is wrapping. That way we can avoid ever creating two 14 # different wrapper objects for the same C object, which saves memory and 15 # preserves object identity. 16 # 17 # We use WeakMap for the cache. If sizeof(long) > sizeof(VALUE), we also 18 # need a secondary Hash to store WeakMap keys, because our pointer keys may 19 # need to be stored as Bignum instead of Fixnum. Since WeakMap is weak for 20 # both keys and values, a Bignum key will cause the WeakMap entry to be 21 # collected immediately unless there is another reference to the Bignum. 22 # This happens on 64-bit Windows, on which pointers are 64 bits but longs 23 # are 32 bits. In this case, we enable the secondary Hash to hold the keys 24 # and prevent them from being collected. 25 class ObjectCache 26 def initialize 27 @map = ObjectSpace::WeakMap.new 28 @mutex = Mutex.new 29 end 30 31 def get(key) 32 @map[key] 33 end 34 35 def try_add(key, value) 36 @map[key] || @mutex.synchronize do 37 @map[key] ||= value 38 end 39 end 40 end 41 42 class LegacyObjectCache 43 def initialize 44 @secondary_map = {} 45 @map = ObjectSpace::WeakMap.new 46 @mutex = Mutex.new 47 end 48 49 def get(key) 50 value = if secondary_key = @secondary_map[key] 51 @map[secondary_key] 52 else 53 @mutex.synchronize do 54 @map[(@secondary_map[key] ||= Object.new)] 55 end 56 end 57 58 # GC if we could remove at least 2000 entries or 20% of the table size 59 # (whichever is greater). Since the cost of the GC pass is O(N), we 60 # want to make sure that we condition this on overall table size, to 61 # avoid O(N^2) CPU costs. 62 cutoff = (@secondary_map.size * 0.2).ceil 63 cutoff = 2_000 if cutoff < 2_000 64 if (@secondary_map.size - @map.size) > cutoff 65 purge 66 end 67 68 value 69 end 70 71 def try_add(key, value) 72 if secondary_key = @secondary_map[key] 73 if old_value = @map[secondary_key] 74 return old_value 75 end 76 end 77 78 @mutex.synchronize do 79 secondary_key ||= (@secondary_map[key] ||= Object.new) 80 @map[secondary_key] ||= value 81 end 82 end 83 84 private 85 86 def purge 87 @mutex.synchronize do 88 @secondary_map.each do |key, secondary_key| 89 unless @map.key?(secondary_key) 90 @secondary_map.delete(key) 91 end 92 end 93 end 94 nil 95 end 96 end 97 end 98 end 99end 100