diff --git a/.gitignore b/.gitignore index 76143cd10..0dac51100 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ build-dynamic build-static-debug build-dynamic-debug cmake-build-* +.cache # For Macs.. .DS_Store diff --git a/libs/server-sdk/src/data_interfaces/source/fdv2_source_result.hpp b/libs/server-sdk/src/data_interfaces/source/fdv2_source_result.hpp new file mode 100644 index 000000000..069fe5a38 --- /dev/null +++ b/libs/server-sdk/src/data_interfaces/source/fdv2_source_result.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace launchdarkly::server_side::data_interfaces { + +/** + * Result returned by IFDv2Initializer::Run and IFDv2Synchronizer::Next. + * + * Mirrors Java's FDv2SourceResult. + */ +struct FDv2SourceResult { + using ErrorInfo = common::data_sources::DataSourceStatusErrorInfo; + + /** + * A changeset was successfully received and is ready to apply. + */ + struct ChangeSet { + data_model::FDv2ChangeSet change_set; + /** If true, the server signaled that the client should fall back to + * FDv1. */ + bool fdv1_fallback; + }; + + /** + * A transient error occurred; the source may recover. + */ + struct Interrupted { + ErrorInfo error; + bool fdv1_fallback; + }; + + /** + * A non-recoverable error occurred; the source should not be retried. + */ + struct TerminalError { + ErrorInfo error; + bool fdv1_fallback; + }; + + /** + * The source was closed cleanly (via Close()). + */ + struct Shutdown {}; + + /** + * The server sent a goodbye; the orchestrator should rotate sources. + */ + struct Goodbye { + std::optional reason; + bool fdv1_fallback; + }; + + /** + * Next() returned because the timeout expired before a result arrived. + */ + struct Timeout {}; + + using Value = std::variant; + + Value value; +}; + +} // namespace launchdarkly::server_side::data_interfaces diff --git a/libs/server-sdk/src/data_interfaces/source/ifdv2_initializer.hpp b/libs/server-sdk/src/data_interfaces/source/ifdv2_initializer.hpp new file mode 100644 index 000000000..5c2608cd1 --- /dev/null +++ b/libs/server-sdk/src/data_interfaces/source/ifdv2_initializer.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "fdv2_source_result.hpp" + +#include + +namespace launchdarkly::server_side::data_interfaces { + +/** + * Defines a one-shot data source that runs to completion and returns a single + * result. Used during the initialization phase of FDv2, before handing off to + * an IFDv2Synchronizer. + */ +class IFDv2Initializer { + public: + /** + * Run the initializer to completion. Blocks until a result is available. + * Called at most once per instance. + * + * Close() may be called from another thread to unblock Run(), in which + * case Run() returns FDv2SourceResult::Shutdown. + */ + virtual FDv2SourceResult Run() = 0; + + /** + * Unblocks any in-progress Run() call, causing it to return + * FDv2SourceResult::Shutdown. + */ + virtual void Close() = 0; + + /** + * @return A display-suitable name of the initializer. + */ + [[nodiscard]] virtual std::string const& Identity() const = 0; + + virtual ~IFDv2Initializer() = default; + IFDv2Initializer(IFDv2Initializer const&) = delete; + IFDv2Initializer(IFDv2Initializer&&) = delete; + IFDv2Initializer& operator=(IFDv2Initializer const&) = delete; + IFDv2Initializer& operator=(IFDv2Initializer&&) = delete; + + protected: + IFDv2Initializer() = default; +}; + +} // namespace launchdarkly::server_side::data_interfaces diff --git a/libs/server-sdk/src/data_interfaces/source/ifdv2_synchronizer.hpp b/libs/server-sdk/src/data_interfaces/source/ifdv2_synchronizer.hpp new file mode 100644 index 000000000..a56bacee3 --- /dev/null +++ b/libs/server-sdk/src/data_interfaces/source/ifdv2_synchronizer.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "fdv2_source_result.hpp" + +#include + +#include +#include + +namespace launchdarkly::server_side::data_interfaces { + +/** + * Defines a continuous data source that produces a stream of results. Used + * during the synchronization phase of FDv2, after initialization is complete. + * + * The stream is started lazily on the first call to Next(). The synchronizer + * runs until Close() is called. + */ +class IFDv2Synchronizer { + public: + /** + * Block until the next result is available or the timeout expires. + * + * On the first call, the synchronizer starts its underlying connection. + * Subsequent calls continue reading from the same connection. + * + * If the timeout expires before a result arrives, returns + * FDv2SourceResult::Timeout. The orchestrator uses this to evaluate + * fallback conditions. + * + * Close() may be called from another thread to unblock Next(), in which + * case Next() returns FDv2SourceResult::Shutdown. + * + * @param timeout Maximum time to wait for the next result. + * @param selector The selector to send with the request, reflecting any + * changesets applied since the previous call. + */ + virtual FDv2SourceResult Next(std::chrono::milliseconds timeout, + data_model::Selector selector) = 0; + + /** + * Unblocks any in-progress Next() call, causing it to return + * FDv2SourceResult::Shutdown, and releases underlying resources. + */ + virtual void Close() = 0; + + /** + * @return A display-suitable name of the synchronizer. + */ + [[nodiscard]] virtual std::string const& Identity() const = 0; + + virtual ~IFDv2Synchronizer() = default; + IFDv2Synchronizer(IFDv2Synchronizer const&) = delete; + IFDv2Synchronizer(IFDv2Synchronizer&&) = delete; + IFDv2Synchronizer& operator=(IFDv2Synchronizer const&) = delete; + IFDv2Synchronizer& operator=(IFDv2Synchronizer&&) = delete; + + protected: + IFDv2Synchronizer() = default; +}; + +} // namespace launchdarkly::server_side::data_interfaces