Skip to content

Commit a5b19ed

Browse files
authored
feat: add StartAsync (#110)
This PR adds `StartAsync`, which allows users to wait on initialization using a future. C bindings have been updated to allow for waiting.
1 parent 213bbbc commit a5b19ed

File tree

16 files changed

+314
-119
lines changed

16 files changed

+314
-119
lines changed

apps/hello-c/main.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ int main() {
4545
LDContext context = LDContextBuilder_Build(context_builder);
4646

4747
LDClientSDK client = LDClientSDK_New(config, context);
48+
49+
bool initialized_successfully;
50+
if (LDClientSDK_Start(client, 5000, &initialized_successfully)) {
51+
printf("SDK initialized successfully: %s\n",
52+
(initialized_successfully ? "true" : "false"));
53+
} else {
54+
printf("SDK couldn't initialize in time; quitting\n");
55+
return 1;
56+
}
57+
4858
//
4959
// std::cout << "Initial Status: " << client.DataSourceStatus().Status()
5060
// << std::endl;

apps/hello-cpp/main.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,14 @@ int main() {
118118
std::cout << "Got flag change: " << *event << std::endl;
119119
});
120120

121-
client.WaitForReadySync(std::chrono::seconds(30));
121+
auto started = client.StartAsync();
122+
auto status = started.wait_for(std::chrono::seconds(5));
123+
if (status == std::future_status::ready) {
124+
std::cout << "SDK initialized successfully: "
125+
<< (started.get() ? "true" : "false") << std::endl;
126+
} else {
127+
std::cout << "SDK didn't initialize in time; quitting" << std::endl;
128+
}
122129

123130
auto value = client.BoolVariationDetail("my-boolean-flag", false);
124131
std::cout << "Value was: " << *value << std::endl;

apps/sdk-contract-tests/src/client_entity.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ tl::expected<nlohmann::json, std::string> ClientEntity::Identify(
3535
return tl::make_unexpected(maybe_ctx->errors());
3636
}
3737

38-
client_->IdentifyAsync(*maybe_ctx).wait();
38+
// The typical way to identify in contract tests would be .wait(), to ensure
39+
// identify completes fully before proceeding. Some of the contract tests
40+
// send invalid data to the data source though, so identify will never
41+
// complete because the data source can't start. So, limit the amount of
42+
// time such that: 1) For most tests, this is *basically* synchronous 2) For
43+
// the problematic tests, this eventually does unblock and let the test
44+
// proceed.
45+
// TODO: SC-204250
46+
client_->IdentifyAsync(*maybe_ctx).wait_for(std::chrono::seconds(5));
3947
return nlohmann::json{};
4048
}
4149

