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 RefreshFlag(std::string const& key) const;
71  void RefreshSegment(std::string const& key) const;
72 
73  static std::string CacheTraceMsg(
75 
76  template <typename TResult>
77  TResult Get(std::string const& key,
79 
80  std::function<void(void)> const& refresh,
81  std::function<TResult(void)> const& get) const {
82  LD_LOG(logger_, LogLevel::kDebug)
83  << Identity() << ": get " << key << " - " << CacheTraceMsg(state);
84 
85  switch (state) {
87  [[fallthrough]];
89  refresh();
90  [[fallthrough]];
92  return get();
93  }
94  detail::unreachable();
95  }
96 
97  template <typename Item, typename Evictor>
98  void RefreshItem(
99  data_components::DataKind const kind,
100  std::string const& key,
101  std::function<data_interfaces::IDataReader::SingleResult<Item>(
102  std::string const&)> const& getter,
103  Evictor&& evictor) const {
104  // Refreshing this item is always rate limited, even
105  // if the refresh has an error.
106  tracker_.Add(kind, key, ExpiryTime());
107 
108  if (auto expected_item = getter(key)) {
109  status_manager_.SetState(DataSourceState::kValid);
110 
111  if (auto optional_item = *expected_item) {
112  cache_.Upsert(key, std::move(*optional_item));
113  } else {
114  // If the item is actually *missing* - not just a deleted
115  // tombstone representation - it implies that the source
116  // was re-initialized. In this case, the correct thing to do
117  // is evict it from the memory cache
118  LD_LOG(logger_, LogLevel::kDebug)
119  << kind << key << " requested but not found via "
120  << reader_->Identity();
121  if (evictor(key)) {
122  LD_LOG(logger_, LogLevel::kDebug)
123  << "removed " << kind << " " << key << " from cache";
124  }
125  }
126  } else {
127  status_manager_.SetState(
128  DataSourceState::kInterrupted,
129  common::data_sources::DataSourceStatusErrorKind::kUnknown,
130  expected_item.error());
131 
132  // If there's a persistent error, it will be logged at the refresh
133  // interval.
134  LD_LOG(logger_, LogLevel::kError)
135  << "failed to refresh " << kind << " " << key << " via "
136  << reader_->Identity() << ": " << expected_item.error();
137  }
138  }
139 
140  template <typename Item>
141  void RefreshAll(
142  std::string const& all_item_key,
143  data_components::DataKind const item_kind,
144  std::function<
145  data_interfaces::IDataReader::CollectionResult<Item>()> const&
146  getter) const {
147  // The 'all' key and the individual item keys should expire
148  // at exactly the same time, because there is no separate data item
149  // for 'all' - it exists only to rate limit the refresh of all items.
150  auto const updated_expiry = ExpiryTime();
151 
152  // Refreshing 'all' for this item is always rate limited, even if
153  // the refresh has an error.
154  tracker_.Add(all_item_key, updated_expiry);
155 
156  if (auto all_items = getter()) {
157  status_manager_.SetState(DataSourceState::kValid);
158 
159  for (auto item : *all_items) {
160  cache_.Upsert(item.first, std::move(item.second));
161  tracker_.Add(item_kind, item.first, updated_expiry);
162  }
163  } else {
164  status_manager_.SetState(
165  DataSourceState::kInterrupted,
166  common::data_sources::DataSourceStatusErrorKind::kUnknown,
167  all_items.error());
168 
169  // If there's a persistent error, it will be logged at the
170  // refresh interval.
171  LD_LOG(logger_, LogLevel::kError)
172  << "failed to refresh all " << item_kind << "s via "
173  << reader_->Identity() << ": " << all_items.error();
174  }
175  }
176 
177  ClockType::time_point ExpiryTime() const;
178 
179  Logger const& logger_;
180 
181  mutable data_components::MemoryStore cache_;
182  std::unique_ptr<data_interfaces::IDataReader> reader_;
183 
184  data_components::DataSourceStatusManager& status_manager_;
185 
186  mutable data_components::ExpirationTracker tracker_;
187  TimeFn time_;
188 
189  ClockType::duration fresh_duration_;
190 
191  struct Keys {
192  static inline std::string const kAllFlags = "allFlags";
193  static inline std::string const kAllSegments = "allSegments";
194  };
195 };
196 } // 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:64
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:131
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:103
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:122
bool Initialized() const override
Definition: lazy_load_system.cpp:139
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:112