Repository navigation
feat: Retry event delivery and flush on close - #129
flagsmithsmith[bot] wants to merge 5 commits into
Conversation
Its entry in Flagsmith/sdk-spec's compatibility matrix at v0.9.0: `core events local-evaluation`.
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
Implement SDK spec v0.9.0: - Send a batch of events up to 3 times, one batch at a time, backing off from 1 second with full jitter, with a 3 second request timeout. Keep a batch whose last attempt failed transiently for the next flush, unless that attempt got a 500; drop a batch the events API rejects; stop event tracking when it refuses the environment key (401/403). A full buffer drops its oldest event. - Add `Client#flush_events`, which returns once the tracked events are delivered, dropped or kept, and `Client#close(timeout_seconds:)`, which stops background work and sends the buffered events within the timeout. - Record exposures under local evaluation: carry the environment document's `metadata.experiment` and multivariate option keys through to identity flags' `experiment` and `variant`. - Wire the new spec steps in sdk-spec/steps.rb. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Implemented SDK spec v0.9.0 in 4b281a4. CI had failed because the SDK was missing 4 steps: the local evaluation + events client, flushing events, closing the client, and tracking a numbered range of events. Behind those steps were 2 missing SDK features. Event delivery (
Client
Exposures under local evaluation
Tests and steps
Unrelated to this change, possibly a bug: under local evaluation, identity overrides get priority |
Zaimwa9
left a comment
There was a problem hiding this comment.
Requesting changes. Spec passes and the threading is sound, but once a kept batch fills the buffer, every track restarts a retry cycle. Please also make close terminal.
| drop_oldest if @events.length >= @max_buffer_items | ||
| @events << event | ||
| next false if @flush_pending || @events.length < @max_buffer_items | ||
| @tracked_count += 1 | ||
| next if @flush_pending || @events.length < @max_buffer_items | ||
|
|
||
| @flush_pending = true | ||
| @tracked_count | ||
| end | ||
| Thread.new { scheduled_flush } if schedule_flush | ||
| Thread.new { scheduled_flush(tracked_count) } if tracked_count |
There was a problem hiding this comment.
Once a kept batch fills the buffer, every track starts a new retry cycle. Skip the full-buffer flush while a failed batch is held.
There was a problem hiding this comment.
Fixed in 6aa2ec4. While the buffer holds a batch whose attempts all failed (@holding_failed_batch, set when a batch is kept and cleared when it is taken again), a full buffer no longer schedules a flush. Tracking still drops the oldest event, and the held batch goes out with the next interval or explicit flush. Covered by "waits for the next flush rather than attempting the kept batch again" and "sends a full buffer without waiting again once the kept batch is sent".
Writing that second test found a related bug, also fixed in 6aa2ec4. A flush skipped any event already attempted, even when the attempt happened before the flush was called. So flush on a kept batch with no new events sent nothing. A flush now only skips events sent by a batch taken after it was requested. New test: "sends a kept batch with the next flush, even with no events tracked since".
| def stop(timeout_seconds = CLOSE_TIMEOUT_SECONDS) | ||
| RunningEventProcessors.delete(self) | ||
| flush | ||
| change_state { @stopping = true } | ||
| @interval = nil | ||
| final_flush = Thread.new { send_batch } | ||
| @logger.warn "Gave up sending events after #{timeout_seconds} seconds while closing" unless final_flush.join(timeout_seconds) | ||
| change_state { @closed = true } | ||
| end |
There was a problem hiding this comment.
After close, track still buffers and flush still sends. Make both no-ops once stopping.
There was a problem hiding this comment.
Fixed in 6aa2ec4. Once stop (and so Client#close) begins, track_event/track_exposure_event buffer nothing, and flush (and so Client#flush_events) sends nothing; each logs at debug level ("Not tracking … event: the client is closed" / "Not flushing events: the client is closed"). start also does nothing after a stop. stop itself still sends what was buffered before the close, through its own final flush. Covered by the new "when stopped" specs.
…minal - While the buffer holds a batch whose attempts all failed, a full buffer waits for the next flush instead of starting another round of attempts on every tracked event. - Once the processor is stopping, tracking and flushing do nothing, and it does not start again. - A flush now only skips events a batch taken after it was requested already sent, so a flush resends a kept batch even when no event was tracked since. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both are fixed in 6aa2ec4; details are in the inline replies.
That change also fixes a related bug: a flush with no events tracked since a kept batch failed used to send nothing. It now resends the kept batch. Locally, rspec (456 examples, 6 consecutive runs) and rubocop pass. |
A flush requested while a full-buffer flush was sending a batch counted no batch taken since, so it sent the kept batch again once that batch's attempts failed. Count settled batches instead: one in flight at the request settles after it, and covers the events tracked up to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A follow-up to 6aa2ec4, in daead64. In CI, the spec run on 6aa2ec4 failed "A full buffer drops its oldest event": The cause was in 6aa2ec4's new rule for when a flush skips events another flush already sent. It counted batches taken. If the full-buffer flush had already taken the batch when the application called It now counts batches settled. A batch in flight when a flush is requested settles after the request, so the flush skips the events it covered. The new spec "does not send again a batch in flight when the flush was requested" fails on 6aa2ec4 and passes now. All checks are green on daead64. |
Contributes to Flagsmith/sdk-spec#23