Skip to content

Commit 03b7de1

Browse files
authored
feat: redis data source C bindings (#345)
This adds C bindings for creating a Redis Source, as well as a LazyLoad config method to accept pointers to the C type representing an instantiated Redis Source.
1 parent 07661c4 commit 03b7de1

File tree

14 files changed

+387
-15
lines changed

14 files changed

+387
-15
lines changed

examples/hello-cpp-server-redis/main.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ int main() {
5252
}
5353

5454
config_builder.DataSystem().Method(
55-
LazyLoad().Source(*redis).CacheRefresh(std::chrono::seconds(30)));
55+
LazyLoad()
56+
.Source(std::move(*redis))
57+
.CacheRefresh(std::chrono::seconds(30)));
5658

5759
auto config = config_builder.Build();
5860
if (!config) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/** @file redis_source.h
2+
* @brief LaunchDarkly Server-side Redis Source C Binding.
3+
*/
4+
// NOLINTBEGIN modernize-use-using
5+
#pragma once
6+
7+
#include <launchdarkly/bindings/c/export.h>
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
// only need to export C interface if
12+
// used by C++ source code
13+
#endif
14+
15+
typedef struct _LDServerLazyLoadRedisSource* LDServerLazyLoadRedisSource;
16+
17+
/* Defines the size of the error message buffer in LDServerLazyLoadResult.
18+
*/
19+
#ifndef LDSERVER_LAZYLOAD_REDISSOURCE_ERROR_MESSAGE_SIZE
20+
#define LDSERVER_LAZYLOAD_REDISSOURCE_ERROR_MESSAGE_SIZE 256
21+
#endif
22+
23+
/**
24+
* @brief Stores the result of calling LDDServerLazyLoadRedisSource_New.
25+
*
26+
* On successful creation, source will contain a pointer which may be passed
27+
* into the LaunchDarkly SDK's configuration.
28+
*
29+
* On failure, error_message contains a NULL-terminated string describing the
30+
* error.
31+
*
32+
* The message may be truncated if it was originally longer than error_message's
33+
* buffer size.
34+
*/
35+
struct LDServerLazyLoadRedisResult {
36+
LDServerLazyLoadRedisSource source;
37+
char error_message[LDSERVER_LAZYLOAD_REDISSOURCE_ERROR_MESSAGE_SIZE];
38+
};
39+
40+
/**
41+
* @brief Creates a new Redis data source suitable for usage in the SDK's
42+
* Lazy Load data system.
43+
*
44+
* In this system, the SDK will query Redis for flag/segment
45+
* data as required, with an in-memory cache to reduce the number of queries.
46+
*
47+
* Data is never written back to Redis.
48+
*
49+
* @param uri Redis URI string. Must not be NULL or empty string.
50+
*
51+
* @param prefix Prefix to use when reading SDK data from Redis. This allows
52+
* multiple SDK environments to coexist in the same database, or for the SDK's
53+
* data to coexist with other unrelated data. Must not be NULL.
54+
*
55+
* @param out_result Pointer to struct where the source pointer or an error
56+
* message should be stored.
57+
*
58+
* @return True if the source was created successfully; out_result->source
59+
* will contain the pointer. The caller must either free the
60+
* pointer with LDServerLazyLoadRedisSource_Free, OR pass it into the SDK's
61+
* configuration methods which will take ownership (in which case do not call
62+
* LDServerLazyLoadRedisSource_Free.)
63+
*/
64+
LD_EXPORT(bool)
65+
LDServerLazyLoadRedisSource_New(char const* uri,
66+
char const* prefix,
67+
struct LDServerLazyLoadRedisResult* out_result);
68+
69+
/**
70+
* @brief Frees a Redis data source pointer. Only necessary to call if not
71+
* passing ownership to SDK configuration.
72+
*/
73+
LD_EXPORT(void)
74+
LDServerLazyLoadRedisSource_Free(LDServerLazyLoadRedisSource source);
75+
76+
#ifdef __cplusplus
77+
}
78+
#endif
79+
80+
// NOLINTEND modernize-use-using

