Skip to content

feat: add server FDv2 data system orchestrator#529

Merged
beekld merged 5 commits intomainfrom
beeklimt/SDK-2098
May 6, 2026
Merged

feat: add server FDv2 data system orchestrator#529
beekld merged 5 commits intomainfrom
beeklimt/SDK-2098

Conversation

@beekld
Copy link
Copy Markdown
Contributor

@beekld beekld commented May 5, 2026

Add FDv2DataSystem orchestrator

Orchestrates FDv2 initializers and synchronizers; applies changesets and reports data-source status.

What's implemented

  • FDv2DataSystemIDataSystem impl. Runs initializers, then a synchronizer; applies changesets; tracks the selector; emits status (kInitializingkValid on first apply / kInterrupted on errors → kOff on destruction). Offline (no factories) goes straight to kValid.
  • IFDv2InitializerFactory, IFDv2SynchronizerFactory — build a fresh source per call.
  • ITransactionalDestination — extends IDestination with Apply(ChangeSet); implemented by MemoryStore and ChangeNotifier.
  • ChangeNotifier::Apply — diffs against the current store, updates the dependency tracker, applies to the sink, emits change events. Full diffs per-kind and version-aware; Partial notifies unconditionally per item.

Design decisions

  • No shared_ptr<State> for orchestrator-level async safety. Other FDv2 components keep async state in a shared_ptr<State> so callbacks can outlive the public object. This one captures this directly — safe because ~ClientImpl does ioc_.stop() and run_thread_.join() before destroying the data system. This was done because ClientImpl is shared with FDv1.
  • New ITransactionalDestination rather than adding Apply to IDestination. Leaves the FDv1 persistent-store path untouched.
  • DataSourceStatusManager is non-owning. ClientImpl (shared with FDv1) is its sole owner; the data system only borrows it. Declaration order in ClientImpl guarantees the manager outlives the data system.

Test plan

11 cases in libs/server-sdk/tests/fdv2_data_system_test.cpp cover lifecycle, initializer phase (basis received, basis-skips-remaining, Interrupted advances, ChangeSet without selector continues), and synchronizer phase (apply, Interrupted loops, Goodbye/TerminalError advances, selector forwarding). Mocks resolve futures synchronously; tests run the io_context to drain orchestration before assertions.


Note

Medium Risk
Introduces new FDv2 orchestration and a transactional changeset apply path that affects how flags/segments are updated and how change notifications fire; bugs here could lead to stale/incorrect flag state or missed updates under concurrency.

Overview
Implements a new FDv2DataSystem that orchestrates FDv2 initializers and synchronizers on an executor, applies incoming changesets into an in-memory store, tracks the latest selector for incremental updates, and drives DataSourceStatusManager transitions.

Adds ITransactionalDestination (extending IDestination) plus ChangeNotifier::Apply support so full/partial/none changesets can be applied atomically while updating dependency tracking and emitting change events; MemoryStore and ChangeNotifier are updated to implement this interface.

Improves FDv2StreamingSynchronizer handling of goodbye events by resetting protocol state and restarting the underlying SSE connection; adds/extends unit tests for the new orchestrator, changeset apply semantics, and goodbye restart/reset behavior.

Reviewed by Cursor Bugbot for commit 7a147d2. Bugbot is set up for automated code reviews on this repo. Configure here.

@beekld beekld marked this pull request as ready for review May 6, 2026 17:09
@beekld beekld requested a review from a team as a code owner May 6, 2026 17:09
@kinyoklion
Copy link
Copy Markdown
Member

Potential test suggestions in pr-529-destruction-tests.

Just validating the behavior of destruction mid-synchronization/initialization.

@kinyoklion
Copy link
Copy Markdown
Member

kinyoklion commented May 6, 2026

And another set of test suggestions in pr-529-goodbye-tests and pr-529-selector-tests

kinyoklion added 3 commits May 6, 2026 14:40
Adds two regression tests that exercise the destruction protocol contract
documented on FDv2DataSystem: when the destructor runs while an initializer
or synchronizer Future is unresolved, the orchestrator must close the active
source, transition status to kOff, and tear down the captured-this
continuation chain without firing it against the destroyed object.

Existing Destructor_TransitionsStatusToOff only covers the offline-mode case
(no factories, no orchestration ever started); the in-flight teardown paths
were not exercised. Adds StalledInitializer / StalledSynchronizer mocks that
return an unresolved Future to drive the orchestrator into the in-flight
state, then destroy the data system before the future resolves.
Adds two regression tests for the Goodbye handling added in d154cbe:

- GoodbyeEventTriggersAsyncRestart: verifies that on receiving a goodbye
  event, the synchronizer drives sse::Client::async_restart with the
  documented reason string. Without this, the server's "we're about to
  disconnect" signal would lead to a stalled connection rather than a
  controlled reconnect. Adds a MockSseClient that records calls and a
  SetSseClient test peer to inject it.

- GoodbyeMidPayloadDiscardsAccumulatedAndAcceptsFreshChangeset: feeds
  a partial payload, then a goodbye, then a fresh full changeset, and
  asserts that the accumulated puts were discarded and only the fresh
  put is in the resulting ChangeSet. Locks in the spec-aligned property
  that Goodbye does not corrupt subsequent payloads.
Adds SynchronizerGoodbye_PreservesSelectorOnNextCall: drives the
orchestrator through initializer-basis@v1 -> ChangeSet@v2 -> Goodbye ->
Shutdown, and asserts the captured Next() selectors are v1, v2, v2 in
order.

The existing SynchronizerGoodbye_StaysOnSameSynchronizer test only
checks that Goodbye does not rotate the synchronizer factory; it does
not verify what selector the post-Goodbye Next() call receives. Without
this preservation, the SDK would reconnect with stale or empty payload
state on every Goodbye, forcing the server into expensive xfer-full
responses instead of efficient xfer-changes.

Verified load-bearing: temporarily clearing selector_ on Goodbye in
fdv2_data_system.cpp makes only this test fail (the existing Goodbye
test still passes).
@beekld beekld merged commit 65e110f into main May 6, 2026
44 checks passed
@beekld beekld deleted the beeklimt/SDK-2098 branch May 6, 2026 22:54
@github-actions github-actions Bot mentioned this pull request May 6, 2026
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.

2 participants