116 static_assert(std::is_same_v<Stream, PlainStream> || kTls,
117 "Connection supports PlainStream and TlsStream<PlainStream>");
123 cfg_(std::move(cfg)),
125 backoff_(cfg_.backoff),
128 if constexpr (kTls) {
129 if (tls ==
nullptr) {
130 owned_tls_ = std::make_unique<TlsContext>();
133 tls = owned_tls_.get();
137 host_ = std::string(url_.
host);
140 target_.resize(url_.
path.size() + url_.
query.size() + 1);
144 Connection(
const Connection&) =
delete;
150 user_closed_ =
false;
160 if (active_ && active_->ws.is_open())
161 active_->ws.send_close(WsCloseCode::Normal,
"client closing");
167 bool send_text(std::string_view text)
noexcept {
168 Session* s = send_target();
169 if (s ==
nullptr || !s->ws.is_open())
return false;
170 const bool ok = s->ws.send_text(text);
171 if (!ok) ++stats_.
drops;
176 void cork() noexcept {
177 corked_ = send_target();
178 if (corked_ !=
nullptr) corked_->ws.cork();
181 Session* s = corked_;
183 if (s ==
nullptr)
return true;
184 if (s != active_.get() && s != pending_.get())
return false;
185 return s->ws.uncork();
187 bool send_binary(std::span<const std::byte> data)
noexcept {
188 Session* s = send_target();
189 if (s ==
nullptr || !s->ws.is_open())
return false;
190 const bool ok = s->ws.send_binary(data);
191 if (!ok) ++stats_.
drops;
195 Session* s = send_target();
196 return s !=
nullptr && s->ws.is_open() && s->ws.send_ping();
201 if (Session* s = newest(); s !=
nullptr && s->phase == Phase::Authenticating)
202 enter_subscribing(*s);
205 if (Session* s = newest(); s !=
nullptr && s->phase == Phase::Subscribing) enter_live(*s);
210 std::int64_t
last_rx_ns() const noexcept {
return last_rx_ns_; }
211 const Url&
url() const noexcept {
return url_; }
217 for (
const Session* sess : {active_.get(), pending_.get()}) {
218 if (sess ==
nullptr)
continue;
219 const WsStats& w = sess->ws.stats();
230 enum class Phase : std::uint8_t { Connecting, Authenticating, Subscribing, Live, Dead };
237 struct SessionEvents {
240 void on_ws_progress(
WsProgress p) { owner->on_session_progress(*self, p); }
241 void on_ws_open() { owner->on_session_open(*self); }
242 void on_ws_text(std::string_view t, std::int64_t ts) { owner->on_session_text(*self, t, ts); }
243 void on_ws_binary(std::span<const std::byte> b, std::int64_t ts) {
244 owner->on_session_binary(*self, b, ts);
246 void on_ws_ping(std::span<const std::byte>) { owner->touch_rx(); }
247 void on_ws_pong(std::span<const std::byte>) { owner->touch_rx(); }
248 void on_ws_close(std::uint16_t, std::string_view) { owner->on_session_end(*self); }
249 void on_ws_error(NetError, std::string_view) { owner->on_session_end(*self); }
255 Session(Connection& c, Stream&& stream)
256 : events{&c, this}, ws(c.reactor_, std::move(stream), events, c.cfg_.ws) {}
258 SessionEvents events;
259 WsClient<Stream, SessionEvents> ws;
260 Phase phase = Phase::Connecting;
261 std::int64_t live_at_ns = 0;
266 if (state_ == s)
return;
268 handler_.on_state(s);
270 Session* newest() noexcept {
return pending_ ? pending_.get() : active_.get(); }
271 Session* send_target() noexcept {
272 if (pending_ && pending_->phase != Phase::Connecting)
return pending_.get();
273 return active_.get();
278 void start_resolve() {
280 const std::uint64_t gen = ++resolve_gen_;
281 std::weak_ptr<int> alive = alive_;
282 resolver_.resolve(host_, url_.
port, [
this, gen, alive](ResolveResult r) {
283 if (alive.expired() || gen != resolve_gen_ || user_closed_) return;
288 addrs_ = std::move(r.addrs);
289 keep_reachable(cfg_.
source, addrs_);
290 if (addrs_.empty()) {
299 Stream make_stream(TcpSocket&& sock) {
300 if constexpr (kTls) {
301 return Stream(*tls_, PlainStream(std::move(sock)), host_);
303 return Stream(std::move(sock));
307 void open_session(
bool rollover) {
308 const SockAddr& addr = addrs_[addr_index_ % addrs_.size()];
311 const std::optional<SockAddr> local = source_bind_address(cfg_.source, addr.family());
312 if (sock.connect(addr, local ? &*local : nullptr) == ConnectStatus::
Error) {
314 schedule_rollover_retry();
320 sock.set_nodelay(
true);
321 sock.set_keepalive(
true);
322 sock.set_rcvbuf(4 * 1024 * 1024);
323 auto session = std::make_unique<Session>(*
this, make_stream(std::move(sock)));
324 Session* raw = session.get();
325 (rollover ? pending_ : active_) = std::move(session);
328 const std::string headers = cfg_.make_headers ? cfg_.make_headers() : cfg_.extra_headers;
329 if (!raw->ws.start(host_, url_.port, kTls, target_, headers)) {
330 on_session_end(*raw);
334 void arm_connect_timer() {
335 cancel(connect_timer_);
336 connect_timer_ = reactor_.add_timer_after(ms_to_ns(cfg_.connect_timeout_ms), [
this] {
337 connect_timer_ = kInvalidTimer;
338 if (Session* s = newest(); s != nullptr && s->phase != Phase::Live) on_session_end(*s);
342 void on_session_progress(Session& s,
WsProgress p) {
343 if (&s != active_.get())
return;
351 void on_session_open(Session& s) {
352 s.phase = Phase::Authenticating;
355 if (!cfg_.manual_auth) enter_subscribing(s);
358 void enter_subscribing(Session& s) {
359 s.phase = Phase::Subscribing;
361 handler_.on_connected_send_subscriptions();
362 if (s.phase != Phase::Subscribing)
return;
363 if (!cfg_.manual_subscribe) enter_live(s);
366 void enter_live(Session& s) {
367 s.phase = Phase::Live;
369 cancel(connect_timer_);
372 if (&s == pending_.get()) promote_pending();
375 arm_heartbeat_timer();
376 arm_lifetime_timer();
380 void on_session_text(Session&, std::string_view t, std::int64_t ts) {
382 handler_.on_text(t, ts);
384 void on_session_binary(Session&, std::span<const std::byte> b, std::int64_t ts) {
386 handler_.on_binary(b, ts);
388 void on_rx(std::int64_t ts) {
396 void on_session_end(Session& s) {
397 if (s.phase == Phase::Dead)
return;
398 s.phase = Phase::Dead;
400 if (&s == pending_.get()) {
403 schedule_rollover_retry();
410 active_ = std::move(pending_);
416 cancel_session_timers();
417 if (!user_closed_) schedule_backoff();
420 void retire(Session& s)
noexcept {
421 const WsStats& w = s.ws.stats();
422 stats_.frames_rx += w.frames_rx;
423 stats_.frames_tx += w.frames_tx;
424 stats_.bytes_rx += w.bytes_rx;
425 stats_.bytes_tx += w.bytes_tx;
426 stats_.drops += w.drops;
430 void schedule_backoff() {
433 cancel(backoff_timer_);
434 backoff_timer_ = reactor_.add_timer_after(ms_to_ns(backoff_.next_ms()), [
this] {
435 backoff_timer_ = kInvalidTimer;
436 if (!user_closed_) start_resolve();
441 void arm_health_timer() {
442 cancel(health_timer_);
443 const std::uint32_t period =
444 std::max<std::uint32_t>(10, std::min(cfg_.stale_ms, cfg_.dead_ms) / 4);
445 health_timer_ = reactor_.add_timer_after(ms_to_ns(period), [
this] {
447 if (!is_live())
return;
448 const std::int64_t idle_ms = (
Reactor::now_ns() - last_rx_ns_) / 1'000'000;
449 if (idle_ms >= cfg_.dead_ms) {
450 ++stats_.dead_events;
451 on_session_end(*active_);
455 ++stats_.stale_events;
462 void arm_heartbeat_timer() {
463 cancel(heartbeat_timer_);
464 if (cfg_.heartbeat_interval_ms == 0)
return;
465 heartbeat_timer_ = reactor_.add_timer_after(ms_to_ns(cfg_.heartbeat_interval_ms), [
this] {
466 heartbeat_timer_ = kInvalidTimer;
467 if (!is_live()) return;
469 arm_heartbeat_timer();
473 void arm_lifetime_timer() {
474 cancel(lifetime_timer_);
475 if (cfg_.max_lifetime_ms == 0)
return;
476 lifetime_timer_ = reactor_.add_timer_after(ms_to_ns(cfg_.max_lifetime_ms), [
this] {
477 lifetime_timer_ = kInvalidTimer;
482 void start_rollover() {
483 if (!is_live() || pending_ || addrs_.empty())
return;
488 void schedule_rollover_retry() {
489 cancel(lifetime_timer_);
490 lifetime_timer_ = reactor_.add_timer_after(ms_to_ns(backoff_.next_ms()), [
this] {
491 lifetime_timer_ = kInvalidTimer;
496 void promote_pending() {
501 active_ = std::move(pending_);
504 void bury(std::unique_ptr<Session>& s) {
506 graveyard_.push_back(std::move(s));
507 if (reap_pending_)
return;
508 reap_pending_ =
true;
509 std::weak_ptr<int> alive = alive_;
510 reactor_.post([
this, alive] {
511 if (alive.expired())
return;
512 reap_pending_ =
false;
517 void cancel(
TimerId&
id)
noexcept {
519 reactor_.cancel_timer(
id);
523 void cancel_session_timers() noexcept {
524 cancel(connect_timer_);
525 cancel(health_timer_);
526 cancel(heartbeat_timer_);
527 cancel(lifetime_timer_);
532 void teardown() noexcept {
533 cancel_session_timers();
534 cancel(backoff_timer_);
545 static constexpr std::int64_t ms_to_ns(std::uint64_t ms)
noexcept {
546 return static_cast<std::int64_t
>(ms) * 1'000'000;
550 ConnectionConfig cfg_;
555 std::unique_ptr<TlsContext> owned_tls_;
556 TlsContext* tls_ =
nullptr;
557 ExponentialBackoff backoff_;
558 AsyncResolver resolver_;
559 std::shared_ptr<int> alive_ = std::make_shared<int>(0);
560 std::uint64_t resolve_gen_ = 0;
561 std::vector<SockAddr> addrs_;
562 std::size_t addr_index_ = 0;
563 std::unique_ptr<Session> active_;
564 std::unique_ptr<Session> pending_;
565 Session* corked_ =
nullptr;
566 std::vector<std::unique_ptr<Session>> graveyard_;
567 bool reap_pending_ =
false;
569 bool user_closed_ =
false;
570 std::int64_t last_rx_ns_ = 0;
571 ConnectionStats stats_;