libs/server-sdk-redis-source/include/launchdarkly/server_side/integrations/redis/redis_source.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class RedisDataSource final : public ISerializedDataReader {
5656
[[nodiscard]] std::string const& Identity() const override;
5757
[[nodiscard]] bool Initialized() const override;
5858

59+
~RedisDataSource() override; // = default
60+
5961
private:
6062
RedisDataSource(std::unique_ptr<sw::redis::Redis> redis,
6163
std::string prefix);

libs/server-sdk-redis-source/src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ target_sources(${LIBNAME}
1414
PRIVATE
1515
${HEADER_LIST}
1616
redis_source.cpp
17+
bindings/redis/redis_source.cpp
1718
)
1819

1920

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include <launchdarkly/server_side/bindings/c/integrations/redis/redis_source.h>
2+
3+
#include <launchdarkly/server_side/integrations/redis/redis_source.hpp>
4+
5+
#include <launchdarkly/detail/c_binding_helpers.hpp>
6+
#include <launchdarkly/error.hpp>
7+
8+
#include <redis++.h>
9+
10+
using namespace launchdarkly::server_side::integrations;
11+
12+
LD_EXPORT(bool)
13+
LDServerLazyLoadRedisSource_New(char const* uri,
14+
char const* prefix,
15+
LDServerLazyLoadRedisResult* out_result) {
16+
LD_ASSERT_NOT_NULL(uri);
17+
LD_ASSERT_NOT_NULL(prefix);
18+
LD_ASSERT_NOT_NULL(out_result);
19+
20+
// Explicitely zero out the exception_msg buffer in case the exception is
21+
// shorter than the buffer.
22+
memset(out_result->error_message, 0,
23+
sizeof(LDServerLazyLoadRedisResult::error_message));
24+
25+
// Ensure the source pointer isn't garbage.
26+
out_result->source = nullptr;
27+
28+
auto maybe_source = RedisDataSource::Create(uri, prefix);
29+
if (!maybe_source) {
30+
// Avoid heap allocating another string to pass back to the caller;
31+
// instead, we copy into the buffer and ensure a terminator is present.
32+
// This does mean the message may be truncated.
33+
34+
std::size_t const len = maybe_source.error().copy(
35+
out_result->error_message, sizeof(out_result->error_message) - 1);
36+
out_result->error_message[len] = '\0';
37+
38+
return false;
39+
}
40+
41+
// The pointer is no longer managed and must either be freed by the caller,
42+
// or passed into the SDK which will take ownership.
43+
out_result->source =
44+
reinterpret_cast<LDServerLazyLoadRedisSource>(maybe_source->release());
45+
46+
return true;
47+
}
48+
49+
LD_EXPORT(void)
50+
LDServerLazyLoadRedisSource_Free(LDServerLazyLoadRedisSource source) {
51+
delete reinterpret_cast<RedisDataSource*>(source);
52+
}

libs/server-sdk-redis-source/src/redis_source.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ RedisDataSource::Create(std::string uri, std::string prefix) {
1414
}
1515
}
1616

