Repository navigation
feat: Retry event delivery and flush on close - #74
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
|
Implements the SDK spec v0.9.0 for events: - Event batches are sent up to 3 times, giving up on each attempt after 3 seconds and backing off from 1 second, doubling, with full jitter. Batches the events API rejects are dropped, as are those whose last attempt fails with a 500; others are kept for the next flush, ahead of events tracked since. A 401 or 403 stops event tracking. - A full buffer (1000 events) drops its oldest event. - `Flagsmith.Client.flush_events/1` sends the tracked events and returns once they have been delivered, dropped or kept. - `Flagsmith.Client.close/2` sends the buffered events within a timeout, and stops the client's events processor and poller. - Local evaluation now reads experiment metadata and variant keys from the environment document, so experiment flags evaluated locally carry their experiment and variant, and send exposures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Implemented SDK spec v0.9.0 in b6b19ed. The spec run was failing because the SDK had no steps (and no SDK behaviour) for event delivery retries, flushing, closing the client, or a client with both local evaluation and events. Event delivery (
New public API (
Local evaluation of experiment flags (needed by the new
Steps ( Tests: unit tests cover retries and the 3 s attempt timeout, kept/dropped batches (400, 500 last attempt, 503 last attempt), 401/403, full-buffer dropping, close (sends the buffer, and returns within its timeout), and locally evaluated experiment exposures. Locally I ran |
Zaimwa9
left a comment
There was a problem hiding this comment.
Requesting changes. Spec passes, but a flush already running keeps sending after close/2 times out.
`close/2` stopped waiting when its timeout expired, but a flush already running kept retrying for up to ~12 seconds. The processor is now killed on timeout, and is started as a temporary child so that its supervisor doesn't restart it; tracking an event starts a new one, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addressed in f81d3f2 (details in the inline reply). When Checks run locally:
One more thing: |
|
@themis-blindfold review |
|
Review failed after 2 attempt(s) (OutputError). Check the worker logs. |
The review run didn't produce any findings. It failed with "Review failed after 2 attempt(s) (OutputError)", so I have nothing to address and haven't changed anything. Re-running it is up to you. The branch is unchanged at f81d3f2, and every check passes (spec v0.9.0, build and test, benchmarks). Your earlier point about |
|
@themis-blindfold review |
⚖️ Themis review: 🟠 Fix before mergeThe retry and shutdown APIs cover the main delivery flow, but several edge cases undermine the promised delivery and resource bounds. The build, SDK specification, and benchmark checks passed. Local tests could not be run because Elixir and Mix are unavailable.
🟠 Majors
📝 Walkthrough
🧪 How to verify
Product take: Reliable delivery and predictable shutdown matter for experiment exposure data; these gaps can delay or lose events and affect host applications. 🧭 Assumptions & unverified claimsNo unverified assumptions or claims. May the next flush be a clean one · reviewed at f81d3f2 1 finding(s) anchored outside the diff (not posted inline)
Bound events queued while delivery is in progress Observed: delivery and retry sleeps run inside the GenServer, while tracking sends asynchronous casts; the 1,000-event limit is applied only after those messages are processed. Predicted: sustained tracking during a slow request would accumulate an unbounded mailbox, exceeding the advertised buffer limit and potentially exhausting host memory. Apply admission and eviction to all pending events, including those arriving during a send, and test a stalled delivery under load. |
…'t wait on them
The events processor sent batches, retries included, inside the
GenServer, for up to ~12 seconds. Events tracked meanwhile piled up in
its mailbox, past the buffer's 1000-event limit, and a flush caller
exited when closing the client killed the processor.
Batches are now sent by a task. The processor keeps buffering events
while one is in flight, dropping the oldest once full; flush callers
wait for the batch in flight; and closing abandons it at the deadline,
replying `{:error, :closed}` to flush callers instead of killing the
processor. `flush/1` no longer exits if the processor stops between
being looked up and called.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replies to the Themis review of f81d3f2. The three inline findings have their own replies. Here is where each point landed:
No change. The spec at v0.9.0 retries 500s: in "A batch whose last attempt fails otherwise is kept, after server errors", the proxy answers 500, 500, 503, then 202 for the same batch. It drops the batch only when the last attempt gets a 500. How to treat other 5xx (such as 501) is left open in the spec; I've offered that choice in the inline reply.
No change, because the spec's "A full buffer drops its oldest event" requires it. Re-sending the kept full-buffer batch on the explicit flush would deliver
Fixed in 19b4272.
Fixed in 19b4272. This was real, both in this PR and before it: delivery and retry backoff ran inside the GenServer, so tracking casts piled up in its mailbox while a batch was retried. Batches are now sent by a task. The processor keeps handling tracked events during a send and drops the oldest once its buffer holds 1,000. At most one full buffer plus the batch in flight is held in memory, and a kept batch is merged back to 1,000. The new test "events tracked while a batch is being sent are buffered, up to the buffer's limit" holds a batch of 1,000 at the events API, then tracks 1,500 more. It checks that the processor's mailbox is empty and it holds 1,000 buffered events. It also checks that the newest 1,000 are sent once the held batch completes. Checks:
My last local spec run got 15 of 24 through; the other 9 failed in the runner's setup. The runner's admin calls to Flagsmith failed, with 403 on |
Zaimwa9
left a comment
There was a problem hiding this comment.
Requesting changes. Sending batches in a task fixes the close and mailbox problems, but it introduces two regressions: under steady tracking flush_events/1 can wait indefinitely, and exposures dropped from a full buffer are never tracked again.
…s again - A flush arriving while a batch is being sent, with events tracked since, now waits for one more batch at most, then gets a reply. Previously it waited until a batch ended with nothing tracked meanwhile, which steady tracking could postpone indefinitely. A flush arriving with nothing tracked since only waits for the batch in flight. - Exposures dropped by a full buffer, or when a kept batch no longer fits it, are forgotten, so that they're tracked again. Previously their keys stayed, and those exposures were skipped as duplicates for the life of the processor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both are fixed in 11df3af; details in the inline replies.
There are 3 new regression tests, and all 3 fail on 19b4272. Checks:
|
Contributes to Flagsmith/sdk-spec#23