C++ Server-Side SDK
LaunchDarkly SDK
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 
17 namespace launchdarkly::server_side::data_systems {
18 
28 class LazyLoad final : public data_interfaces::IDataSystem {
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
Definition: data_source_status_manager.hpp:16
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