C++ Server-Side SDK
LaunchDarkly SDK
Loading...
Searching...
No Matches
lazy_load_system.hpp
1#pragma once
2
3#include "../../../include/launchdarkly/server_side/integrations/data_reader/kinds.hpp"
4#include "../../data_components/expiration_tracker/expiration_tracker.hpp"
5#include "../../data_components/memory_store/memory_store.hpp"
6#include "../../data_components/status_notifications/data_source_status_manager.hpp"
7#include "../../data_interfaces/source/idata_reader.hpp"
8#include "../../data_interfaces/system/idata_system.hpp"
9
10#include <launchdarkly/server_side/config/built/data_system/lazy_load_config.hpp>
11#include <launchdarkly/server_side/integrations/data_reader/iserialized_data_reader.hpp>
12
13#include <launchdarkly/data_model/descriptors.hpp>
14#include <launchdarkly/detail/unreachable.hpp>
15#include <launchdarkly/logging/logger.hpp>
16
17namespace launchdarkly::server_side::data_systems {
18
29 public:
30 using ClockType = std::chrono::steady_clock;
31 using TimeFn = std::function<std::chrono::time_point<ClockType>()>;
32
33 explicit LazyLoad(Logger const& logger,
36
37 LazyLoad(Logger const& logger,
40 TimeFn time);
41
42 std::string const& Identity() const override;
43
44 std::shared_ptr<data_model::FlagDescriptor> GetFlag(
45 std::string const& key) const override;
46
47 std::shared_ptr<data_model::SegmentDescriptor> GetSegment(
48 std::string const& key) const override;
49
50 std::unordered_map<std::string, std::shared_ptr<data_model::FlagDescriptor>>
51 AllFlags() const override;
52
53 std::unordered_map<std::string,
54 std::shared_ptr<data_model::SegmentDescriptor>>
55 AllSegments() const override;
56
57 void Initialize() override;
58
59 bool Initialized() const override;
60
61 // Public for usage in tests.
62 struct Kinds {
63 static integrations::FlagKind const Flag;
64 static integrations::SegmentKind const Segment;
65 };
66
67 private:
68 void RefreshAllFlags() const;
69 void RefreshAllSegments() const;
70 void RefreshInitState() const;
71 void RefreshFlag(std::string const& key) const;
72 void RefreshSegment(std::string const& key) const;
73
74 static std::string CacheTraceMsg(
76
77 template <typename TResult>
78 TResult Get(std::string const& key,
80
81 std::function<void(void)> const& refresh,
82 std::function<TResult(void)> const& get) const {
83 LD_LOG(logger_, LogLevel::kDebug)
84 << Identity() << ": get " << key << " - " << CacheTraceMsg(state);
85
86 switch (state) {
88 [[fallthrough]];
90 refresh();
91 [[fallthrough]];
93 return get();
94 }
95 detail::unreachable();
96 }
97
98 template <typename Item, typename Evictor>
99 void RefreshItem(
100 data_components::DataKind const kind,
101 std::string const& key,
102 std::function<data_interfaces::IDataReader::SingleResult<Item>(
103 std::string const&)> const& getter,
104 Evictor&& evictor) const {
105 // Refreshing this item is always rate limited, even
106 // if the refresh has an error.
107 tracker_.Add(kind, key, ExpiryTime());
108
109 if (auto expected_item = getter(key)) {
110 status_manager_.SetState(DataSourceState::kValid);
111
112 if (auto optional_item = *expected_item) {
113 cache_.Upsert(key, std::move(*optional_item));
114 } else {
115 // If the item is actually *missing* - not just a deleted
116 // tombstone representation - it implies that the source
117 // was re-initialized. In this case, the correct thing to do
118 // is evict it from the memory cache
119 LD_LOG(logger_, LogLevel::kDebug)
120 << kind << key << " requested but not found via "
121 << reader_->Identity();
122 if (evictor(key)) {
123 LD_LOG(logger_, LogLevel::kDebug)
124 << "removed " << kind << " " << key << " from cache";
125 }
126 }
127 } else {
128 status_manager_.SetState(
129 DataSourceState::kInterrupted,
130 common::data_sources::DataSourceStatusErrorKind::kUnknown,
131 expected_item.error());
132
133 // If there's a persistent error, it will be logged at the refresh
134 // interval.
135 LD_LOG(logger_, LogLevel::kError)
136 << "failed to refresh " << kind << " " << key << " via "
137 << reader_->Identity() << ": " << expected_item.error();
138 }
139 }
140
141 template <typename Item>
142 void RefreshAll(
143 std::string const& all_item_key,
144 data_components::DataKind const item_kind,
145 std::function<
146 data_interfaces::IDataReader::CollectionResult<Item>()> const&
147 getter) const {
148 // The 'all' key and the individual item keys should expire
149 // at exactly the same time, because there is no separate data item
150 // for 'all' - it exists only to rate limit the refresh of all items.
151 auto const updated_expiry = ExpiryTime();
152
153 // Refreshing 'all' for this item is always rate limited, even if
154 // the refresh has an error.
155 tracker_.Add(all_item_key, updated_expiry);
156
157 if (auto all_items = getter()) {
158 status_manager_.SetState(DataSourceState::kValid);
159
160 for (auto item : *all_items) {
161 cache_.Upsert(item.first, std::move(item.second));
162 tracker_.Add(item_kind, item.first, updated_expiry);
163 }
164 } else {
165 status_manager_.SetState(
166 DataSourceState::kInterrupted,
167 common::data_sources::DataSourceStatusErrorKind::kUnknown,
168 all_items.error());
169
170 // If there's a persistent error, it will be logged at the
171 // refresh interval.
172 LD_LOG(logger_, LogLevel::kError)
173 << "failed to refresh all " << item_kind << "s via "
174 << reader_->Identity() << ": " << all_items.error();
175 }
176 }
177
178 ClockType::time_point ExpiryTime() const;
179
180 Logger const& logger_;
181
182 mutable data_components::MemoryStore cache_;
183 std::unique_ptr<data_interfaces::IDataReader> reader_;
184
185 data_components::DataSourceStatusManager& status_manager_;
186
187 mutable data_components::ExpirationTracker tracker_;
188 TimeFn time_;
189 mutable std::optional<bool> initialized_;
190
191 ClockType::duration fresh_duration_;
192
193 struct Keys {
194 static inline std::string const kAllFlags = "allFlags";
195 static inline std::string const kAllSegments = "allSegments";
196 static inline std::string const kInitialized = "initialized";
197 };
198};
199} // namespace launchdarkly::server_side::data_systems
void Add(std::string const &key, TimePoint expiration)
Definition expiration_tracker.cpp:7
void Upsert(std::string const &key, data_model::FlagDescriptor flag) override
Upsert a flag named by key.
Definition memory_store.cpp:62
IDataSystem obtains data used for flag evaluations and makes it available to other components.
Definition idata_system.hpp:11
Definition lazy_load_system.hpp:28
std::unordered_map< std::string, std::shared_ptr< data_model::SegmentDescriptor > > AllSegments() const override
Get a map of all segments.
Definition lazy_load_system.cpp:115
std::string const & Identity() const override
Definition lazy_load_system.cpp:75
std::shared_ptr< data_model::FlagDescriptor > GetFlag(std::string const &key) const override
Get the flag named by key. Returns nullptr if no such flag exists.
Definition lazy_load_system.cpp:87
std::unordered_map< std::string, std::shared_ptr< data_model::FlagDescriptor > > AllFlags() const override
Get a map of all flags.
Definition lazy_load_system.cpp:106
bool Initialized() const override
Definition lazy_load_system.cpp:123
void Initialize() override
Initializes the system. This method will be called before any of the IStore methods are called.
Definition lazy_load_system.cpp:80
std::shared_ptr< data_model::SegmentDescriptor > GetSegment(std::string const &key) const override
Get the segment named by key. Returns nullptr if no such flag exists.
Definition lazy_load_system.cpp:96