|
| 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 | + |
| 8 | +module 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 |
| 99 | +end |
0 commit comments