apps/sdk-contract-tests/src/entity_manager.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,9 @@ std::optional<std::string> EntityManager::create(ConfigParams const& in) {
143143
if (in.startWaitTimeMs) {
144144
waitForClient = std::chrono::milliseconds(*in.startWaitTimeMs);
145145
}
146-
client->WaitForReadySync(waitForClient);
146+
147+
auto init = client->StartAsync();
148+
init.wait_for(waitForClient);
147149

148150
entities_.try_emplace(id, std::move(client));
149151

apps/sdk-contract-tests/test-suppressions.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
events/summary events/basic counter behavior
2-
events/summary events/reset after each flush
3-
41
# The Client doesn't need to know how to deserialize users.
52
context type/convert/old user to context/{"key": ""}
63
context type/convert/old user to context/{"key": "a"}

libs/client-sdk/include/launchdarkly/client_side/bindings/c/sdk.h

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,72 @@ typedef struct _LDClientSDK* LDClientSDK;
3131
LD_EXPORT(LDClientSDK)
3232
LDClientSDK_New(LDClientConfig config, LDContext context);
3333

34+
/**
35+
* Starts the SDK, initiating a connection to LaunchDarkly if not offline.
36+
*
37+
* Only one Start call can be in progress at once; calling it
38+
* concurrently invokes undefined behavior.
39+
*
40+
* The method may be blocking or asynchronous depending on the arguments.
41+
*
42+
* To block, pass a positive milliseconds value and an optional pointer to a
43+
boolean. The return
44+
* value will be true if the SDK started within the specified timeframe, or
45+
false if the
46+
* operation couldn't complete in time. The value of out_succeeded will be true
47+
* if the SDK successfully initialized.
48+
*
49+
* Example:
50+
* @code
51+
* bool initialized_successfully;
52+
* if (LDClientSDK_Start(client, 5000, &initialized_successfully)) {
53+
* // The client was able to initialize in less than 5 seconds.
54+
* if (initialized_successfully) {
55+
* // Initialization succeeded.
56+
* else {
57+
* // Initialization failed.
58+
* }
59+
* } else {
60+
* // The client is still initializing.
61+
* }
62+
* @endcode
63+
*
64+
* To start asynchronously, pass `LD_NONBLOCKING`. In this case, the return
65+
value
66+
* will be false and you may pass NULL to out_succeeded.
67+
*
68+
* @code
69+
* // Returns immediately.
70+
* LDClientSDK_Start(client, LD_NONBLOCKING, NULL);
71+
* @endcode
72+
*
73+
* @param sdk SDK. Must not be NULL.
74+
* @param milliseconds Milliseconds to wait for initialization or
75+
`LD_NONBLOCKING` to return immediately.
76+
* @param out_succeeded Pointer to bool representing successful initialization.
77+
Only
78+
* modified if a positive milliseconds value is passed; may be NULL.
79+
* @return True if the client started within the specified timeframe.
80+
*/
81+
LD_EXPORT(bool)
82+
LDClientSDK_Start(LDClientSDK sdk,
83+
unsigned int milliseconds,
84+
bool* out_succeeded);
85+
3486
/**
3587
* Returns a boolean value indicating LaunchDarkly connection and flag state
3688
* within the client.
3789
*
38-
* When you first start the client, once WaitForReadySync has returned or
39-
* WaitForReadyAsync has completed, Initialized should return true if
40-
* and only if either 1. it connected to LaunchDarkly and successfully
41-
* retrieved flags, or 2. it started in offline mode so there's no need to
42-
* connect to LaunchDarkly. If the client timed out trying to connect to LD,
43-
* then Initialized returns false (even if we do have cached flags).
44-
* If the client connected and got a 401 error, Initialized is
45-
* will return false. This serves the purpose of letting the app know that
46-
* there was a problem of some kind.
90+
* When you first start the client, once Start has completed, Initialized
91+
* should return true if and only if either 1. it connected to LaunchDarkly and
92+
* successfully retrieved flags, or 2. it started in offline mode so there's no
93+
* need to connect to LaunchDarkly.
94+
*
95+
* If the client timed out trying to connect to
96+
* LD, then Initialized returns false (even if we do have cached flags). If the
97+
* client connected and got a 401 error, Initialized is will return false. This
98+
* serves the purpose of letting the app know that there was a problem of some
99+
* kind.
47100
*
48101
* @param sdk SDK. Must not be NULL.
49102
* @return True if initialized.
@@ -89,10 +142,10 @@ LDClientSDK_TrackData(LDClientSDK sdk, char const* event_name, LDValue data);
89142
/**
90143
* Requests delivery of all pending analytic events (if any).
91144
*
92-
* You MUST pass LD_NONBLOCKING as the second parameter.
145+
* You MUST pass `LD_NONBLOCKING` as the second parameter.
93146
*
94147
* @param sdk SDK. Must not be NULL.
95-
* @param milliseconds Must pass LD_NONBLOCKING.
148+
* @param milliseconds Must pass `LD_NONBLOCKING`.
96149
*/
97150
LD_EXPORT(void)
98151
LDClientSDK_Flush(LDClientSDK sdk, unsigned int reserved);
@@ -105,19 +158,54 @@ LDClientSDK_Flush(LDClientSDK sdk, unsigned int reserved);
105158
* Only one Identify call can be in progress at once; calling it
106159
* concurrently invokes undefined behavior.
107160
*
108-
* To block until the identify operation is complete or a timeout is reached,
109-
* pass a positive milliseconds parameter. Otherwise to return immediately,
110-
* pass LD_NONBLOCKING.
161+
* The method may be blocking or asynchronous depending on the arguments.
162+
*
163+
* To block, pass a positive milliseconds value and an optional pointer to a
164+
boolean. The return
165+
* value will be true if the SDK was able to attempt the operation within
166+
the specified timeframe, or false if the
167+
* operation couldn't complete in time. The value of out_succeeded will be true
168+
* if the SDK successfully changed evaluation contexts.
169+
*
170+
* Example:
171+
* @code
172+
* bool identified_successfully;
173+
* if (LDClientSDK_Identify(client, 5000, &identified_successfully)) {
174+
* // The client was able to re-initialize in less than 5 seconds.
175+
* if (identified_successfully) {
176+
* // Evaluations will use data for the new context.
177+
* else {
178+
* // Evaluations will continue using existing data.
179+
* }
180+
* } else {
181+
* // The client is still identifying.
182+
* }
183+
* @endcode
184+
*
185+
* To start asynchronously, pass `LD_NONBLOCKING`. In this case, the return
186+
value
187+
* will be false and you may pass NULL to out_succeeded.
188+
*
189+
* @code
190+
* // Returns immediately.
191+
* LDClientSDK_Identify(client, LD_NONBLOCKING, NULL);
192+
* @endcode
193+
*
111194
*
112195
* @param sdk SDK. Must not be NULL.
113196
* @param context The new evaluation context.
114-
* @param milliseconds How long to wait for the identify to complete, or
115-
* LD_NONBLOCKING to return immediately.
197+
* @param milliseconds Milliseconds to wait for identify to complete, or
198+
* `LD_NONBLOCKING` to return immediately.
199+
* @param out_succeeded Pointer to bool representing successful identification.
200+
Only
201+
* modified if a positive milliseconds value is passed; may be NULL.
202+
* @return True if the client started within the specified timeframe.
116203
*/
117-
LD_EXPORT(void)
204+
LD_EXPORT(bool)
118205
LDClientSDK_Identify(LDClientSDK sdk,
119206
LDContext context,
120-
unsigned int milliseconds);
207+
unsigned int milliseconds,
208+
bool* out_succeeded);
121209

122210
/**
123211
* Returns the boolean value of a feature flag for a given flag key.

libs/client-sdk/include/launchdarkly/client_side/client.hpp

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,28 @@ namespace launchdarkly::client_side {
2121
*/
2222
class IClient {
2323
public:
24+
/** Connects the client to LaunchDarkly's flag delivery endpoints.
25+
*
26+
* If StartAsync isn't called, the client is able to post events but is
27+
* unable to obtain flag data.
28+
*
29+
* The returned future will resolve to true or false based on the logic
30+
* outlined on @ref Initialized.
31+
*/
32+
virtual std::future<bool> StartAsync() = 0;
33+
2434
/**
2535
* Returns a boolean value indicating LaunchDarkly connection and flag state
2636
* within the client.
2737
*
28-
* When you first start the client, once WaitForReadySync has returned or
29-
* WaitForReadyAsync has completed, Initialized should return true if
30-
* and only if either 1. it connected to LaunchDarkly and successfully
31-
* retrieved flags, or 2. it started in offline mode so there's no need to
32-
* connect to LaunchDarkly. If the client timed out trying to connect to LD,
33-
* then Initialized returns false (even if we do have cached flags).
34-
* If the client connected and got a 401 error, Initialized is
35-
* will return false. This serves the purpose of letting the app know that
36-
* there was a problem of some kind.
38+
* When you first start the client, once StartAsync has completed,
39+
* Initialized should return true if and only if either 1. it connected to
40+
* LaunchDarkly and successfully retrieved flags, or 2. it started in
41+
* offline mode so there's no need to connect to LaunchDarkly. If the client
42+
* timed out trying to connect to LD, then Initialized returns false (even
43+
* if we do have cached flags). If the client connected and got a 401 error,
44+
* Initialized is will return false. This serves the purpose of letting the
45+
* app know that there was a problem of some kind.
3746
*
3847
* @return True if the client is initialized.
3948
*/
@@ -101,10 +110,13 @@ class IClient {
101110
* To block until the identify operation is complete, call wait() on
102111
* the returned future.
103112
*
113+
* The returned future will resolve to true or false based on the logic
114+
* outlined on @ref Initialized.
115+
*
104116
* @param context The new evaluation context.
105117
*/
106118

107-
virtual std::future<void> IdentifyAsync(Context context) = 0;
119+
virtual std::future<bool> IdentifyAsync(Context context) = 0;
108120

109121
/**
110122
* Returns the boolean value of a feature flag for a given flag key.
@@ -230,14 +242,6 @@ class IClient {
230242
*/
231243
virtual flag_manager::IFlagNotifier& FlagNotifier() = 0;
232244

233-
/**
234-
* Wait for the client to be ready. A client will be ready when it either
235-
* successfully initializes, or encounters a permanent error.
236-
*
237-
* @param timeout Time to wait.
238-
*/
239-
virtual void WaitForReadySync(std::chrono::milliseconds timeout) = 0;
240-
241245
virtual ~IClient() = default;
242246
IClient(IClient const& item) = delete;
243247
IClient(IClient&& item) = delete;
@@ -260,6 +264,8 @@ class Client : public IClient {
260264
Client& operator=(Client) = delete;
261265
Client& operator=(Client&& other) = delete;
262266

267+
std::future<bool> StartAsync() override;
268+
263269
[[nodiscard]] bool Initialized() const override;
264270

265271
using FlagKey = std::string;
@@ -275,7 +281,7 @@ class Client : public IClient {
275281

276282
void FlushAsync() override;
277283

278-
std::future<void> IdentifyAsync(Context context) override;
284+
std::future<bool> IdentifyAsync(Context context) override;
279285

280286
bool BoolVariation(FlagKey const& key, bool default_value) override;
281287

@@ -309,8 +315,6 @@ class Client : public IClient {
309315

310316
flag_manager::IFlagNotifier& FlagNotifier() override;
311317

312-
void WaitForReadySync(std::chrono::milliseconds timeout) override;
313-
314318
private:
315319
std::unique_ptr<IClient> client;
316320
};

libs/client-sdk/include/launchdarkly/client_side/data_source_status.hpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class DataSourceStatus {
6666
* SDK key will never become valid), or because the SDK client was
6767
* explicitly shut down.
6868
*/
69-
kShutdown
69+
kShutdown,
7070

7171
// BackgroundDisabled,
7272
// TODO: A plugin of sorts would likely be required for some
@@ -208,7 +208,7 @@ class IDataSourceStatusProvider {
208208
* The current status of the data source. Suitable for broadcast to
209209
* data source status listeners.
210210
*/
211-
virtual DataSourceStatus Status() = 0;
211+
virtual DataSourceStatus Status() const = 0;
212212

213213
/**
214214
* Listen to changes to the data source status.
@@ -219,6 +219,17 @@ class IDataSourceStatusProvider {
219219
virtual std::unique_ptr<IConnection> OnDataSourceStatusChange(
220220
std::function<void(data_sources::DataSourceStatus status)> handler) = 0;
221221

222+
/**
223+
* Listen to changes to the data source status, with ability for listener
224+
* to unregister itself.
225+
*
226+
* @param handler Function which will be called with the new status. Return
227+
* true to unregister.
228+
* @return A IConnection which can be used to stop listening to the status.
229+
*/
230+
virtual std::unique_ptr<IConnection> OnDataSourceStatusChangeEx(
231+
std::function<bool(data_sources::DataSourceStatus status)> handler) = 0;
232+
222233
virtual ~IDataSourceStatusProvider() = default;
223234
IDataSourceStatusProvider(IDataSourceStatusProvider const& item) = delete;
224235
IDataSourceStatusProvider(IDataSourceStatusProvider&& item) = delete;

0 commit comments

Comments
 (0)