Skip to content

Commit 6ab8e82

Browse files
authored
feat: implement Identify method (#89)
This commit adds support for `IdentifyAsync`, which causes the client to shut down its data source, switch contexts, and restart the data source. Since it returns a future, users can call `wait()` to make it synchronous. An alternative API would be to accept a completion handler. That would imply executing user callbacks on a shared or separate executor, and probably isn't as convenient for the common case of blocking. To enable safe shutdown of the Eventsource/Datasources, I had to refactor them to use shared ownership. This is because the eventsource client (or polling requester) has completion handlers queued within asio that have pointers back to the objects. If we've shutdown and destroyed those objects, then the handlers will cause undefined behavior when they execute and access the destroyed object. Now, we pass in `weak_ptr` of the Datasource into the Eventsource client's callbacks, and lock the weak pointer before accessing any members (such as the logger). So if a completion handler finally executes long after the object is destroyed, it should be harmless.
1 parent 6516711 commit 6ab8e82

File tree

28 files changed

+355
-274
lines changed

28 files changed

+355
-274
lines changed

.clang-tidy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
22
CheckOptions:
33
- { key: readability-identifier-length.IgnoredParameterNames, value: 'i|j|k|c|os|it' }
4-
- { key: readability-identifier-length.IgnoredVariableNames, value: 'ec' }
4+
- { key: readability-identifier-length.IgnoredVariableNames, value: 'ec|id' }
55
- { key: readability-identifier-length.IgnoredLoopCounterNames, value: 'i|j|k|c|os|it' }

apps/sdk-contract-tests/include/client_entity.hpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@ class ClientEntity {
1313

1414
private:
1515
tl::expected<nlohmann::json, std::string> Evaluate(
16-
EvaluateFlagParams params);
16+
EvaluateFlagParams const&);
1717

1818
tl::expected<nlohmann::json, std::string> EvaluateDetail(
19-
EvaluateFlagParams);
19+
EvaluateFlagParams const&);
2020

2121
tl::expected<nlohmann::json, std::string> EvaluateAll(
22-
EvaluateAllFlagParams params);
22+
EvaluateAllFlagParams const&);
2323

2424
tl::expected<nlohmann::json, std::string> Identify(
25-
IdentifyEventParams params);
25+
IdentifyEventParams const&);
2626

27-
tl::expected<nlohmann::json, std::string> Custom(CustomEventParams params);
28-
29-
tl::expected<nlohmann::json, std::string> ContextBuild(
30-
ContextBuildParams params);
31-
32-
tl::expected<nlohmann::json, std::string> ContextConvert(
33-
ContextConvertParams params);
27+
tl::expected<nlohmann::json, std::string> Custom(CustomEventParams const&);
3428

3529
std::unique_ptr<launchdarkly::client_side::Client> client_;
3630
};
31+
32+
static tl::expected<nlohmann::json, std::string> ContextConvert(
33+
ContextConvertParams const&);
34+
35+
static tl::expected<nlohmann::json, std::string> ContextBuild(
36+
ContextBuildParams const&);

apps/sdk-contract-tests/include/entity_manager.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ class EntityManager {
3939
* @return An ID representing the entity, or none if the entity couldn't
4040
* be created.
4141
*/
42-
std::optional<std::string> create(ConfigParams params);
42+
std::optional<std::string> create(ConfigParams const& params);
4343
/**
4444
* Destroy an entity with the given ID.
4545
* @param id ID of the entity.
4646
* @return True if the entity was found and destroyed.
4747
*/
4848
bool destroy(std::string const& id);
4949

50-
tl::expected<nlohmann::json, std::string> command(std::string const& id,
51-
CommandParams params);
50+
tl::expected<nlohmann::json, std::string> command(
51+
std::string const& id,
52+
CommandParams const& params);
5253
};

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

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,36 @@
77
#include <launchdarkly/serialization/json_value.hpp>
88
#include <launchdarkly/value.hpp>
99

10+
#include <chrono>
11+
#include <future>
12+
1013
ClientEntity::ClientEntity(
1114
std::unique_ptr<launchdarkly::client_side::Client> client)
1215
: client_(std::move(client)) {}
1316

