Class: Contrast::Agent::Assess::Finalizers::Hash

Inherits:
Hash
  • Object
show all
Defined in:
lib/contrast/agent/assess/finalizers/hash.rb

Overview

An extension of Hash that doesn’t impact GC of the object being stored by storing its ID as a Key to lookup and registering a finalizer on the object to remove its entry from the Hash immediately after it’s GC’d.

NOTE: PROPERTIES_HASH is called from C

Constant Summary collapse

FROZEN_FINALIZED_IDS =
Set.new
KEEP_AGE =

10 minutes

600_000.cs__freeze

Instance Method Summary collapse

Instance Method Details

#[](key) ⇒ Object?

Retrieves the Object stored in the Hash by its __id__.

Returns:

  • (Object, nil)

    the Object stored.



39
40
41
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 39

def [] key
  super(key.__id__)
end

#[]=(key, obj) ⇒ Object

Store the given Object in the Hash, using its __id__ as the key. If the Object is frozen, we need to pre-finalize it and store its __id__ in our tracking Set.

Returns:

  • (Object)

    the Object stored



23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 23

def []= key, obj
  return unless obj
  return unless ::Contrast::AGENT.enabled? && ::Contrast::ASSESS.enabled?

  # We can't finalize frozen things, so only act on those that went through .pre_freeze
  if key.cs__frozen?
    return unless FROZEN_FINALIZED_IDS.include?(key.__id__)
  else
    ObjectSpace.define_finalizer(key, finalizing_proc)
  end
  super(key.__id__, obj)
end

#cleanup!Object



103
104
105
106
107
108
109
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 103

def cleanup!
  ids = keys.dup
  ids.each do |key|
    properties = fetch(key.__id__, nil)
    delete(key) unless properties&.event && (Contrast::Utils::Timer.now_ms - properties.event.time) < KEEP_AGE
  end
end

#finalizing_procProc

Create a Proc to remove the given key from our frozen and properties tracking during finalization of the Object to which the given key_id pertains. The ObjectSpace’s finalizer mechanism will handle passing this ID in, so we only need to define the Proc once.

NOTE: by necessity, this is the only method which takes the __id__, not the Object itself. You CANNOT pass the Object to this as a finalizer cannot hold reference to the Object being finalized; that prevents GC, which introduces a memory leak and defeats the entire purpose of this.

Returns:

  • (Proc)

    the Proc to remove references to the ID of the GC’d object from our Hash



78
79
80
81
82
83
84
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 78

def finalizing_proc
  @_finalizing_proc ||= proc do |key_id|
    FROZEN_FINALIZED_IDS.delete(key_id)
    Contrast::Agent::Assess::Policy::PropagationMethod.instance_variable_get(:@properties).delete(key_id)
    delete(key_id)
  end
end

#pre_freeze(key) ⇒ Object

Frozen things cannot be finalized. To avoid any issue here, we intercept the #freeze call and set finalizers on the Object. To ensure later we know it’s been pre-finalized, we add it’s __id__ to our tracking.

Parameters:

  • key (Object)

    the Object on which we need to pre-define finalizers



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 91

def pre_freeze key
  return unless ::Contrast::AGENT.enabled? && ::Contrast::ASSESS.enabled?
  return if key.cs__frozen?
  return if FROZEN_FINALIZED_IDS.include?(key.__id__)

  ObjectSpace.define_finalizer(key, finalizing_proc)

  FROZEN_FINALIZED_IDS << key.__id__
rescue StandardError => _e
  nil
end

#trackable?(key) ⇒ Boolean

Something is trackable if it is not a collection and either not frozen or it was frozen after we put a finalizer on it.

Parameters:

  • key (Object)

    the thing to determine if trackable

Returns:

  • (Boolean)


48
49
50
51
52
53
54
55
56
57
58
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 48

def trackable? key
  return false unless key
  # Track things in these, not them themselves.
  return false if Contrast::Utils::DuckUtils.iterable_hash?(key)
  return false if Contrast::Utils::DuckUtils.iterable_enumerable?(key)
  # If it's not frozen, we can finalize/ track it.
  return true unless key.cs__frozen?

  # Otherwise, we can only track it if we've finalized it in our freeze patch.
  FROZEN_FINALIZED_IDS.include?(key.__id__)
end

#tracked?(key) ⇒ Boolean

Determine if the given Object is tracked, meaning it has a known set of properties and those properties are tracked.

Parameters:

  • key (Object)

    the Object whose properties, by id, we want to check for tracked status

Returns:

  • (Boolean)


65
66
67
# File 'lib/contrast/agent/assess/finalizers/hash.rb', line 65

def tracked? key
  key?(key.__id__) && fetch(key.__id__, nil)&.tracked?
end