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 |