Module: HeapScope::Closures

Defined in:
lib/heapscope/closures.rb

Overview

Conservative Proc/closure retention analysis. Ruby does not expose captured bindings cleanly — treat results as hypotheses.

Class Method Summary collapse

Class Method Details

.findings(procs) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/heapscope/closures.rb', line 42

def findings(procs)
  procs.select { |p| p[:approx_retained].to_i > 500_000 }.map do |p|
    site = p.dig(:allocation, :file) && "#{p[:allocation][:file]}:#{p[:allocation][:line]}"
    Finding.new(
      code: "HS006",
      severity: :medium,
      subject: site || "Proc",
      facts: [
        "Observed fact: Proc approx retained #{p[:approx_retained]} bytes" \
        "#{site ? " allocated at #{site}" : ""}."
      ],
      derived: ["Derived: bounded retained-size estimate exceeded 500KB."],
      hypothesis: "A long-lived Proc may be capturing a large object graph.",
      suspected_cause: site,
      suggestions: [
        "Avoid capturing large objects in long-lived callbacks",
        "Prefer weak refs or explicit clearable context objects"
      ],
      evidence: [p]
    )
  end
end

.inventory(limit: 200) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/heapscope/closures.rb', line 9

def inventory(limit: 200)
  procs = []
  return procs unless Runtime.current.each_object?

  Runtime.current.each_object(Proc) do |proc|
    break if procs.size >= limit

    info = Runtime.current.allocation_info(proc)
    retained = begin
      Graph.new.estimate_retained_size(proc, max_objects: 500, max_depth: 4)
    rescue StandardError
      { retained: nil }
    end
    procs << {
      class: "Proc",
      lambda: proc.lambda?,
      arity: begin
        proc.arity
      rescue StandardError
        nil
      end,
      allocation: info,
      shallow_bytes: Runtime.current.memsize_of(proc),
      approx_retained: retained[:retained],
      approximate: true,
      note: "Closure captures are not fully introspectable — retained size is approximate."
    }
  end
  procs
rescue StandardError
  []
end