Skip to content

Commit e72e001

Browse files
committed
fix: don't crash on platforms without en_US.utf8 locale when parsing date headers
1 parent e852966 commit e72e001

File tree

6 files changed

+56
-16
lines changed

6 files changed

+56
-16
lines changed

examples/hello-cpp-client/main.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#include <launchdarkly/client_side/client.hpp>
22
#include <launchdarkly/context_builder.hpp>
33

4-
#include <iostream>
54
#include <cstring>
5+
#include <iostream>
66

77
// Set MOBILE_KEY to your LaunchDarkly mobile key.
8-
#define MOBILE_KEY ""
8+
#define MOBILE_KEY "mob-0b7cf941-c423-4235-a885-0740c50eb4a3"
99

1010
// Set FEATURE_FLAG_KEY to the feature flag key you want to evaluate.
1111
#define FEATURE_FLAG_KEY "my-boolean-flag"
@@ -23,7 +23,9 @@ int main() {
2323
return 1;
2424
}
2525

26-
auto config = client_side::ConfigBuilder(MOBILE_KEY).Build();
26+
auto configg = client_side::ConfigBuilder(MOBILE_KEY);
27+
configg.Events().FlushInterval(std::chrono::seconds(10));
28+
auto config = configg.Build();
2729
if (!config) {
2830
std::cout << "error: config is invalid: " << config.error() << '\n';
2931
return 1;
@@ -50,10 +52,14 @@ int main() {
5052
return 1;
5153
}
5254

53-
bool flag_value = client.BoolVariation(FEATURE_FLAG_KEY, false);
55+
for (int i = 0; i < 100; i++) {
56+
bool flag_value = client.BoolVariation(FEATURE_FLAG_KEY, false);
5457

55-
std::cout << "*** Feature flag '" << FEATURE_FLAG_KEY << "' is "
56-
<< (flag_value ? "true" : "false") << " for this user\n\n";
58+
std::cout << "*** Feature flag '" << FEATURE_FLAG_KEY << "' is "
59+
<< (flag_value ? "true" : "false") << " for this user\n\n";
60+
61+
std::this_thread::sleep_for(std::chrono::seconds(10));
62+
}
5763

5864
return 0;
5965
}

libs/internal/include/launchdarkly/events/parse_date_header.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
namespace launchdarkly::events {
99

1010
template <typename Clock>
11+
1112
static std::optional<typename Clock::time_point> ParseDateHeader(
12-
std::string const& datetime) {
13+
std::string const& datetime,
14+
std::locale const& locale) {
1315
// The following comments may not be entirely accurate.
1416
// TODO: There must be a better way.
1517

1618
std::tm gmt_tm = {};
19+
1720
std::istringstream string_stream(datetime);
18-
string_stream.imbue(std::locale("en_US.utf-8"));
21+
string_stream.imbue(locale);
1922
string_stream >> std::get_time(&gmt_tm, "%a, %d %b %Y %H:%M:%S GMT");
2023
if (string_stream.fail()) {
2124
return std::nullopt;

libs/internal/include/launchdarkly/events/request_worker.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class RequestWorker {
9999
RequestWorker(boost::asio::any_io_executor io,
100100
std::chrono::milliseconds retry_after,
101101
std::size_t id,
102+
std::optional<std::locale> date_header_locale,
102103
Logger& logger);
103104

104105
/**
@@ -143,6 +144,8 @@ class RequestWorker {
143144
}
144145

145146
private:
147+
Logger& logger_;
148+
146149
/* Used to wait a specific amount of time after a failed request before
147150
* trying again. */
148151
boost::asio::steady_timer timer_;
@@ -163,7 +166,9 @@ class RequestWorker {
163166
/* Tag used in logs. */
164167
std::string tag_;
165168

166-
Logger& logger_;
169+
/* The en_US locale is used to parse the Date header from HTTP responses.
170+
* On some platforms, this may not be available hence the optional. */
171+
std::optional<std::locale> date_header_locale_;
167172

168173
void OnDeliveryAttempt(network::HttpResult request, ResultCallback cb);
169174
};

libs/internal/include/launchdarkly/events/worker_pool.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ namespace launchdarkly::events {
2424
*/
2525
class WorkerPool {
2626
public:
27-
using ServerTimeCallback =
28-
std::function<void(std::chrono::system_clock::time_point)>;
2927
/**
3028
* Constructs a new WorkerPool.
3129
* @param io The executor used for all workers.
@@ -70,6 +68,11 @@ class WorkerPool {
7068

7169
private:
7270
boost::asio::any_io_executor io_;
71+
72+
/* The en_US locale is used to parse the Date header from HTTP responses.
73+
* On some platforms, this may not be available hence the optional. */
74+
std::optional<std::locale> date_header_locale_;
75+
7376
std::vector<std::unique_ptr<RequestWorker>> workers_;
7477
};
7578

libs/internal/src/events/request_worker.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ namespace launchdarkly::events {
66
RequestWorker::RequestWorker(boost::asio::any_io_executor io,
77
std::chrono::milliseconds retry_after,
88
std::size_t id,
9+
std::optional<std::locale> date_header_locale,
910
Logger& logger)
10-
: timer_(io),
11+
: logger_(logger),
12+
timer_(io),
1113
retry_delay_(retry_after),
1214
state_(State::Idle),
1315
requester_(timer_.get_executor()),
1416
batch_(std::nullopt),
1517
tag_("flush-worker[" + std::to_string(id) + "]: "),
16-
logger_(logger) {}
18+
date_header_locale_(std::move(date_header_locale)) {}
1719

1820
bool RequestWorker::Available() const {
1921
return state_ == State::Idle;
@@ -81,11 +83,15 @@ void RequestWorker::OnDeliveryAttempt(network::HttpResult result,
8183
batch_.reset();
8284
break;
8385
case Action::ParseDateAndReset: {
86+
if (!date_header_locale_) {
87+
batch_.reset();
88+
break;
89+
}
8490
auto headers = result.Headers();
8591
if (auto date = headers.find("Date"); date != headers.end()) {
8692
if (auto server_time =
8793
ParseDateHeader<std::chrono::system_clock>(
88-
date->second)) {
94+
date->second, *date_header_locale_)) {
8995
callback(batch_->Count(), *server_time);
9096
}
9197
}

libs/internal/src/events/worker_pool.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,31 @@
55

66
namespace launchdarkly::events {
77

8+
std::optional<std::locale> GetLocale(std::string const& locale,
9+
std::string const& tag,
10+
Logger& logger) {
11+
try {
12+
return std::locale(locale);
13+
} catch (std::runtime_error const& err) {
14+
LD_LOG(logger, LogLevel::kWarn)
15+
<< tag << " couldn't load " << locale
16+
<< " locale. If debug events are enabled, they may be emitted for "
17+
"longer "
18+
"than expected";
19+
return std::nullopt;
20+
}
21+
}
22+
823
WorkerPool::WorkerPool(boost::asio::any_io_executor io,
924
std::size_t pool_size,
1025
std::chrono::milliseconds delivery_retry_delay,
1126
Logger& logger)
12-
: io_(io), workers_() {
27+
: io_(io),
28+
workers_(),
29+
date_header_locale_(GetLocale("en_US.utf-8", "event-processor", logger)) {
1330
for (std::size_t i = 0; i < pool_size; i++) {
1431
workers_.emplace_back(std::make_unique<RequestWorker>(
15-
io_, delivery_retry_delay, i, logger));
32+
io_, delivery_retry_delay, i, date_header_locale_, logger));
1633
}
1734
}
1835

0 commit comments

Comments
 (0)