Class: Flare::Marker

Inherits:
Object
  • Object
show all
Defined in:
lib/flare/marker.rb

Overview

Thread-safe registry of trace_ids that Path 2 (the WebMarkerSubscriber) has marked for export. FilteringSpanProcessor checks marked? on every on_finish; matching spans get forwarded to the trace exporter, the rest are dropped.

Each entry records the OWNER span_id (the local rack server span the subscriber was inside when it marked the trace). Cleanup is keyed on the owner finishing, not the trace root finishing – remote-parented rack spans aren’t trace roots, and child spans can outlive their parent in OTel, so root-driven cleanup would leak on the dominant production case (web app behind a load balancer or service mesh).

Bounded by:

- sweep(): drops entries older than max_age (default 5 min) so a rack
  span that never finishes (process killed mid-request, exception path
  that skips ensure) doesn't leak forever.
- hard ceiling at max_entries (default 10k): on overflow, drop oldest
  10% by marked_at.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

DEFAULT_MAX_ENTRIES =
10_000
DEFAULT_MAX_AGE =

seconds

5 * 60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_entries: DEFAULT_MAX_ENTRIES, max_age: DEFAULT_MAX_AGE) ⇒ Marker

Returns a new instance of Marker.



33
34
35
36
37
38
# File 'lib/flare/marker.rb', line 33

def initialize(max_entries: DEFAULT_MAX_ENTRIES, max_age: DEFAULT_MAX_AGE)
  @entries = Concurrent::Map.new
  @max_entries = max_entries
  @max_age = max_age
  @evicted_count = Concurrent::AtomicFixnum.new(0)
end

Instance Attribute Details

#evicted_countObject (readonly)

Returns the value of attribute evicted_count.



31
32
33
# File 'lib/flare/marker.rb', line 31

def evicted_count
  @evicted_count
end

Instance Method Details

#mark(trace_id, owner_span_id:, rule_id:) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/flare/marker.rb', line 40

def mark(trace_id, owner_span_id:, rule_id:)
  @entries[trace_id] = Entry.new(
    owner_span_id: owner_span_id,
    rule_id: rule_id,
    marked_at: monotonic_now
  )
  maybe_evict_oldest
end

#marked?(trace_id) ⇒ Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/flare/marker.rb', line 49

def marked?(trace_id)
  @entries.key?(trace_id)
end

#owner?(trace_id, span_id) ⇒ Boolean

True only when span_id matches the marker’s owner – the rack span that originally marked this trace. Used by FilteringSpanProcessor to decide when to unmark (only when that exact span finishes, not on every span that happens to have this trace_id).

Returns:

  • (Boolean)


57
58
59
60
# File 'lib/flare/marker.rb', line 57

def owner?(trace_id, span_id)
  entry = @entries[trace_id]
  !entry.nil? && entry.owner_span_id == span_id
end

#rule_id(trace_id) ⇒ Object



62
63
64
65
# File 'lib/flare/marker.rb', line 62

def rule_id(trace_id)
  entry = @entries[trace_id]
  entry&.rule_id
end

#sizeObject



71
72
73
# File 'lib/flare/marker.rb', line 71

def size
  @entries.size
end

#sweepObject

Drop entries older than max_age. Call periodically (the RuleManager’s scheduler is the natural place) to handle the rack-span-never-finishes leak case (CAF-7).



78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/flare/marker.rb', line 78

def sweep
  threshold = monotonic_now - @max_age
  evicted = 0
  @entries.each_pair do |trace_id, entry|
    if entry.marked_at < threshold
      @entries.delete(trace_id)
      evicted += 1
    end
  end
  @evicted_count.increment(evicted) if evicted.positive?
  evicted
end

#unmark(trace_id) ⇒ Object



67
68
69
# File 'lib/flare/marker.rb', line 67

def unmark(trace_id)
  @entries.delete(trace_id)
end