Class: ConvertSdk::EventManager
- Inherits:
-
Object
- Object
- ConvertSdk::EventManager
- Defined in:
- lib/convert_sdk/event_manager.rb
Overview
Synchronous, thread-safe pub/sub engine for SDK lifecycle events.
+EventManager+ is the single emission point for the SDK's lifecycle signals. Consumers subscribe with #on using the cross-SDK-consistent event names (SystemEvents); the SDK's internal stages fire those events with #fire as wiring lands in later stories (Client +ready+ in 2.5, +config.updated+ per refresh in 2.7, +bucketing+ in 2.11/4.1, +conversion+ in 4.3, +api.queue.released+ in 4.2). This story delivers the engine only.
== Event names are a wire-parity surface (FR57)
Event names are byte-identical to the JS SDK's +SystemEvents+ strings. A SystemEvents constant is its wire string (e.g. +SystemEvents::READY == "ready"+), so +on(SystemEvents::READY)+ and +on("ready")+ register under the SAME string key. Names are normalized to their string form (+#to_s+) before they touch the registry.
== Synchronous firing
Events fire synchronously, in registration order, at each lifecycle stage — no event thread, no queue. A slow listener slows the SDK (documented, JS parity). The firing path never raises into its caller: a listener that raises is caught and logged, and the remaining listeners still run.
== Deferred replay for late subscribers
Some events (READY, CONVERSION in JS) fire with deferred: true. The first deferred emission of an event records its +err+ so a listener that subscribes after the event already happened is replayed the stored value the moment it registers. This lets late subscribers observe a one-shot lifecycle signal they would otherwise have missed.
== Thread safety
The listener registry and the deferred store are both guarded by +@listeners_mutex+. Registration mutates the registry inside the lock. Firing takes a +dup+ snapshot of the listener list inside the lock, then iterates that snapshot OUTSIDE the lock — so a listener body (which runs unlocked) may itself call #on to register a new listener without deadlocking. The newly added listener is not invoked by the in-flight fire (it was not in the snapshot); it participates in subsequent fires.
Instance Method Summary collapse
-
#fire(event, payload = nil, err = nil, deferred: false) ⇒ void
private
Emit an event to all currently registered listeners.
-
#initialize(log_manager:) ⇒ EventManager
constructor
A new instance of EventManager.
-
#on(event) {|payload, err| ... } ⇒ self
Subscribe to an event.
Constructor Details
#initialize(log_manager:) ⇒ EventManager
Returns a new instance of EventManager.
48 49 50 51 52 53 54 55 56 57 |
# File 'lib/convert_sdk/event_manager.rb', line 48 def initialize(log_manager:) @log_manager = log_manager # event name (String) => Array<Proc> of listeners, registration-ordered. @listeners = {} # event name (String) => { payload:, err: } recorded by the first # deferred fire, replayed to late subscribers. @deferred = {} # Thread safety: guarded by @listeners_mutex (both @listeners and @deferred). @listeners_mutex = Thread::Mutex.new end |
Instance Method Details
#fire(event, payload = nil, err = nil, deferred: false) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Emit an event to all currently registered listeners. Internal API.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/convert_sdk/event_manager.rb', line 95 def fire(event, payload = nil, err = nil, deferred: false) key = event.to_s snapshot = @listeners_mutex.synchronize do @deferred[key] ||= { payload: payload, err: err } if deferred @listeners[key]&.dup end if snapshot.nil? || snapshot.empty? @log_manager.debug("EventManager#fire: no listeners for '#{key}'") return end # Iterate the snapshot OUTSIDE the lock — listener bodies run unlocked and # may re-register without deadlock. snapshot.each { |listener| invoke(key, listener, payload, err) } end |
#on(event) {|payload, err| ... } ⇒ self
Subscribe to an event. Public API.
Accepts a SystemEvents constant (which IS its string value) or any matching string; the name is normalized to its string form so both spellings register under one key. If the event was previously fired with deferred: true, the listener is invoked immediately with the stored payload/err (deferred replay).
72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/convert_sdk/event_manager.rb', line 72 def on(event, &listener) return self if listener.nil? key = event.to_s deferred = @listeners_mutex.synchronize do (@listeners[key] ||= []) << listener @deferred[key] end # Replay outside the lock so the listener body may itself call #on. invoke(key, listener, deferred[:payload], deferred[:err]) if deferred self end |