Skip to content

feat(events): add flushAndWait on Apple (tier 2) - #536

Open
abelonogov-ld wants to merge 15 commits into
andrey/event-durability-tier1-bufferfrom
andrey/event-durability-tier2-bounded-flush
Open

abelonogov-ld wants to merge 15 commits into
andrey/event-durability-tier1-bufferfrom
andrey/event-durability-tier2-bounded-flush

Conversation

@abelonogov-ld

Copy link
Copy Markdown
Contributor

Tier 2 of the mobile event durability work. It stacks on tier 1 (#530), and nothing here persists events; that is tier 3.

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

Stacked on #530. Android already has the equivalent LDClient.flushAndWait(long, TimeUnit).

Describe the solution you've provided

A bounded flush: LDClient.flushAndWait(timeout:) spends up to a caller-supplied budget trying to deliver, then reports whether the events left the SDK's hands. Seconds, to match identify and start; ObjcLDClient.flushAndWait(timeout:) exposes it to Objective-C.

  • EventReporting.flushReportingOutcome is the seam. It reports true when the events were delivered, refused for good, or there were none, and false when the SDK is offline or a retryable failure means they are still waiting.
  • Response classification is now a decision rather than a Bool. processEventResponse returns settled, retryable, or dropped — the three outcomes its two callers already implied — which is what lets the flush say which one happened.
  • No deadlock from the main thread. TimeoutExecutor bounds the wait, and completions run on a queue that is neither main nor the reporter's delivery queue, so a lifecycle caller on the main thread cannot end up waiting for itself.
  • Backgrounding tries to deliver. It runs the delivery inside a ProcessInfo.performExpiringActivity assertion (BackgroundActivity), so the system does not suspend the process under a request that has only just reached the network. UIApplication.beginBackgroundTask is not available because the framework is built extension-safe.

Describe alternatives you've considered

  • An SDK-installed crash hook that flushes. Not done: the SDK has no crash hook of its own on Apple, and a Mach exception handler is not a supported caller. This is not a crash-time API, and its documentation says so.
  • Reporting delivery as a plain Bool from the response handler. It could not distinguish a permanent refusal (settled, nothing to retry) from a retryable failure, and the flush has to tell them apart.

Additional context

Spec: Event Durability §8–§12 (recoverable failure: bounded flush, response classification, lifecycle hooks, and the honesty requirement that flushAndWait is not a promise under termination).

Made with Cursor

abelonogov-ld and others added 11 commits September 18, 2026 18:38
A bounded flush spends up to a caller-supplied budget trying to deliver, then
reports whether the events left the SDK's hands. Android already had this; Apple
now has LDClient.flushAndWait(timeout:), spelled in seconds to match identify
and start.

EventReporting.flushReportingOutcome is the seam: true when the events were
delivered, refused for good, or there were none; false when the SDK is offline
or a retryable failure means they are still waiting. Saying that needed the
response classification to be a decision rather than a Bool, so
processEventResponse now returns settled, retryable, or dropped -- the three
outcomes that were already implied by its two callers.

TimeoutExecutor bounds the wait. Completions run on a queue that is neither main
nor the reporter's delivery queue, so a lifecycle caller on the main thread
cannot deadlock waiting for itself.

Backgrounding now spends its last moment trying to deliver, inside a
ProcessInfo activity assertion so the system does not suspend the process out
from under a request that has only just reached the network.

This is not a crash-time API. The SDK has no crash hook of its own on Apple, and
a Mach exception handler is not a supported caller.

Spec: Event Durability §10, §11, §17.3.
Co-authored-by: Cursor <cursoragent@cursor.com>
…-durability-tier2-bounded-flush

# Conflicts:
#	LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift
…-durability-tier2-bounded-flush

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	LaunchDarkly.xcodeproj/project.pbxproj
…-durability-tier2-bounded-flush

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	LaunchDarkly/LaunchDarkly/LDClient.swift
@abelonogov-ld
abelonogov-ld requested a review from a team as a code owner October 1, 2026 15:33

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread LaunchDarkly/LaunchDarkly/LDClient.swift
A flush starting while a delivery was on the wire found an empty event
store -- the events had left it when the request was sent -- and reported
success for events that could still fail. Backgrounding is where that
mattered: the hook released its activity assertion as soon as it heard
success, letting the system suspend the process out from under the very
request it was holding the assertion for.

A delivery now claims the reporter for the length of its round trip, and a
flush arriving inside that window is answered by the pass that follows it,
with the in-flight outcome folded into its answer. A tier 2 failure drops
the events rather than leaving them for that pass to retry, so a waiter
told only about the pass would hear success for events that are gone.

internalFlushAndWait read its result after a wait that had already
expired, which only the semaphore's signal orders against the write.

BackgroundActivity's documentation claimed undelivered events were already
on disk. Nothing is written until tier 3.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dc9d1e6. Configure here.

Comment thread LaunchDarkly/LaunchDarkly/ServiceObjects/EventReporter.swift Outdated
abelonogov-ld and others added 3 commits October 1, 2026 09:20
…correctly from flushAndWait

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Oct 2, 2026

Copy link
Copy Markdown

Current version of PR was reviewed by /review-bugbot on Oct 1, 13:41 PDT. It flagged 0 findings.

Bugbot on commit 47700b3 is skipped.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant