Module: Alplus::Dedup
- Defined in:
- lib/alplus/dedup.rb
Overview
Exception dedup (issue #15), mirroring
packages/sdk/src/core/observe/dedup.ts's resolveDedupId: the same
error captured twice within a short window (auto-capture AND a manual
capture_exception for the same raised exception, e.g. the Rack
middleware re-raising into a Rails handler that also reports it)
produces ONE event, not two.
The JS SDK keys identity-bearing errors in a WeakMap so a dedup entry
never outlives (or pins alive) the error object. Ruby's
ObjectSpace::WeakMap holds its VALUES weakly too (not just keys) with
no other strong referent to a plain dedup-entry object, so a value
stashed there is eligible for GC before the next lookup -- unusable for
this. Instead, the dedup entry is stashed directly on the error object
itself via a hidden instance variable: it lives and dies with the
exact same object, which is a stronger and simpler guarantee than a
WeakMap gives (zero separate table to leak or prune for this path).
A raised String/Symbol/Number/boolean/nil can't hold an instance
variable (and has no reference identity worth keying on regardless --
two unrelated "boom" literals are different objects but the same
error), so those use a small bounded value-keyed Hash instead, same
split as the JS SDK's isWeakKeyable.
Defined Under Namespace
Classes: Entry
Constant Summary collapse
- WINDOW_SECONDS =
2.0- VALUE_CACHE_MAX =
Bounds the primitive-keyed fallback so a flood of distinct thrown strings can't grow it unboundedly.
50- VALUE_KEYABLE_CLASSES =
[String, Symbol, Integer, Float, TrueClass, FalseClass, NilClass].freeze
Class Method Summary collapse
-
.reset! ⇒ Object
Test-only: clears the value-keyed table between examples.
-
.resolve(error, fresh_id) ⇒ Object
Returns
{id:, duplicate:}— the fresh id for a new error, or the PREVIOUS capture's id (andduplicate: true) iferrorwas already captured within the window.
Class Method Details
.reset! ⇒ Object
Test-only: clears the value-keyed table between examples. The
identity path needs no reset — a fresh Exception.new each example
carries no leftover ivar.
64 65 66 |
# File 'lib/alplus/dedup.rb', line 64 def reset! @mutex.synchronize { @value_map.clear } end |
.resolve(error, fresh_id) ⇒ Object
Returns {id:, duplicate:} — the fresh id for a new error, or the
PREVIOUS capture's id (and duplicate: true) if error was
already captured within the window. Never raises: on any internal
failure (e.g. a frozen error object rejecting the ivar write),
treats it as a fresh, non-duplicate capture rather than risk
silently dropping a real error.
48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/alplus/dedup.rb', line 48 def resolve(error, fresh_id) now = monotonic_now @mutex.synchronize do if value_keyable?(error) resolve_value_keyed(error, fresh_id, now) else resolve_identity_keyed(error, fresh_id, now) end end rescue StandardError { id: fresh_id, duplicate: false } end |