Skip to content

Commit e4d4858

Browse files
committed
feat: add internal BigSegmentsBuilder and BigSegmentsConfig
1 parent 136aca7 commit e4d4858

5 files changed

Lines changed: 320 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include <launchdarkly/server_side/integrations/big_segments/ibig_segment_store.hpp>
4+
5+
#include <chrono>
6+
#include <cstddef>
7+
#include <memory>
8+
9+
namespace launchdarkly::server_side::config::built {
10+
11+
struct BigSegmentsConfig {
12+
std::shared_ptr<integrations::IBigSegmentStore> store;
13+
std::size_t context_cache_size;
14+
std::chrono::milliseconds context_cache_time;
15+
std::chrono::milliseconds status_poll_interval;
16+
std::chrono::milliseconds stale_after;
17+
};
18+
19+
} // namespace launchdarkly::server_side::config::built

libs/server-sdk/src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ file(GLOB HEADER_LIST CONFIGURE_DEPENDS
44
"${LaunchDarklyCPPServer_SOURCE_DIR}/include/launchdarkly/server_side/integrations/*.hpp"
55
"${LaunchDarklyCPPServer_SOURCE_DIR}/include/launchdarkly/server_side/integrations/big_segments/*.hpp"
66
"${LaunchDarklyCPPServer_SOURCE_DIR}/include/launchdarkly/server_side/hooks/*.hpp"
7+
"${LaunchDarklyCPPServer_SOURCE_DIR}/include/launchdarkly/server_side/config/built/*.hpp"
78
)
89