17+
RedisDataSource::~RedisDataSource() = default;
18+
1719
std::string RedisDataSource::key_for_kind(
1820
ISerializedItemKind const& kind) const {
1921
return prefix_ + ":" + kind.Namespace();
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <launchdarkly/server_side/bindings/c/integrations/redis/redis_source.h>
4+
5+
TEST(RedisBindings, SourcePointerIsStoredOnSuccessfulCreation) {
6+
LDServerLazyLoadRedisResult result;
7+
ASSERT_TRUE(LDServerLazyLoadRedisSource_New("tcp://localhost:1234", "foo",
8+
&result));
9+
ASSERT_NE(result.source, nullptr);
10+
LDServerLazyLoadRedisSource_Free(result.source);
11+
}
12+
13+
TEST(RedisBindings, ErrorMessageIsPropagatedOnFailure) {
14+
LDServerLazyLoadRedisResult result;
15+
ASSERT_FALSE(
16+
LDServerLazyLoadRedisSource_New("totally not a URI", "foo", &result));
17+
// Note: this test might begin failing if the Redis++ library ever returns
18+
// a different string here. The important thing is not the exact message,
19+
// but that the message was propagated.
20+
ASSERT_STREQ(result.error_message, "invalid URI: no scheme");
21+
}
22+
23+
TEST(RedisBindings, SourcePointerIsNullptrOnFailure) {
24+
LDServerLazyLoadRedisResult result;
25+
ASSERT_FALSE(
26+
LDServerLazyLoadRedisSource_New("totally not a URI", "foo", &result));
27+
ASSERT_EQ(result.source, nullptr);
28+
}

libs/server-sdk/include/launchdarkly/server_side/bindings/c/config/builder.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#pragma once
55

66
#include <launchdarkly/server_side/bindings/c/config/config.h>
7+
#include <launchdarkly/server_side/bindings/c/config/lazy_load_builder/lazy_load_builder.h>
78

89
#include <launchdarkly/bindings/c/config/logging_builder.h>
910
#include <launchdarkly/bindings/c/export.h>
@@ -218,6 +219,22 @@ LDServerConfigBuilder_DataSystem_BackgroundSync_Polling(
218219
LDServerConfigBuilder b,
219220
LDServerDataSourcePollBuilder poll_builder);
220221

222+
/**
223+
* Configures the Lazy Load data system. This method is mutually exclusive with
224+
* the BackgroundSync_Polling and BackgroundSync_Streaming builders.
225+
*
226+
* In this mode the SDK will query a data source on-demand as required, with an
227+
* in-memory cache to reduce the number of queries.
228+
*
229+
* @param b Server config builder. Must not be NULL.
230+
* @param lazy_load_builder The lazy load builder. The builder is consumed; do
231+
* not free it. Must not be NULL.
232+
*/
233+
LD_EXPORT(void)
234+
LDServerConfigBuilder_DataSystem_LazyLoad(
235+
LDServerConfigBuilder b,
236+
LDServerLazyLoadBuilder lazy_load_builder);
237+
221238
/**
222239
* Specify if the SDK's data system should be enabled or not.
223240
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/** @file lazy_load_builder.h */
2+
// NOLINTBEGIN modernize-use-using
3+
4+
#pragma once
5+
6+
#include <launchdarkly/bindings/c/export.h>
7+
8+
#include <stdbool.h>
9+
#include <stddef.h>
10+
11+
#ifdef __cplusplus
12+
extern "C" { // only need to export C interface if
13+
// used by C++ source code
14+
#endif
15+
16+
typedef struct _LDServerLazyLoadBuilder* LDServerLazyLoadBuilder;
17+
18+
typedef struct _LDServerLazyLoadSourcePtr* LDServerLazyLoadSourcePtr;
19+
20+
/**
21+
* @brief Specifies the action taken when a data item within the
22+
* in-memory cache expires.
23+
*
24+
* At this time, the default policy is the only supported policy so there
25+
* is no need to explicitely set it.
26+
*/
27+
enum LDLazyLoadCacheEvictionPolicy {
28+
/* No action taken; cache eviction is disabled. Stale items will be used
29+
* in evaluations if they cannot be refreshed. */
30+
LD_LAZYLOAD_CACHE_EVICTION_POLICY_DISABLED = 0
31+
};
32+
33+
/**
34+
* Creates a Lazy Load builder which can be used as the SDK's data system.
35+
*
36+
* In Lazy Load mode, the SDK will query a source for data as required, with an
37+
* in-memory cache to reduce the number of queries. This enables usage of
38+
* databases for storing feature flag/segment data.
39+
*
40+
* In contrast, the Background Sync system injects data into the SDK
41+
* asynchronously (either instantly as updates happen, in streaming mode; or
42+
* periodically, in polling mode).
43+
*
44+
* Background Sync mode is preferred for most use cases, but Lazy Load may be
45+
* beneficial when no connection to LaunchDarkly is required, such as when using
46+
* the Relay Proxy to populate a database.
47+
*/
48+
LD_EXPORT(LDServerLazyLoadBuilder)
49+
LDServerLazyLoadBuilder_New();
50+
51+
/**
52+
* @brief Frees the memory associated with a Lazy Load builder. Do not call if
53+
* the builder was consumed by the SDK config builder.
54+
* @param b The builder to free.
55+
*/
56+
LD_EXPORT(void)
57+
LDServerLazyLoadBuilder_Free(LDServerLazyLoadBuilder b);
58+
59+
/**
60+
* Configures the Lazy Load system with a source via opaque pointer to
61+
* C++ ISerializedDataReader.
62+
*
63+
* @param b The builder. Must not be NULL.
64+
* @param source The source pointer. Behavior is undefined if the pointer is not
65+
* an ISerializedDataReader. Must not be NULL.
66+
*/
67+
LD_EXPORT(void)
68+
LDServerLazyLoadBuilder_SourcePtr(LDServerLazyLoadBuilder b,
69+
LDServerLazyLoadSourcePtr source);
70+
71+
/**
72+
* @brief Specify the duration data items should live in-memory
73+
* before requiring a refresh via the database. The chosen @ref
74+
* LDLazyLoadCacheEvictionPolicy affects usage of this TTL.
75+
* @param b The builder. Must not be NULL.
76+
* @param milliseconds The time-to-live for an item in milliseconds.
77+
*/
78+
LD_EXPORT(void)
79+
LDServerLazyLoadBuilder_CacheRefreshMs(LDServerLazyLoadBuilder b,
80+
unsigned int milliseconds);
81+
82+
/**
83+
* @brief Specify the eviction policy when a data item's TTL expires.
84+
* At this time, only LD_LAZYLOAD_CACHE_EVICTION_POLICY_DISABLED is supported
85+
* (the default), which leaves stale items in the cache until they can be
86+
* refreshed.
87+
* @param b The builder. Must not be NULL.
88+
* @param policy The eviction policy.
89+
*/
90+
LD_EXPORT(void)
91+
LDServerLazyLoadBuilder_CachePolicy(LDServerLazyLoadBuilder b,
92+
enum LDLazyLoadCacheEvictionPolicy policy);
93+
94+
#ifdef __cplusplus
95+
}
96+
#endif
97+
98+
// NOLINTEND modernize-use-using

libs/server-sdk/include/launchdarkly/server_side/config/builders/data_system/lazy_load_builder.hpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace launchdarkly::server_side::config::builders {
1212

1313
/**
14-
* \brief LazyLoadBuilder allows for specifying the configuration of
14+
* @brief LazyLoadBuilder allows for specifying the configuration of
1515
* the Lazy Load data system, which is appropriate when a LaunchDarkly
1616
* environment should be stored external to the SDK (such as in Redis.)
1717
*
@@ -28,30 +28,33 @@ struct LazyLoadBuilder {
2828
using SourcePtr = std::shared_ptr<integrations::ISerializedDataReader>;
2929
using EvictionPolicy = built::LazyLoadConfig::EvictionPolicy;
3030
/**
31-
* \brief Constructs a new LazyLoadBuilder.
31+
* @brief Constructs a new LazyLoadBuilder.
3232
*/
3333
LazyLoadBuilder();
3434

3535
/**
36-
* \brief Specify the source of the data.
37-
* \param source Component implementing ISerializedDataPullSource.
38-
* \return Reference to this.
36+
* @brief Specify the source of the data.
37+
* @param source Component implementing ISerializedDataReader. Ownership is
38+
* shared.
39+
* @return Reference to this.
3940
*/
4041
LazyLoadBuilder& Source(SourcePtr source);
4142

4243
/**
43-
* \brief
44-
* \param ttl Specify the duration data items should be live in-memory
45-
* before being refreshed from the database. The chosen \ref EvictionPolicy
46-
* affects usage of this TTL. \return Reference to this.
44+
* @brief Specify the duration data items should live in-memory
45+
* before requiring a refresh via the database. The chosen @ref
46+
* EvictionPolicy affects usage of this TTL.
47+
* @param ttl The time-to-live for an item.
48+
* @return Reference to this.
4749
*/
4850
LazyLoadBuilder& CacheRefresh(std::chrono::milliseconds ttl);
4951

5052
/**
51-
* \brief Specify the eviction policy when a data item's TTL expires.
53+
* @brief Specify the eviction policy when a data item's TTL expires.
5254
* At this time, only EvictionPolicy::Disabled is supported (the default),
53-
* which leaves stale items in the cache until they can be refreshed. \param
54-
* policy The EvictionPolicy. \return Reference to this.
55+
* which leaves stale items in the cache until they can be refreshed.
56+
* @param policy The EvictionPolicy.
57+
* @return Reference to this.
5558
*/
5659
LazyLoadBuilder& CacheEviction(EvictionPolicy policy);
5760

libs/server-sdk/include/launchdarkly/server_side/config/built/data_system/lazy_load_config.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ namespace launchdarkly::server_side::config::built {
1010
struct LazyLoadConfig {
1111
/**
1212
* \brief Specifies the action taken when a data item's TTL expires.
13+
*
14+
* The values must not be changed to ensure backwards compatibility
15+
* with the C API.
1316
*/
1417
enum class EvictionPolicy {
1518
/* No action taken; eviction is disabled. Stale items will be used

0 commit comments

Comments
 (0)