1417
tl::expected<nlohmann::json, std::string> ClientEntity::Identify(
15-
IdentifyEventParams params) {
16-
return tl::make_unexpected("identify not yet supported");
18+
IdentifyEventParams const& params) {
19+
boost::system::error_code ec;
20+
auto json_value = boost::json::parse(params.context.dump(), ec);
21+
if (ec) {
22+
return tl::make_unexpected(ec.what());
23+
}
24+
25+
auto maybe_ctx = boost::json::value_to<
26+
tl::expected<launchdarkly::Context, launchdarkly::JsonError>>(
27+
json_value);
28+
29+
if (!maybe_ctx) {
30+
return tl::make_unexpected(
31+
launchdarkly::ErrorToString(maybe_ctx.error()));
32+
}
33+
34+
if (!maybe_ctx->valid()) {
35+
return tl::make_unexpected(maybe_ctx->errors());
36+
}
37+
38+
client_->IdentifyAsync(*maybe_ctx).wait();
39+
return nlohmann::json{};
1740
}
1841

1942
static void BuildContextFromParams(launchdarkly::ContextBuilder& builder,
@@ -38,8 +61,8 @@ static void BuildContextFromParams(launchdarkly::ContextBuilder& builder,
3861
}
3962
}
4063

41-
tl::expected<nlohmann::json, std::string> ClientEntity::ContextBuild(
42-
ContextBuildParams params) {
64+
tl::expected<nlohmann::json, std::string> ContextBuild(
65+
ContextBuildParams const& params) {
4366
ContextResponse resp{};
4467

4568
auto builder = launchdarkly::ContextBuilder();
@@ -62,8 +85,8 @@ tl::expected<nlohmann::json, std::string> ClientEntity::ContextBuild(
6285
return resp;
6386
}
6487

65-
tl::expected<nlohmann::json, std::string> ClientEntity::ContextConvert(
66-
ContextConvertParams params) {
88+
tl::expected<nlohmann::json, std::string> ContextConvert(
89+
ContextConvertParams const& params) {
6790
ContextResponse resp{};
6891

6992
boost::system::error_code ec;
@@ -92,7 +115,7 @@ tl::expected<nlohmann::json, std::string> ClientEntity::ContextConvert(
92115
}
93116

94117
tl::expected<nlohmann::json, std::string> ClientEntity::Custom(
95-
CustomEventParams params) {
118+
CustomEventParams const& params) {
96119
auto data = params.data ? boost::json::value_to<launchdarkly::Value>(
97120
boost::json::parse(params.data->dump()))
98121
: launchdarkly::Value::Null();
@@ -113,7 +136,7 @@ tl::expected<nlohmann::json, std::string> ClientEntity::Custom(
113136
}
114137

115138
tl::expected<nlohmann::json, std::string> ClientEntity::EvaluateAll(
116-
EvaluateAllFlagParams params) {
139+
EvaluateAllFlagParams const& params) {
117140
EvaluateAllFlagsResponse resp{};
118141

119142
boost::ignore_unused(params);
@@ -127,7 +150,7 @@ tl::expected<nlohmann::json, std::string> ClientEntity::EvaluateAll(
127150
}
128151

129152
tl::expected<nlohmann::json, std::string> ClientEntity::EvaluateDetail(
130-
EvaluateFlagParams params) {
153+
EvaluateFlagParams const& params) {
131154
auto const& key = params.flagKey;
132155

133156
auto const& defaultVal = params.defaultValue;
@@ -202,7 +225,7 @@ tl::expected<nlohmann::json, std::string> ClientEntity::EvaluateDetail(
202225
return result;
203226
}
204227
tl::expected<nlohmann::json, std::string> ClientEntity::Evaluate(
205-
EvaluateFlagParams params) {
228+
EvaluateFlagParams const& params) {
206229
if (params.detail) {
207230
return EvaluateDetail(params);
208231
}
@@ -279,7 +302,7 @@ tl::expected<nlohmann::json, std::string> ClientEntity::Command(
279302
}
280303
return Custom(*params.customEvent);
281304
case Command::FlushEvents:
282-
client_->AsyncFlush();
305+
client_->FlushAsync();
283306
return nlohmann::json{};
284307
case Command::ContextBuild:
285308
if (!params.contextBuild) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ ParseContext(nlohmann::json value) {
2323
boost_json_val);
2424
}
2525

26-
std::optional<std::string> EntityManager::create(ConfigParams in) {
26+
std::optional<std::string> EntityManager::create(ConfigParams const& in) {
2727
std::string id = std::to_string(counter_++);
2828

2929
auto config_builder = ConfigBuilder(in.credential);
@@ -159,7 +159,7 @@ bool EntityManager::destroy(std::string const& id) {
159159

160160
tl::expected<nlohmann::json, std::string> EntityManager::command(
161161
std::string const& id,
162-
CommandParams params) {
162+
CommandParams const& params) {
163163
auto it = entities_.find(id);
164164
if (it == entities_.end()) {
165165
return tl::make_unexpected("entity not found");

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

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,10 @@
11
events/summary events/basic counter behavior
2-
events/summary events/context kinds
32
events/summary events/reset after each flush
4-
events/identify events/basic properties/single kind default
5-
events/identify events/basic properties/single kind non-default
6-
events/identify events/basic properties/multi-kind
7-
events/custom events/basic properties/single kind default
8-
events/custom events/basic properties/single kind non-default
9-
events/custom events/basic properties/multi-kind
10-
events/context properties/single-kind minimal/debug event
11-
events/context properties/single-kind minimal/identify event
12-
events/context properties/multi-kind minimal/debug event
13-
events/context properties/multi-kind minimal/identify event
14-
events/context properties/single-kind with attributes, nothing private/debug event
15-
events/context properties/single-kind with attributes, nothing private/identify event
16-
events/context properties/single-kind, allAttributesPrivate/debug event
17-
events/context properties/single-kind, allAttributesPrivate/identify event
18-
events/context properties/single-kind, specific private attributes/debug event
19-
events/context properties/single-kind, specific private attributes/identify event
20-
events/context properties/single-kind, private attribute nested property/debug event
21-
events/context properties/single-kind, private attribute nested property/identify event
22-
events/context properties/custom attribute with value false/debug event
23-
events/context properties/custom attribute with value false/identify event
24-
events/context properties/custom attribute with value true/debug event
25-
events/context properties/custom attribute with value true/identify event
26-
events/context properties/custom attribute with value -1000/debug event
27-
events/context properties/custom attribute with value -1000/identify event
28-
events/context properties/custom attribute with value 0/debug event
29-
events/context properties/custom attribute with value 0/identify event
30-
events/context properties/custom attribute with value 1000/debug event
31-
events/context properties/custom attribute with value 1000/identify event
32-
events/context properties/custom attribute with value -1000.5/debug event
33-
events/context properties/custom attribute with value -1000.5/identify event
34-
events/context properties/custom attribute with value 1000.5/debug event
35-
events/context properties/custom attribute with value 1000.5/identify event
36-
events/context properties/custom attribute with value ""/debug event
37-
events/context properties/custom attribute with value ""/identify event
38-
events/context properties/custom attribute with value "abc"/debug event
39-
events/context properties/custom attribute with value "abc"/identify event
40-
events/context properties/custom attribute with value "has \"escaped\" characters"/debug event
41-
events/context properties/custom attribute with value "has \"escaped\" characters"/identify event
42-
events/context properties/custom attribute with value []/debug event
43-
events/context properties/custom attribute with value []/identify event
44-
events/context properties/custom attribute with value ["a","b"]/debug event
45-
events/context properties/custom attribute with value ["a","b"]/identify event
46-
events/context properties/custom attribute with value {}/debug event
47-
events/context properties/custom attribute with value {}/identify event
48-
events/context properties/custom attribute with value {"a":1}/debug event
49-
events/context properties/custom attribute with value {"a":1}/identify event
50-
events/disabling/identify event
513
streaming/requests/query parameters/evaluationReasons set to true/GET
524
streaming/requests/query parameters/evaluationReasons set to true/REPORT
535
tags/disallowed characters
6+
7+
# The Client doesn't need to know how to deserialize users.
548
context type/convert/old user to context/{"key": ""}
559
context type/convert/old user to context/{"key": "a"}
5610
context type/convert/old user to context/{"key": "a"}

apps/sse-contract-tests/include/entity_manager.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class EntityManager {
4242
* @return An ID representing the entity, or none if the entity couldn't
4343
* be created.
4444
*/
45-
std::optional<std::string> create(ConfigParams params);
45+
std::optional<std::string> create(ConfigParams const& params);
4646
/**
4747
* Destroy an entity with the given ID.
4848
* @param id ID of the entity.

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ using launchdarkly::LogLevel;
55

66
EntityManager::EntityManager(boost::asio::any_io_executor executor,
77
launchdarkly::Logger& logger)
8-
: entities_(),
9-
counter_{0},
10-
executor_{std::move(executor)},
11-
logger_{logger} {}
8+
: counter_{0}, executor_{std::move(executor)}, logger_{logger} {}
129

13-
std::optional<std::string> EntityManager::create(ConfigParams params) {
10+
std::optional<std::string> EntityManager::create(ConfigParams const& params) {
1411
std::string id = std::to_string(counter_++);
1512

1613
auto poster = std::make_shared<EventOutbox>(executor_, params.callbackUrl);
@@ -20,8 +17,8 @@ std::optional<std::string> EntityManager::create(ConfigParams params) {
2017
launchdarkly::sse::Builder(executor_, params.streamUrl);
2118

2219
if (params.headers) {
23-
for (auto const& h : *params.headers) {
24-
client_builder.header(h.first, h.second);
20+
for (auto const& header : *params.headers) {
21+
client_builder.header(header.first, header.second);
2522
}
2623
}
2724

@@ -65,7 +62,7 @@ bool EntityManager::destroy(std::string const& id) {
6562
return false;
6663
}
6764

68-
it->second.first->close();
65+
it->second.first->async_shutdown(nullptr);
6966
it->second.second->stop();
7067

7168
entities_.erase(it);

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include <chrono>
4+
#include <future>
45
#include <memory>
56
#include <string>
67
#include <unordered_map>
@@ -87,16 +88,23 @@ class IClient {
8788
* Tells the client that all pending analytics events (if any) should be
8889
* delivered as soon as possible.
8990
*/
90-
virtual void AsyncFlush() = 0;
91+
virtual void FlushAsync() = 0;
9192

9293
/**
9394
* Changes the current evaluation context, requests flags for that context
9495
* from LaunchDarkly if we are online, and generates an analytics event to
9596
* tell LaunchDarkly about the context.
9697
*
98+
* Only one IdentifyAsync can be in progress at once; calling it
99+
* concurrently is undefined behavior.
100+
*
101+
* To block until the identify operation is complete, call wait() on
102+
* the returned future.
103+
*
97104
* @param context The new evaluation context.
98105
*/
99-
virtual void AsyncIdentify(Context context) = 0;
106+
107+
virtual std::future<void> IdentifyAsync(Context context) = 0;
100108

101109
/**
102110
* Returns the boolean value of a feature flag for a given flag key.
@@ -265,9 +273,9 @@ class Client : public IClient {
265273

266274
void Track(std::string event_name) override;
267275

268-
void AsyncFlush() override;
276+
void FlushAsync() override;
269277

270-
void AsyncIdentify(Context context) override;
278+
std::future<void> IdentifyAsync(Context context) override;
271279

272280
bool BoolVariation(FlagKey const& key, bool default_value) override;
273281

@@ -307,4 +315,4 @@ class Client : public IClient {
307315
std::unique_ptr<IClient> client;
308316
};
309317

310-
} // namespace launchdarkly::client_side
318+
} // namespace launchdarkly::client_side

libs/client-sdk/src/client.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ void Client::Track(std::string event_name) {
2828
client->Track(std::move(event_name));
2929
}
3030

31-
void Client::AsyncFlush() {
32-
client->AsyncFlush();
31+
void Client::FlushAsync() {
32+
client->FlushAsync();
3333
}
3434

35-
void Client::AsyncIdentify(Context context) {
36-
client->AsyncIdentify(std::move(context));
35+
std::future<void> Client::IdentifyAsync(Context context) {
36+
return client->IdentifyAsync(std::move(context));
3737
}
3838

3939
bool Client::BoolVariation(FlagKey const& key, bool default_value) {

0 commit comments

Comments
 (0)