910
if (LD_BUILD_SHARED_LIBS)
@@ -30,6 +31,7 @@ target_sources(${LIBNAME}
3031
config/builders/data_system/data_system_builder.cpp
3132
config/builders/data_system/lazy_load_builder.cpp
3233
config/builders/data_system/data_destination_builder.cpp
34+
config/builders/big_segments_builder.cpp
3335
all_flags_state/all_flags_state.cpp
3436
all_flags_state/json_all_flags_state.cpp
3537
all_flags_state/all_flags_state_builder.cpp
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include "big_segments_builder.hpp"
2+
3+
#include <algorithm>
4+
#include <utility>
5+
6+
namespace launchdarkly::server_side::config::builders {
7+
8+
BigSegmentsBuilder::BigSegmentsBuilder(
9+
std::shared_ptr<integrations::IBigSegmentStore> store)
10+
: store_(std::move(store)),
11+
context_cache_size_(kDefaultContextCacheSize),
12+
context_cache_time_(kDefaultContextCacheTime),
13+
status_poll_interval_(kDefaultStatusPollInterval),
14+
stale_after_(kDefaultStaleAfter) {}
15+
16+
BigSegmentsBuilder& BigSegmentsBuilder::ContextCacheSize(
17+
std::size_t const size) {
18+
context_cache_size_ = size;
19+
return *this;
20+
}
21+
22+
BigSegmentsBuilder& BigSegmentsBuilder::ContextCacheTime(
23+
std::chrono::milliseconds const ttl) {
24+
context_cache_time_ = ttl > std::chrono::milliseconds::zero()
25+
? ttl
26+
: kDefaultContextCacheTime;
27+
return *this;
28+
}
29+
30+
BigSegmentsBuilder& BigSegmentsBuilder::StatusPollInterval(
31+
std::chrono::milliseconds const interval) {
32+
status_poll_interval_ = interval > std::chrono::milliseconds::zero()
33+
? interval
34+
: kDefaultStatusPollInterval;
35+
return *this;
36+
}
37+
38+
BigSegmentsBuilder& BigSegmentsBuilder::StaleAfter(
39+
std::chrono::milliseconds const threshold) {
40+
stale_after_ = threshold > std::chrono::milliseconds::zero()
41+
? threshold
42+
: kDefaultStaleAfter;
43+
return *this;
44+
}
45+
46+
built::BigSegmentsConfig BigSegmentsBuilder::Build() const {
47+
auto const poll = std::min(status_poll_interval_, stale_after_);
48+
return built::BigSegmentsConfig{store_, context_cache_size_,
49+
context_cache_time_, poll, stale_after_};
50+
}
51+
52+
} // namespace launchdarkly::server_side::config::builders
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#pragma once
2+
3+
#include <launchdarkly/server_side/config/built/big_segments_config.hpp>
4+
#include <launchdarkly/server_side/integrations/big_segments/ibig_segment_store.hpp>
5+
6+
#include <chrono>
7+
#include <cstddef>
8+
#include <memory>
9+
10+
namespace launchdarkly::server_side::config::builders {
11+
12+
/**
13+
* @brief Configures the SDK's Big Segments behavior.
14+
*
15+
* A Big Segments store implementation is required at construction. The
16+
* tunables that control the SDK's Big Segments caching and staleness
17+
* detection have sensible defaults and can be overridden via the fluent
18+
* setters.
19+
*
20+
* Non-positive durations passed to a setter are coerced back to the
21+
* default for that field.
22+
*
23+
* Not thread-safe. Construct, configure, and call @ref Build on a single
24+
* thread; the resulting @ref built::BigSegmentsConfig is safe to share.
25+
*/
26+
class BigSegmentsBuilder {
27+
public:
28+
static constexpr std::size_t kDefaultContextCacheSize = 1000;
29+
static constexpr std::chrono::milliseconds kDefaultContextCacheTime{5000};
30+
static constexpr std::chrono::milliseconds kDefaultStatusPollInterval{5000};
31+
static constexpr std::chrono::milliseconds kDefaultStaleAfter{120000};
32+
33+
/**
34+
* @brief Constructs a builder for the given Big Segments store.
35+
*
36+
* @param store The Big Segments store implementation. Shared ownership;
37+
* the SDK retains a reference for the lifetime of the client.
38+
*/
39+
explicit BigSegmentsBuilder(
40+
std::shared_ptr<integrations::IBigSegmentStore> store);
41+
42+
/**
43+
* @brief Sets the maximum number of context membership lookups cached
44+
* by the SDK.
45+
*
46+
* To reduce store traffic, the SDK maintains an LRU cache keyed by
47+
* context key. A higher value reduces store queries for
48+
* recently-referenced contexts at the cost of memory.
49+
*/
50+
BigSegmentsBuilder& ContextCacheSize(std::size_t size);
51+
52+
/**
53+
* @brief Sets the time-to-live for cached membership lookups.
54+
*
55+
* A higher value reduces store queries for any given context, but
56+
* delays the SDK noticing membership changes. Zero or negative
57+
* durations are coerced to @ref kDefaultContextCacheTime.
58+
*/
59+
BigSegmentsBuilder& ContextCacheTime(std::chrono::milliseconds ttl);
60+
61+
/**
62+
* @brief Sets the interval at which the SDK polls the store's metadata
63+
* to determine availability and staleness.
64+
*
65+
* Zero or negative durations are coerced to
66+
* @ref kDefaultStatusPollInterval.
67+
*/
68+
BigSegmentsBuilder& StatusPollInterval(std::chrono::milliseconds interval);
69+
70+
/**
71+
* @brief Sets how long the SDK waits before treating store data as
72+
* stale.
73+
*
74+
* If the store's last-updated timestamp falls behind the current time
75+
* by more than this duration, evaluations report
76+
* @ref launchdarkly::EvaluationReason::BigSegmentsStatus::STALE and the
77+
* status provider reports the store as stale. Zero or negative
78+
* durations are coerced to @ref kDefaultStaleAfter.
79+
*/
80+
BigSegmentsBuilder& StaleAfter(std::chrono::milliseconds threshold);
81+
82+
/**
83+
* @brief Resolves the configuration.
84+
*
85+
* If the configured @ref StatusPollInterval exceeds @ref StaleAfter,
86+
* the poll interval is clamped to the stale-after value so the SDK can
87+
* detect staleness within one poll cycle (per BIGSEG §1.4.6).
88+
*/
89+
[[nodiscard]] built::BigSegmentsConfig Build() const;
90+
91+
private:
92+
std::shared_ptr<integrations::IBigSegmentStore> store_;
93+
std::size_t context_cache_size_;
94+
std::chrono::milliseconds context_cache_time_;
95+
std::chrono::milliseconds status_poll_interval_;
96+
std::chrono::milliseconds stale_after_;
97+
};
98+
99+
} // namespace launchdarkly::server_side::config::builders
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <launchdarkly/server_side/integrations/big_segments/big_segment_store_types.hpp>
4+
#include <launchdarkly/server_side/integrations/big_segments/ibig_segment_store.hpp>
5+
6+
#include "config/builders/big_segments_builder.hpp"
7+
8+
#include <chrono>
9+
#include <memory>
10+
11+
using launchdarkly::server_side::config::builders::BigSegmentsBuilder;
12+
using launchdarkly::server_side::integrations::IBigSegmentStore;
13+
using launchdarkly::server_side::integrations::Membership;
14+
using launchdarkly::server_side::integrations::StoreMetadata;
15+
16+
namespace {
17+
18+
// Minimal stub used only to obtain a shared_ptr<IBigSegmentStore>. The builder
19+
// never invokes the store; it only stores the pointer for later use by the
20+
// wrapper, so the methods here are unreachable in these tests.
21+
class StubStore final : public IBigSegmentStore {
22+
public:
23+
GetMembershipResult GetMembership(
24+
std::string const& /*context_hash*/) const override {
25+
return Membership::FromSegmentRefs({}, {});
26+
}
27+
GetMetadataResult GetMetadata() const override {
28+
return std::optional<StoreMetadata>{};
29+
}
30+
};
31+
32+
std::shared_ptr<IBigSegmentStore> MakeStubStore() {
33+
return std::make_shared<StubStore>();
34+
}
35+
36+
} // namespace
37+
38+
TEST(BigSegmentsBuilderTest, DefaultsMatchSpec) {
39+
auto store = MakeStubStore();
40+
auto const cfg = BigSegmentsBuilder(store).Build();
41+
42+
EXPECT_EQ(cfg.context_cache_size,
43+
BigSegmentsBuilder::kDefaultContextCacheSize);
44+
EXPECT_EQ(cfg.context_cache_time,
45+
BigSegmentsBuilder::kDefaultContextCacheTime);
46+
EXPECT_EQ(cfg.status_poll_interval,
47+
BigSegmentsBuilder::kDefaultStatusPollInterval);
48+
EXPECT_EQ(cfg.stale_after, BigSegmentsBuilder::kDefaultStaleAfter);
49+
}
50+
51+
TEST(BigSegmentsBuilderTest, BuildPreservesStoreIdentity) {
52+
auto store = MakeStubStore();
53+
auto const cfg = BigSegmentsBuilder(store).Build();
54+
EXPECT_EQ(cfg.store.get(), store.get());
55+
}
56+
57+
TEST(BigSegmentsBuilderTest, AcceptsNullStore) {
58+
// The builder doesn't validate the store; downstream components treat a
59+
// null store as "Big Segments not configured".
60+
auto const cfg = BigSegmentsBuilder(nullptr).Build();
61+
EXPECT_EQ(cfg.store, nullptr);
62+
}
63+
64+
TEST(BigSegmentsBuilderTest, SettersOverrideEachField) {
65+
using namespace std::chrono_literals;
66+
auto store = MakeStubStore();
67+
auto const cfg = BigSegmentsBuilder(store)
68+
.ContextCacheSize(7)
69+
.ContextCacheTime(11s)
70+
.StatusPollInterval(13s)
71+
.StaleAfter(60s)
72+
.Build();
73+
74+
EXPECT_EQ(cfg.context_cache_size, 7u);
75+
EXPECT_EQ(cfg.context_cache_time, 11s);
76+
EXPECT_EQ(cfg.status_poll_interval, 13s);
77+
EXPECT_EQ(cfg.stale_after, 60s);
78+
}
79+
80+
TEST(BigSegmentsBuilderTest, ZeroDurationsAreCoercedToDefaults) {
81+
auto store = MakeStubStore();
82+
auto const cfg = BigSegmentsBuilder(store)
83+
.ContextCacheTime(std::chrono::milliseconds::zero())
84+
.StatusPollInterval(std::chrono::milliseconds::zero())
85+
.StaleAfter(std::chrono::milliseconds::zero())
86+
.Build();
87+
88+
EXPECT_EQ(cfg.context_cache_time,
89+
BigSegmentsBuilder::kDefaultContextCacheTime);
90+
EXPECT_EQ(cfg.status_poll_interval,
91+
BigSegmentsBuilder::kDefaultStatusPollInterval);
92+
EXPECT_EQ(cfg.stale_after, BigSegmentsBuilder::kDefaultStaleAfter);
93+
}
94+
95+
TEST(BigSegmentsBuilderTest, NegativeDurationsAreCoercedToDefaults) {
96+
auto store = MakeStubStore();
97+
auto const cfg = BigSegmentsBuilder(store)
98+
.ContextCacheTime(std::chrono::milliseconds{-1})
99+
.StatusPollInterval(std::chrono::milliseconds{-1})
100+
.StaleAfter(std::chrono::milliseconds{-1})
101+
.Build();
102+
103+
EXPECT_EQ(cfg.context_cache_time,
104+
BigSegmentsBuilder::kDefaultContextCacheTime);
105+
EXPECT_EQ(cfg.status_poll_interval,
106+
BigSegmentsBuilder::kDefaultStatusPollInterval);
107+
EXPECT_EQ(cfg.stale_after, BigSegmentsBuilder::kDefaultStaleAfter);
108+
}
109+
110+
TEST(BigSegmentsBuilderTest, BuildClampsPollIntervalToStaleAfter) {
111+
// BIGSEG §1.4.6: when poll interval > stale-after, clamp poll to
112+
// stale-after so the SDK detects staleness within one poll cycle.
113+
using namespace std::chrono_literals;
114+
auto store = MakeStubStore();
115+
auto const cfg = BigSegmentsBuilder(store)
116+
.StatusPollInterval(10s)
117+
.StaleAfter(3s)
118+
.Build();
119+
120+
EXPECT_EQ(cfg.status_poll_interval, 3s);
121+
EXPECT_EQ(cfg.stale_after, 3s);
122+
}
123+
124+
TEST(BigSegmentsBuilderTest, BuildPreservesPollIntervalWhenWithinStaleAfter) {
125+
using namespace std::chrono_literals;
126+
auto store = MakeStubStore();
127+
auto const cfg = BigSegmentsBuilder(store)
128+
.StatusPollInterval(3s)
129+
.StaleAfter(10s)
130+
.Build();
131+
132+
EXPECT_EQ(cfg.status_poll_interval, 3s);
133+
EXPECT_EQ(cfg.stale_after, 10s);
134+
}
135+
136+
TEST(BigSegmentsBuilderTest, BuildIsRepeatable) {
137+
using namespace std::chrono_literals;
138+
auto store = MakeStubStore();
139+
BigSegmentsBuilder builder(store);
140+
builder.ContextCacheSize(42).ContextCacheTime(2s);
141+
142+
auto const cfg1 = builder.Build();
143+
auto const cfg2 = builder.Build();
144+
145+
EXPECT_EQ(cfg1.context_cache_size, cfg2.context_cache_size);
146+
EXPECT_EQ(cfg1.context_cache_time, cfg2.context_cache_time);
147+
EXPECT_EQ(cfg1.store.get(), cfg2.store.get());
148+
}

0 commit comments

Comments
 (0)