LCOV - code coverage report
Current view: top level - http/server - connection_tracker.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.3 % 183 178
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 31 31

            Line data    Source code
       1              : #include "connection_tracker.hpp"
       2              : #include "../../util/logger.hpp"
       3              : #include <boost/asio/post.hpp>
       4              : #include <utility>
       5              : 
       6              : namespace thinger::http {
       7              : 
       8              : // tracked_connection
       9              : 
      10        28098 : tracked_connection::~tracked_connection() {
      11        28098 :     untrack();
      12        28098 : }
      13              : 
      14        28092 : void tracked_connection::track(std::shared_ptr<connection_tracker> tracker, std::weak_ptr<void> self,
      15              :                                boost::asio::io_context& io_context, connection_type type) {
      16        28092 :     tracker_ = tracker;
      17        28092 :     self_ = std::move(self);
      18        28092 :     io_context_ = &io_context;
      19        28092 :     type_ = type;
      20        28092 :     tracker->add(*this);
      21        28092 : }
      22              : 
      23        42403 : void tracked_connection::untrack() {
      24              :     // (a tracker gone no longer lists it)
      25        42403 :     auto tracker = tracker_.lock();
      26        42403 :     tracker_.reset();
      27        42403 :     if (tracker) tracker->remove(*this);
      28        42403 : }
      29              : 
      30         7191 : void tracked_connection::set_type(connection_type type) {
      31         7191 :     if (auto tracker = tracker_.lock()) tracker->retype(*this, type);
      32         7191 :     else type_ = type;
      33         7191 : }
      34              : 
      35              : // connection_tracker
      36              : 
      37        28092 : size_t connection_tracker::shard_of(const boost::asio::io_context& io_context) {
      38              :     // The connections of an io_context share a shard, so it is not contended by the others
      39        28092 :     auto address = reinterpret_cast<uintptr_t>(&io_context);
      40        28092 :     return ((address >> 6) * 0x9E3779B97F4A7C15ull) >> 60;
      41              : }
      42              : 
      43        28092 : void connection_tracker::add(tracked_connection& connection) {
      44        28092 :     connection.shard_ = static_cast<uint8_t>(shard_of(*connection.io_context_));
      45        28092 :     auto& shard = shards_[connection.shard_];
      46        28092 :     std::scoped_lock lock(shard.mutex);
      47        28092 :     connection.next_ = shard.head;
      48        28092 :     if (shard.head) shard.head->prev_ = &connection;
      49        28092 :     shard.head = &connection;
      50        28092 :     connection.linked_ = true;
      51        28092 :     ++shard.active[static_cast<size_t>(connection.type_)];
      52        28092 :     if (connection.type_ == connection_type::http) ++shard.total_connections;
      53        28092 : }
      54              : 
      55        28091 : void connection_tracker::remove(tracked_connection& connection) {
      56              :     {
      57        28091 :         auto& shard = shards_[connection.shard_];
      58        28091 :         std::scoped_lock lock(shard.mutex);
      59        28091 :         if (!connection.linked_) return;
      60        28091 :         if (connection.prev_) connection.prev_->next_ = connection.next_;
      61        21948 :         else shard.head = connection.next_;
      62        28091 :         if (connection.next_) connection.next_->prev_ = connection.prev_;
      63        28091 :         connection.prev_ = connection.next_ = nullptr;
      64        28091 :         connection.linked_ = false;
      65        28091 :         --shard.active[static_cast<size_t>(connection.type_)];
      66        28091 :     }
      67              :     // Only while a shutdown waits for the last one. Set before notify_when_empty() counted the
      68              :     // connections: if that count saw this one, this sees it set (the lock of the shard orders
      69              :     // them), and the last one to leave tells it
      70        28091 :     if (watching_.load(std::memory_order_acquire)) [[unlikely]] check_empty();
      71              : }
      72              : 
      73         7191 : void connection_tracker::retype(tracked_connection& connection, connection_type type) {
      74         7191 :     auto& shard = shards_[connection.shard_];
      75         7191 :     std::scoped_lock lock(shard.mutex);
      76         7191 :     if (connection.linked_) {
      77         7191 :         --shard.active[static_cast<size_t>(connection.type_)];
      78         7191 :         ++shard.active[static_cast<size_t>(type)];
      79              :     }
      80         7191 :     connection.type_ = type;
      81         7191 : }
      82              : 
      83         7906 : server_stats connection_tracker::stats() const {
      84         7906 :     server_stats stats;
      85       134402 :     for (const auto& shard : shards_) {
      86       126496 :         std::scoped_lock lock(shard.mutex);
      87       126496 :         stats.http_connections += shard.active[static_cast<size_t>(connection_type::http)];
      88       126496 :         stats.websocket_connections += shard.active[static_cast<size_t>(connection_type::websocket)];
      89       126496 :         stats.sse_connections += shard.active[static_cast<size_t>(connection_type::sse)];
      90       126496 :         stats.tunnel_connections += shard.active[static_cast<size_t>(connection_type::tunnel)];
      91       126496 :         stats.taken_over_connections += shard.active[static_cast<size_t>(connection_type::taken_over)];
      92       126496 :         stats.total_connections += shard.total_connections;
      93       126496 :     }
      94         7906 :     return stats;
      95              : }
      96              : 
      97         9871 : size_t connection_tracker::active() const {
      98         9871 :     size_t active = 0;
      99       167807 :     for (const auto& shard : shards_) {
     100       157936 :         std::scoped_lock lock(shard.mutex);
     101       947616 :         for (auto count : shard.active) active += count;
     102       157936 :     }
     103         9871 :     return active;
     104              : }
     105              : 
     106         4464 : void connection_tracker::notify_when_empty(std::function<void()> empty) {
     107              :     {
     108         4464 :         std::scoped_lock lock(empty_mutex_);
     109         4464 :         on_empty_ = std::move(empty);
     110         4464 :         watching_.store(static_cast<bool>(on_empty_), std::memory_order_release);
     111         4464 :     }
     112              :     // None left already (or the last left before it was set)
     113         4464 :     check_empty();
     114         4464 : }
     115              : 
     116         4710 : void connection_tracker::check_empty() {
     117         4710 :     std::function<void()> empty;
     118              :     {
     119              :         // (held while counting: told once, by whoever counts none first)
     120         4710 :         std::scoped_lock lock(empty_mutex_);
     121         4710 :         if (!on_empty_ || active() > 0) return;
     122          228 :         empty = std::exchange(on_empty_, nullptr);
     123          228 :         watching_.store(false, std::memory_order_release);
     124         4710 :     }
     125          228 :     empty();
     126         4710 : }
     127              : 
     128        25665 : std::vector<std::pair<std::shared_ptr<void>, tracked_connection*>> connection_tracker::snapshot() const {
     129        25665 :     std::vector<std::pair<std::shared_ptr<void>, tracked_connection*>> connections;
     130       436305 :     for (const auto& shard : shards_) {
     131       410640 :         std::scoped_lock lock(shard.mutex);
     132       415633 :         for (auto* connection = shard.head; connection; connection = connection->next_) {
     133              :             // (one being destroyed waits for the lock to leave: it cannot be locked anymore)
     134         4993 :             if (auto owner = connection->self_.lock()) connections.emplace_back(std::move(owner), connection);
     135              :         }
     136       410640 :     }
     137        25665 :     return connections;
     138            0 : }
     139              : 
     140         3252 : void connection_tracker::drain_all() {
     141         4161 :     for (auto& [owner, connection] : snapshot()) {
     142          909 :         boost::asio::post(*connection->io_context_, [owner = std::move(owner), connection = connection] {
     143          900 :             connection->shutdown_drain();
     144          900 :         });
     145         3252 :     }
     146         3252 : }
     147              : 
     148        22413 : void connection_tracker::cancel_and_close_all(std::function<void()> done) {
     149        22413 :     auto connections = snapshot();
     150        22413 :     if (connections.empty()) {
     151        20372 :         if (done) done();
     152        20372 :         return;
     153              :     }
     154              :     struct pending_closes {
     155              :         std::atomic<size_t> left;
     156              :         std::function<void()> done;
     157              :     };
     158         2041 :     auto pending = std::make_shared<pending_closes>(connections.size(), std::move(done));
     159         5823 :     for (auto& [owner, connection] : connections) {
     160         3782 :         auto* io_context = connection->io_context_;
     161         3782 :         boost::asio::post(*io_context, [io_context, owner = std::move(owner), connection = connection, pending]() mutable {
     162              :             // In one call: the handlers interrupted resume once closed. Posted apart, the
     163              :             // first could run, and a handler resume and write, before the second was posted
     164          602 :             connection->shutdown_cancel();
     165          602 :             connection->shutdown_close();
     166              :             // Closed once what was posted before the close ran too: the handlers interrupted
     167              :             // resume (on a closed connection) before the server may stop
     168          602 :             boost::asio::post(*io_context, [owner = std::move(owner), pending] {
     169          936 :                 if (pending->left.fetch_sub(1, std::memory_order_acq_rel) == 1 && pending->done) pending->done();
     170          468 :             });
     171          602 :         });
     172              :     }
     173        22413 : }
     174              : 
     175              : // taken_over_tracking
     176              : 
     177              : namespace detail {
     178              : 
     179         4306 : std::shared_ptr<taken_over_tracking> taken_over_tracking::track(std::shared_ptr<connection_tracker> tracker,
     180              :                                                                 std::shared_ptr<asio::socket> socket) {
     181         4306 :     auto& io_context = socket->get_io_context();
     182         4306 :     std::shared_ptr<taken_over_tracking> tracking(new taken_over_tracking(std::move(socket)));
     183         4306 :     tracking->tracked_connection::track(std::move(tracker), tracking, io_context, connection_type::taken_over);
     184         4306 :     return tracking;
     185            0 : }
     186              : 
     187         4306 : taken_over_tracking::~taken_over_tracking() = default;
     188              : 
     189         4306 : upgraded_connection taken_over_tracking::hand_out(std::string buffered) {
     190              :     // The socket handed out shares the ownership of this tracking: the connection counts while
     191              :     // the new owner keeps it, and the socket never knows of the server
     192         4306 :     auto self = shared_from_this();
     193         8612 :     return upgraded_connection{std::shared_ptr<asio::socket>(self, socket_.get()), std::move(buffered),
     194         8612 :                                server_scope(self)};
     195         4306 : }
     196              : 
     197         4306 : void taken_over_tracking::handed_over() {
     198         4306 :     auto tracker = this->tracker();
     199         4306 :     if (!tracker) return;
     200         4214 :     switch (tracker->current_phase()) {
     201         4120 :         case connection_tracker::phase::serving: break;
     202            9 :         case connection_tracker::phase::draining: shutdown_drain(); break;
     203           85 :         case connection_tracker::phase::closing: shutdown_close(); break;
     204              :     }
     205         4306 : }
     206              : 
     207         3702 : void taken_over_tracking::on_shutdown(sync_callback<void()> callback) {
     208              :     // Closed, or told already: never told (again)
     209         3702 :     if (!tracked() || notified_) return;
     210         3702 :     if (!draining_) {
     211         3684 :         on_shutdown_ = std::move(callback);
     212         3684 :         return;
     213              :     }
     214              :     // Set once the drain started (e.g. by a coroutine handler, after the takeover): told now,
     215              :     // posted, unless closed meanwhile. Not kept here, as it may own the socket
     216           18 :     notified_ = true;
     217           18 :     boost::asio::post(socket_->get_io_context(), [weak = weak_from_this(), callback = std::move(callback)] {
     218           18 :         auto self = weak.lock();
     219           18 :         if (self && self->tracked()) notify(callback);
     220           18 :     });
     221              : }
     222              : 
     223         3666 : void taken_over_tracking::closed() {
     224         3666 :     on_shutdown_ = nullptr;
     225         3666 :     untrack();
     226         3666 : }
     227              : 
     228          165 : void taken_over_tracking::notify(const sync_callback<void()>& callback) {
     229              :     try {
     230          165 :         callback();
     231            0 :     } catch (const std::exception& e) {
     232            0 :         LOG_ERROR("error notifying a connection of the server shutdown: {}", e.what());
     233            0 :     }
     234          165 : }
     235              : 
     236          228 : void taken_over_tracking::shutdown_drain() {
     237          300 :     if (draining_ || !tracked()) return;
     238          219 :     draining_ = true;
     239              :     // Called once: released before, as it may own the socket (which owns this). Without one
     240              :     // yet, the owner is told once it sets it (on_shutdown())
     241          219 :     auto callback = std::move(on_shutdown_);
     242          219 :     on_shutdown_ = nullptr;
     243          219 :     if (!callback) return;
     244          147 :     notified_ = true;
     245          147 :     notify(callback);
     246          219 : }
     247              : 
     248          194 : void taken_over_tracking::shutdown_close() {
     249          194 :     if (!tracked()) return;
     250          185 :     on_shutdown_ = nullptr;
     251          185 :     socket_->close();
     252          185 :     untrack();
     253              : }
     254              : 
     255              : }
     256              : 
     257              : // server_scope
     258              : 
     259         7230 : void server_scope::set_type(connection_type type) const {
     260         7230 :     if (auto tracking = tracking_.lock()) tracking->set_type(type);
     261         7230 : }
     262              : 
     263         3744 : void server_scope::on_shutdown(sync_callback<void()> callback) const {
     264         3744 :     if (auto tracking = tracking_.lock()) tracking->on_shutdown(std::move(callback));
     265         3744 : }
     266              : 
     267         3732 : void server_scope::closed() const {
     268         3732 :     if (auto tracking = tracking_.lock()) tracking->closed();
     269         3732 : }
     270              : 
     271              : }
        

Generated by: LCOV version 2.0-1