|
1 | 1 | #include "events/detail/asio_event_processor.hpp"
|
| 2 | +#include <boost/asio/post.hpp> |
| 3 | +#include <boost/beast/http.hpp> |
2 | 4 |
|
3 |
| -#include <boost/asio/strand.hpp> |
4 |
| -#include <chrono> |
| 5 | +#include <boost/lexical_cast.hpp> |
| 6 | +#include <boost/uuid/uuid_io.hpp> |
5 | 7 |
|
| 8 | +#include "serialization/events/json_events.hpp" |
| 9 | + |
| 10 | +namespace http = boost::beast::http; |
6 | 11 | namespace launchdarkly::events::detail {
|
7 | 12 |
|
8 |
| -AsioEventProcessor::AsioEventProcessor( |
9 |
| - boost::asio::any_io_executor const& executor, |
10 |
| - config::detail::Events const& config, |
11 |
| - config::ServiceHosts const& endpoints, |
12 |
| - Logger& logger) |
13 |
| - : logger_(logger), |
14 |
| - dispatcher_(boost::asio::make_strand(executor), |
15 |
| - config, |
16 |
| - endpoints, |
17 |
| - "password", |
18 |
| - logger) {} |
| 13 | +auto const kEventSchemaHeader = "X-LaunchDarkly-Event-Schema"; |
| 14 | +auto const kPayloadIdHeader = "X-LaunchDarkly-Payload-Id"; |
| 15 | + |
| 16 | +auto const kEventSchemaVersion = 4; |
19 | 17 |
|
20 |
| -void AsioEventProcessor::AsyncSend(InputEvent in_event) { |
21 |
| - LD_LOG(logger_, LogLevel::kDebug) << "processor: pushing event into inbox"; |
22 |
| - dispatcher_.AsyncSend(std::move(in_event)); |
| 18 | +AsioEventProcessor::AsioEventProcessor(boost::asio::any_io_executor io, |
| 19 | + config::detail::Events const& config, |
| 20 | + config::ServiceHosts const& endpoints, |
| 21 | + std::string authorization, |
| 22 | + Logger& logger) |
| 23 | + : io_(std::move(io)), |
| 24 | + outbox_(config.capacity()), |
| 25 | + summary_state_(std::chrono::system_clock::now()), |
| 26 | + flush_interval_(config.flush_interval()), |
| 27 | + timer_(io_), |
| 28 | + host_(endpoints.events_host()), |
| 29 | + path_(config.path()), |
| 30 | + authorization_(std::move(authorization)), |
| 31 | + uuids_(), |
| 32 | + full_outbox_encountered_(false), |
| 33 | + filter_(config.all_attributes_private(), config.private_attributes()), |
| 34 | + logger_(logger) { |
| 35 | + ScheduleFlush(); |
23 | 36 | }
|
24 | 37 |
|
25 |
| -void AsioEventProcessor::AsyncFlush() { |
26 |
| - LD_LOG(logger_, LogLevel::kDebug) |
27 |
| - << "processor: requesting unscheduled flush"; |
28 |
| - dispatcher_.AsyncFlush(); |
| 38 | +void AsioEventProcessor::AsyncSend(InputEvent input_event) { |
| 39 | + boost::asio::post(io_, [this, e = std::move(input_event)]() mutable { |
| 40 | + HandleSend(std::move(e)); |
| 41 | + }); |
| 42 | +} |
| 43 | + |
| 44 | +void AsioEventProcessor::HandleSend(InputEvent e) { |
| 45 | + summary_state_.update(e); |
| 46 | + |
| 47 | + std::vector<OutputEvent> output_events = Process(std::move(e)); |
| 48 | + |
| 49 | + bool inserted = outbox_.push_discard_overflow(std::move(output_events)); |
| 50 | + if (!inserted && !full_outbox_encountered_) { |
| 51 | + LD_LOG(logger_, LogLevel::kWarn) |
| 52 | + << "event-processor: exceeded event queue capacity; increase " |
| 53 | + "capacity to avoid dropping events"; |
| 54 | + } |
| 55 | + full_outbox_encountered_ = !inserted; |
| 56 | +} |
| 57 | + |
| 58 | +void AsioEventProcessor::Flush(FlushTrigger flush_type) { |
| 59 | + if (auto request = MakeRequest()) { |
| 60 | + conns_.async_write(*request); |
| 61 | + } else { |
| 62 | + LD_LOG(logger_, LogLevel::kDebug) |
| 63 | + << "event-processor: nothing to flush"; |
| 64 | + } |
| 65 | + if (flush_type == FlushTrigger::Automatic) { |
| 66 | + ScheduleFlush(); |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +void AsioEventProcessor::ScheduleFlush() { |
| 71 | + LD_LOG(logger_, LogLevel::kDebug) << "event-processor: scheduling flush in " |
| 72 | + << flush_interval_.count() << "ms"; |
| 73 | + |
| 74 | + timer_.expires_from_now(flush_interval_); |
| 75 | + timer_.async_wait([this](boost::system::error_code ec) { |
| 76 | + if (ec) { |
| 77 | + LD_LOG(logger_, LogLevel::kDebug) |
| 78 | + << "event-processor: flush cancelled"; |
| 79 | + return; |
| 80 | + } |
| 81 | + LD_LOG(logger_, LogLevel::kDebug) << "event-processor: flush"; |
| 82 | + Flush(FlushTrigger::Automatic); |
| 83 | + }); |
29 | 84 | }
|
30 | 85 |
|
31 | 86 | void AsioEventProcessor::AsyncClose() {
|
32 |
| - LD_LOG(logger_, LogLevel::kDebug) << "processor: request shutdown"; |
33 |
| - dispatcher_.AsyncFlush(); |
34 |
| - dispatcher_.AsyncClose(); |
| 87 | + timer_.cancel(); |
| 88 | +} |
| 89 | + |
| 90 | +void AsioEventProcessor::AsyncFlush() { |
| 91 | + boost::asio::post(io_, [this] { |
| 92 | + boost::system::error_code ec; |
| 93 | + Flush(FlushTrigger::Manual); |
| 94 | + }); |
35 | 95 | }
|
36 | 96 |
|
| 97 | +std::optional<AsioEventProcessor::RequestType> |
| 98 | +AsioEventProcessor::MakeRequest() { |
| 99 | + if (outbox_.empty()) { |
| 100 | + return std::nullopt; |
| 101 | + } |
| 102 | + |
| 103 | + LD_LOG(logger_, LogLevel::kDebug) << "generating http request"; |
| 104 | + RequestType req; |
| 105 | + |
| 106 | + req.set(http::field::host, host_); |
| 107 | + req.method(http::verb::post); |
| 108 | + req.set(http::field::content_type, "application/json"); |
| 109 | + req.set(http::field::authorization, authorization_); |
| 110 | + req.set(kEventSchemaHeader, std::to_string(kEventSchemaVersion)); |
| 111 | + req.set(kPayloadIdHeader, boost::lexical_cast<std::string>(uuids_())); |
| 112 | + req.target(host_ + path_); |
| 113 | + |
| 114 | + req.body() = |
| 115 | + boost::json::serialize(boost::json::value_from(outbox_.consume())); |
| 116 | + req.prepare_payload(); |
| 117 | + return req; |
| 118 | +} |
| 119 | + |
| 120 | +static std::map<std::string, std::string> CopyContextKeys( |
| 121 | + std::map<std::string_view, std::string_view> const& refs) { |
| 122 | + std::map<std::string, std::string> copied_keys; |
| 123 | + for (auto kv : refs) { |
| 124 | + copied_keys.insert(kv); |
| 125 | + } |
| 126 | + return copied_keys; |
| 127 | +} |
| 128 | + |
| 129 | +// These helpers are for the std::visit within Dispatcher::process. |
| 130 | +template <class... Ts> |
| 131 | +struct overloaded : Ts... { |
| 132 | + using Ts::operator()...; |
| 133 | +}; |
| 134 | +// explicit deduction guide (not needed as of C++20) |
| 135 | +template <class... Ts> |
| 136 | +overloaded(Ts...) -> overloaded<Ts...>; |
| 137 | + |
| 138 | +std::vector<OutputEvent> AsioEventProcessor::Process(InputEvent event) { |
| 139 | + std::vector<OutputEvent> out; |
| 140 | + std::visit( |
| 141 | + overloaded{ |
| 142 | + [&](client::FeatureEventParams&& e) { |
| 143 | + if (!e.eval_result.track_events()) { |
| 144 | + return; |
| 145 | + } |
| 146 | + std::optional<Reason> reason; |
| 147 | + |
| 148 | + // TODO(cwaldren): should also add the reason if the variation |
| 149 | + // method was VariationDetail(). |
| 150 | + if (e.eval_result.track_reason()) { |
| 151 | + reason = e.eval_result.detail().reason(); |
| 152 | + } |
| 153 | + |
| 154 | + client::FeatureEventBase b = { |
| 155 | + e.creation_date, std::move(e.key), e.eval_result.version(), |
| 156 | + e.eval_result.detail().variation_index(), |
| 157 | + e.eval_result.detail().value(), reason, |
| 158 | + // TODO(cwaldren): change to actual default; figure out |
| 159 | + // where this should be plumbed through. |
| 160 | + Value::null()}; |
| 161 | + |
| 162 | + auto debug_until_date = e.eval_result.debug_events_until_date(); |
| 163 | + bool emit_debug_event = |
| 164 | + debug_until_date && |
| 165 | + debug_until_date.value() > std::chrono::system_clock::now(); |
| 166 | + |
| 167 | + if (emit_debug_event) { |
| 168 | + out.emplace_back( |
| 169 | + client::DebugEvent{b, filter_.filter(e.context)}); |
| 170 | + } |
| 171 | + // TODO(cwaldren): see about not copying the keys / having the |
| 172 | + // getter return a value. |
| 173 | + out.emplace_back(client::FeatureEvent{ |
| 174 | + std::move(b), CopyContextKeys(e.context.kinds_to_keys())}); |
| 175 | + }, |
| 176 | + [&](client::IdentifyEventParams&& e) { |
| 177 | + // Contexts should already have been checked for |
| 178 | + // validity by this point. |
| 179 | + assert(e.context.valid()); |
| 180 | + out.emplace_back(client::IdentifyEvent{ |
| 181 | + e.creation_date, filter_.filter(e.context)}); |
| 182 | + }, |
| 183 | + [&](TrackEventParams&& e) { out.emplace_back(std::move(e)); }}, |
| 184 | + std::move(event)); |
| 185 | + |
| 186 | + return out; |
| 187 | +} |
37 | 188 | } // namespace launchdarkly::events::detail
|
0 commit comments