LCOV - code coverage report
Current view: top level - wirestead/transport/tcp_server - tcp_server_session.cc (source / functions) Coverage Total Hit
Test: Wirestead Coverage Report Lines: 81.0 % 379 307
Test Date: 2026-08-30 10:35:09 Functions: 97.8 % 46 45
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 61.9 % 244 151

             Branch data     Line data    Source code
       1                 :             : /*
       2                 :             :  * Copyright 2025 Jinwoo Sung
       3                 :             :  *
       4                 :             :  * Licensed under the Apache License, Version 2.0 (the "License");
       5                 :             :  * you may not use this file except in compliance with the License.
       6                 :             :  * You may obtain a copy of the License at
       7                 :             :  *
       8                 :             :  *     http://www.apache.org/licenses/LICENSE-2.0
       9                 :             :  *
      10                 :             :  * Unless required by applicable law or agreed to in writing, software
      11                 :             :  * distributed under the License is distributed on an "AS IS" BASIS,
      12                 :             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      13                 :             :  * See the License for the specific language governing permissions and
      14                 :             :  * limitations under the License.
      15                 :             :  */
      16                 :             : 
      17                 :             : #include "wirestead/transport/tcp_server/tcp_server_session.hpp"
      18                 :             : 
      19                 :             : #include <cstring>
      20                 :             : #include <iostream>
      21                 :             : 
      22                 :             : #include "wirestead/memory/memory_pool.hpp"
      23                 :             : #include "wirestead/transport/base/bp_utils.hpp"
      24                 :             : #include "wirestead/transport/tcp_server/boost_tcp_socket.hpp"
      25                 :             : 
      26                 :             : namespace wirestead {
      27                 :             : namespace transport {
      28                 :             : 
      29                 :         110 : TcpServerSession::TcpServerSession(net::io_context& ioc, tcp::socket sock, size_t backpressure_threshold,
      30                 :             :                                    int idle_timeout_ms, base::constants::BackpressureStrategy strategy,
      31                 :         110 :                                    bool enable_memory_pool, size_t read_buffer_size)
      32                 :         110 :     : ioc_(ioc),
      33                 :         110 :       strand_(ioc.get_executor()),
      34                 :         110 :       idle_timer_(ioc),
      35                 :         110 :       socket_(std::make_unique<BoostTcpSocket>(std::move(sock))),
      36                 :         110 :       enable_memory_pool_(enable_memory_pool),
      37                 :         110 :       writing_(false),
      38                 :         110 :       queue_bytes_(0),
      39                 :         110 :       bp_strategy_(strategy),
      40                 :         110 :       bp_high_(backpressure_threshold),
      41                 :         110 :       idle_timeout_ms_(idle_timeout_ms),
      42                 :         110 :       alive_(false),
      43                 :         440 :       cleanup_done_(false) {
      44                 :         110 :   bp_limit_ = std::min(std::max(bp_high_ * 4, base::constants::DEFAULT_BACKPRESSURE_THRESHOLD),
      45                 :             :                        base::constants::MAX_BUFFER_SIZE);
      46         [ +  - ]:         110 :   bp_low_ = bp_high_ > 1 ? bp_high_ / 2 : bp_high_;
      47         [ -  + ]:         110 :   if (bp_low_ == 0) bp_low_ = 1;
      48                 :         110 :   rx_.resize(
      49                 :         110 :       std::clamp(read_buffer_size, base::constants::MIN_READ_BUFFER_SIZE, base::constants::MAX_READ_BUFFER_SIZE));
      50                 :         110 : }
      51                 :             : 
      52                 :          54 : TcpServerSession::TcpServerSession(net::io_context& ioc, std::unique_ptr<interface::TcpSocketInterface> socket,
      53                 :             :                                    size_t backpressure_threshold, int idle_timeout_ms,
      54                 :             :                                    base::constants::BackpressureStrategy strategy, bool enable_memory_pool,
      55                 :          54 :                                    size_t read_buffer_size)
      56                 :          54 :     : ioc_(ioc),
      57                 :          54 :       strand_(ioc.get_executor()),
      58                 :          54 :       idle_timer_(ioc),
      59                 :          54 :       socket_(std::move(socket)),
      60                 :          54 :       enable_memory_pool_(enable_memory_pool),
      61                 :          54 :       writing_(false),
      62                 :          54 :       queue_bytes_(0),
      63                 :          54 :       bp_strategy_(strategy),
      64                 :          54 :       bp_high_(backpressure_threshold),
      65                 :          54 :       idle_timeout_ms_(idle_timeout_ms),
      66                 :          54 :       alive_(false),
      67                 :         216 :       cleanup_done_(false) {
      68                 :          54 :   bp_limit_ = std::min(std::max(bp_high_ * 4, base::constants::DEFAULT_BACKPRESSURE_THRESHOLD),
      69                 :             :                        base::constants::MAX_BUFFER_SIZE);
      70         [ +  - ]:          54 :   bp_low_ = bp_high_ > 1 ? bp_high_ / 2 : bp_high_;
      71         [ -  + ]:          54 :   if (bp_low_ == 0) bp_low_ = 1;
      72                 :          54 :   rx_.resize(
      73                 :          54 :       std::clamp(read_buffer_size, base::constants::MIN_READ_BUFFER_SIZE, base::constants::MAX_READ_BUFFER_SIZE));
      74                 :          54 : }
      75                 :             : 
      76                 :         164 : void TcpServerSession::start() {
      77         [ -  + ]:         164 :   if (alive_.exchange(true)) return;
      78                 :         164 :   auto self = shared_from_this();
      79                 :         164 :   net::dispatch(strand_, [self] {
      80                 :         164 :     self->reset_idle_timer();
      81                 :             :     // No-op on a plain socket, the TLS handshake on an encrypted one. Reading
      82                 :             :     // before it completes would hand the session ciphertext, so the first read
      83                 :             :     // waits on it - and a failed handshake closes rather than reads.
      84                 :         164 :     self->socket_->async_handshake(net::bind_executor(self->strand_, [self](const boost::system::error_code& ec) {
      85   [ +  -  -  +  :         164 :       if (self->closing_ || !self->alive_) return;
                   -  + ]
      86         [ +  + ]:         164 :       if (ec) {
      87                 :          30 :         WIRESTEAD_LOG_WARNING("tcp_server_session", "handshake", "Handshake failed: " + ec.message());
      88                 :          30 :         self->do_close();
      89                 :          30 :         return;
      90                 :             :       }
      91                 :         134 :       self->reset_idle_timer();
      92                 :         134 :       self->start_read();
      93                 :             :     }));
      94                 :         164 :   });
      95                 :         164 : }
      96                 :             : 
      97                 :          60 : bool TcpServerSession::async_write_copy(memory::ConstByteSpan data) {
      98   [ +  -  -  +  :          60 :   if (!alive_ || closing_) {
                   -  + ]
      99                 :           0 :     stats_.record_failed_send();
     100                 :           0 :     return false;
     101                 :             :   }  // Don't queue writes if session is not alive
     102                 :             : 
     103                 :          60 :   size_t size = data.size();
     104         [ -  + ]:          60 :   if (size == 0) {
     105                 :           0 :     stats_.record_failed_send();
     106                 :           0 :     return false;
     107                 :             :   }
     108         [ -  + ]:          60 :   if (size > base::constants::MAX_BUFFER_SIZE) {
     109                 :           0 :     WIRESTEAD_LOG_ERROR("tcp_server_session", "write", "Write size exceeds maximum allowed");
     110                 :           0 :     stats_.record_failed_send();
     111                 :           0 :     return false;
     112                 :             :   }
     113                 :             : 
     114                 :             :   // Use memory pool for better performance (only for reasonable sizes)
     115   [ +  +  +  - ]:          60 :   if (size <= base::constants::LARGE_BUFFER_THRESHOLD && enable_memory_pool_) {  // Only use pool for buffers <= 64KB
     116                 :           7 :     memory::PooledBuffer pooled_buffer(size, pool_);
     117   [ +  -  +  - ]:           7 :     if (pooled_buffer.valid()) {
     118                 :             :       // Copy data to pooled buffer safely
     119                 :           7 :       base::safe_memory::safe_memcpy(pooled_buffer.data(), data.data(), size);
     120   [ +  -  -  + ]:           7 :       if (!queue_util::try_reserve_limit_bytes(write_reserve_mtx_, queue_bytes_, pending_bytes_, inflight_bytes_, size,
     121                 :             :                                                bp_limit_)) {
     122                 :           0 :         stats_.record_failed_send();
     123                 :           0 :         return false;
     124                 :             :       }
     125                 :           7 :       stats_.record_accepted(size);
     126                 :           7 :       net::post(strand_, [self = shared_from_this(), buf = std::move(pooled_buffer)]() mutable {
     127                 :           7 :         const auto added = buf.size();
     128   [ +  -  -  +  :           7 :         if (!self->alive_ || self->closing_) {  // Double-check in case session was closed
                   -  + ]
     129                 :           0 :           queue_util::release_reserved_limit_bytes(self->write_reserve_mtx_, self->inflight_bytes_, added);
     130                 :           0 :           self->stats_.record_failed_send();
     131                 :           0 :           return;
     132                 :             :         }
     133                 :           7 :         self->route_enqueued_buffer(BufferVariant{std::move(buf)}, added);
     134                 :             :       });
     135                 :           7 :       return true;
     136                 :             :     }
     137                 :           7 :   }
     138                 :             : 
     139                 :             :   // Fallback to regular allocation for large buffers or pool exhaustion
     140   [ +  -  +  + ]:          53 :   if (!queue_util::try_reserve_limit_bytes(write_reserve_mtx_, queue_bytes_, pending_bytes_, inflight_bytes_, size,
     141                 :             :                                            bp_limit_)) {
     142                 :          50 :     stats_.record_failed_send();
     143                 :          50 :     return false;
     144                 :             :   }
     145                 :           3 :   std::vector<uint8_t> fallback(data.begin(), data.end());
     146                 :           3 :   stats_.record_accepted(size);
     147                 :             : 
     148                 :           3 :   net::post(strand_, [self = shared_from_this(), buf = std::move(fallback), size]() mutable {
     149   [ +  -  -  +  :           3 :     if (!self->alive_ || self->closing_) {  // Double-check in case session was closed
                   -  + ]
     150                 :           0 :       queue_util::release_reserved_limit_bytes(self->write_reserve_mtx_, self->inflight_bytes_, size);
     151                 :           0 :       self->stats_.record_failed_send();
     152                 :           0 :       return;
     153                 :             :     }
     154                 :           3 :     self->route_enqueued_buffer(BufferVariant{std::move(buf)}, size);
     155                 :             :   });
     156                 :           3 :   return true;
     157                 :           3 : }
     158                 :             : 
     159                 :       24023 : bool TcpServerSession::async_write_move(std::vector<uint8_t>&& data) {
     160   [ +  -  -  +  :       24023 :   if (!alive_ || closing_) {
                   -  + ]
     161                 :           0 :     stats_.record_failed_send();
     162                 :           0 :     return false;
     163                 :             :   }
     164                 :       24023 :   const auto added = data.size();
     165         [ -  + ]:       24023 :   if (added == 0) {
     166                 :           0 :     stats_.record_failed_send();
     167                 :           0 :     return false;
     168                 :             :   }
     169         [ -  + ]:       24023 :   if (added > base::constants::MAX_BUFFER_SIZE) {
     170                 :           0 :     WIRESTEAD_LOG_ERROR("tcp_server_session", "write", "Write size exceeds maximum allowed");
     171                 :           0 :     stats_.record_failed_send();
     172                 :           0 :     return false;
     173                 :             :   }
     174         [ +  + ]:       24023 :   if (!queue_util::try_reserve_limit_bytes(write_reserve_mtx_, queue_bytes_, pending_bytes_, inflight_bytes_, added,
     175                 :             :                                            bp_limit_)) {
     176                 :       13517 :     stats_.record_failed_send();
     177                 :       13517 :     return false;
     178                 :             :   }
     179                 :       10506 :   stats_.record_accepted(added);
     180                 :       10506 :   net::post(strand_, [self = shared_from_this(), buf = std::move(data), added]() mutable {
     181   [ +  -  -  +  :       10506 :     if (!self->alive_ || self->closing_) {
                   -  + ]
     182                 :           0 :       queue_util::release_reserved_limit_bytes(self->write_reserve_mtx_, self->inflight_bytes_, added);
     183                 :           0 :       self->stats_.record_failed_send();
     184                 :           0 :       return;
     185                 :             :     }
     186                 :       10506 :     self->route_enqueued_buffer(BufferVariant{std::move(buf)}, added);
     187                 :             :   });
     188                 :       10506 :   return true;
     189                 :             : }
     190                 :             : 
     191                 :           2 : bool TcpServerSession::async_write_shared(std::shared_ptr<const std::vector<uint8_t>> data) {
     192   [ +  -  +  -  :           2 :   if (!alive_ || closing_ || !data || data->empty()) {
          +  -  -  +  -  
                      + ]
     193                 :           0 :     stats_.record_failed_send();
     194                 :           0 :     return false;
     195                 :             :   }
     196                 :           2 :   const auto added = data->size();
     197         [ -  + ]:           2 :   if (added > base::constants::MAX_BUFFER_SIZE) {
     198                 :           0 :     WIRESTEAD_LOG_ERROR("tcp_server_session", "write", "Write size exceeds maximum allowed");
     199                 :           0 :     stats_.record_failed_send();
     200                 :           0 :     return false;
     201                 :             :   }
     202         [ +  + ]:           2 :   if (!queue_util::try_reserve_limit_bytes(write_reserve_mtx_, queue_bytes_, pending_bytes_, inflight_bytes_, added,
     203                 :             :                                            bp_limit_)) {
     204                 :           1 :     stats_.record_failed_send();
     205                 :           1 :     return false;
     206                 :             :   }
     207                 :           1 :   stats_.record_accepted(added);
     208                 :           1 :   net::post(strand_, [self = shared_from_this(), buf = std::move(data), added]() mutable {
     209   [ +  -  -  +  :           1 :     if (!self->alive_ || self->closing_) {
                   -  + ]
     210                 :           0 :       queue_util::release_reserved_limit_bytes(self->write_reserve_mtx_, self->inflight_bytes_, added);
     211                 :           0 :       self->stats_.record_failed_send();
     212                 :           0 :       return;
     213                 :             :     }
     214                 :           1 :     self->route_enqueued_buffer(BufferVariant{std::move(buf)}, added);
     215                 :             :   });
     216                 :           1 :   return true;
     217                 :             : }
     218                 :             : 
     219                 :           6 : bool TcpServerSession::async_try_write_copy(memory::ConstByteSpan data) {
     220   [ +  -  -  +  :           6 :   if (data.empty() || data.size() > base::constants::MAX_BUFFER_SIZE) {
                   -  + ]
     221                 :           0 :     stats_.record_failed_send();
     222                 :           0 :     return false;
     223                 :             :   }
     224                 :          12 :   return async_try_write_move(std::vector<uint8_t>(data.begin(), data.end()));
     225                 :             : }
     226                 :             : 
     227                 :          10 : bool TcpServerSession::async_try_write_move(std::vector<uint8_t>&& data) {
     228   [ +  -  -  +  :          10 :   if (!alive_ || closing_) {
                   -  + ]
     229                 :           0 :     stats_.record_failed_send();
     230                 :           0 :     return false;
     231                 :             :   }
     232                 :          10 :   const auto added = data.size();
     233   [ +  -  -  + ]:          10 :   if (added == 0 || added > base::constants::MAX_BUFFER_SIZE) {
     234                 :           0 :     stats_.record_failed_send();
     235                 :           0 :     return false;
     236                 :             :   }
     237                 :           3 :   const auto reject_for_pressure = [this, added]() {
     238         [ +  + ]:           3 :     if (bp_strategy_ == base::constants::BackpressureStrategy::BestEffort) {
     239                 :           1 :       stats_.record_dropped(1, added);
     240                 :             :     } else {
     241                 :           2 :       stats_.record_failed_send();
     242                 :             :     }
     243                 :          13 :   };
     244   [ +  +  +  -  :          17 :   if (backpressure_active_.load() || queue_bytes_ + added > bp_high_ ||
                   +  + ]
     245         [ -  + ]:           7 :       queue_bytes_ + pending_bytes_ + added > bp_limit_) {
     246                 :           3 :     reject_for_pressure();
     247                 :           3 :     return false;
     248                 :             :   }
     249         [ -  + ]:           7 :   if (!queue_util::try_reserve_write_bytes(queue_bytes_, pending_bytes_, backpressure_active_, added, bp_high_,
     250                 :             :                                            bp_limit_)) {
     251                 :           0 :     reject_for_pressure();
     252                 :           0 :     return false;
     253                 :             :   }
     254                 :           7 :   stats_.record_accepted(added);
     255                 :             : 
     256                 :           7 :   net::post(strand_, [self = shared_from_this(), buf = std::move(data), added]() mutable {
     257   [ +  -  -  +  :           7 :     if (!self->alive_ || self->closing_) {
                   -  + ]
     258                 :           0 :       queue_util::release_reserved_write_bytes(self->queue_bytes_, added);
     259                 :           0 :       self->stats_.record_failed_send();
     260                 :           0 :       return;
     261                 :             :     }
     262                 :             : 
     263                 :           7 :     self->tx_.emplace_back(std::move(buf));
     264                 :           7 :     self->observe_queue();
     265                 :           7 :     self->report_backpressure(self->queue_bytes_);
     266         [ -  + ]:           7 :     if (!self->writing_) self->do_write();
     267                 :             :   });
     268                 :           7 :   return true;
     269                 :             : }
     270                 :             : 
     271                 :        1321 : bool TcpServerSession::async_try_write_shared(std::shared_ptr<const std::vector<uint8_t>> data) {
     272   [ +  -  +  -  :        1321 :   if (!alive_ || closing_ || !data || data->empty()) {
          +  -  -  +  -  
                      + ]
     273                 :           0 :     stats_.record_failed_send();
     274                 :           0 :     return false;
     275                 :             :   }
     276                 :        1321 :   const auto added = data->size();
     277         [ -  + ]:        1321 :   if (added > base::constants::MAX_BUFFER_SIZE) {
     278                 :           0 :     stats_.record_failed_send();
     279                 :           0 :     return false;
     280                 :             :   }
     281                 :         225 :   const auto reject_for_pressure = [this, added]() {
     282         [ -  + ]:         225 :     if (bp_strategy_ == base::constants::BackpressureStrategy::BestEffort) {
     283                 :           0 :       stats_.record_dropped(1, added);
     284                 :             :     } else {
     285                 :         225 :       stats_.record_failed_send();
     286                 :             :     }
     287                 :        1546 :   };
     288   [ +  +  +  +  :        2417 :   if (backpressure_active_.load() || queue_bytes_ + added > bp_high_ ||
                   +  + ]
     289         [ -  + ]:        1096 :       queue_bytes_ + pending_bytes_ + added > bp_limit_) {
     290                 :         225 :     reject_for_pressure();
     291                 :         225 :     return false;
     292                 :             :   }
     293         [ -  + ]:        1096 :   if (!queue_util::try_reserve_write_bytes(queue_bytes_, pending_bytes_, backpressure_active_, added, bp_high_,
     294                 :             :                                            bp_limit_)) {
     295                 :           0 :     reject_for_pressure();
     296                 :           0 :     return false;
     297                 :             :   }
     298                 :        1096 :   stats_.record_accepted(added);
     299                 :             : 
     300                 :        1096 :   net::post(strand_, [self = shared_from_this(), buf = std::move(data), added]() mutable {
     301   [ +  -  -  +  :        1096 :     if (!self->alive_ || self->closing_) {
                   -  + ]
     302                 :           0 :       queue_util::release_reserved_write_bytes(self->queue_bytes_, added);
     303                 :           0 :       self->stats_.record_failed_send();
     304                 :           0 :       return;
     305                 :             :     }
     306                 :             : 
     307                 :        1096 :     self->tx_.emplace_back(std::move(buf));
     308                 :        1096 :     self->observe_queue();
     309                 :        1096 :     self->report_backpressure(self->queue_bytes_);
     310         [ +  + ]:        1096 :     if (!self->writing_) self->do_write();
     311                 :             :   });
     312                 :        1096 :   return true;
     313                 :             : }
     314                 :             : 
     315                 :         145 : void TcpServerSession::on_bytes(OnBytes cb) {
     316                 :         145 :   auto self = shared_from_this();
     317                 :         145 :   net::dispatch(strand_, [self, cb = std::move(cb)]() mutable {
     318   [ +  -  -  +  :         145 :     if (self->closing_.load() || self->cleanup_done_.load()) return;
                   -  + ]
     319                 :         145 :     self->on_bytes_ = std::move(cb);
     320                 :             :   });
     321                 :         145 : }
     322                 :         155 : void TcpServerSession::on_backpressure(OnBackpressure cb) {
     323                 :         155 :   auto self = shared_from_this();
     324                 :         155 :   net::dispatch(strand_, [self, cb = std::move(cb)]() mutable {
     325   [ +  +  -  +  :         155 :     if (self->closing_.load() || self->cleanup_done_.load()) return;
                   +  + ]
     326                 :         150 :     self->on_bp_ = std::move(cb);
     327                 :             :   });
     328                 :         155 : }
     329                 :         147 : void TcpServerSession::on_close(OnClose cb) {
     330                 :         147 :   auto self = shared_from_this();
     331                 :         147 :   net::dispatch(strand_, [self, cb = std::move(cb)]() mutable {
     332   [ +  -  -  +  :         147 :     if (self->closing_.load() || self->cleanup_done_.load()) return;
                   -  + ]
     333                 :         147 :     self->on_close_ = std::move(cb);
     334                 :             :   });
     335                 :         147 : }
     336                 :             : 
     337                 :        1663 : bool TcpServerSession::alive() const { return alive_.load(); }
     338                 :             : 
     339                 :         179 : wrapper::RuntimeStats TcpServerSession::stats() const {
     340                 :         537 :   return stats_.snapshot(queue_bytes_.load(std::memory_order_relaxed), pending_bytes_.load(std::memory_order_relaxed),
     341                 :         358 :                          backpressure_active_.load(std::memory_order_relaxed));
     342                 :             : }
     343                 :             : 
     344                 :           0 : void TcpServerSession::reset_stats() {
     345                 :           0 :   stats_.reset(queue_bytes_.load(std::memory_order_relaxed) + pending_bytes_.load(std::memory_order_relaxed));
     346                 :           0 : }
     347                 :             : 
     348                 :          33 : void TcpServerSession::stop() {
     349         [ +  + ]:          33 :   if (closing_.exchange(true)) return;
     350                 :          24 :   auto self = shared_from_this();
     351                 :          24 :   net::post(strand_, [self] {
     352                 :             :     // Clear callbacks on the strand to block further user callbacks after stop.
     353                 :          11 :     self->on_bytes_ = nullptr;
     354                 :          11 :     self->on_bp_ = nullptr;
     355                 :          11 :     self->on_close_ = nullptr;
     356                 :          11 :     self->idle_timer_.cancel();
     357                 :          11 :     self->do_close();
     358                 :          11 :   });
     359                 :          24 : }
     360                 :             : 
     361                 :           2 : void TcpServerSession::cancel() {
     362                 :           2 :   auto self = shared_from_this();
     363                 :           2 :   net::dispatch(strand_, [self] {
     364                 :           2 :     self->idle_timer_.cancel();
     365                 :           2 :     boost::system::error_code ec;
     366                 :             :     // Cancelling the socket via close() causes ongoing operations to complete with operation_aborted.
     367                 :             :     // Unlike stop(), this does NOT set closing_ flag immediately, allowing the
     368                 :             :     // error handler to run normally and trigger do_close() via the error path.
     369         [ +  - ]:           2 :     if (self->socket_) {
     370                 :           2 :       self->socket_->close(ec);
     371                 :             :     }
     372                 :           2 :   });
     373                 :           2 : }
     374                 :             : 
     375                 :        2852 : void TcpServerSession::start_read() {
     376                 :        2852 :   auto self = shared_from_this();
     377                 :        8556 :   socket_->async_read_some(
     378                 :        8556 :       net::buffer(rx_.data(), rx_.size()), net::bind_executor(strand_, [self](auto ec, std::size_t n) {
     379   [ +  +  -  +  :        2828 :         if (self->closing_ || !self->alive_) return;
                   +  + ]
     380         [ +  + ]:        2821 :         if (ec) {
     381                 :         101 :           self->do_close();
     382                 :         101 :           return;
     383                 :             :         }
     384                 :        2720 :         self->reset_idle_timer();
     385         [ +  - ]:        2720 :         if (n > 0) self->stats_.record_received(n);
     386         [ +  - ]:        2720 :         if (self->on_bytes_) {
     387                 :             :           try {
     388                 :        2720 :             self->on_bytes_(memory::ConstByteSpan(self->rx_.data(), n));
     389                 :           2 :           } catch (const std::exception& e) {
     390                 :           6 :             WIRESTEAD_LOG_ERROR("tcp_server_session", "on_bytes",
     391                 :             :                                 "Exception in on_bytes callback: " + std::string(e.what()));
     392                 :           2 :             self->do_close();
     393                 :           2 :             return;
     394                 :           0 :           } catch (...) {
     395                 :           0 :             WIRESTEAD_LOG_ERROR("tcp_server_session", "on_bytes", "Unknown exception in on_bytes callback");
     396                 :           0 :             self->do_close();
     397                 :           0 :             return;
     398                 :             :           }
     399                 :             :         }
     400                 :        2718 :         self->start_read();
     401                 :             :       }));
     402                 :        2852 : }
     403                 :             : 
     404                 :         269 : void TcpServerSession::do_write() {
     405         [ +  + ]:         269 :   if (tx_.empty()) {
     406                 :          93 :     writing_ = false;
     407                 :          93 :     return;
     408                 :             :   }
     409                 :         176 :   writing_ = true;
     410                 :         176 :   auto self = shared_from_this();
     411                 :             : 
     412                 :             :   // Drain several queued buffers into one scatter-gather write rather than one
     413                 :             :   // send syscall per message. The batch and its views stay alive for the whole
     414                 :             :   // operation because `writing_` keeps do_write() from re-entering.
     415                 :         176 :   queue_util::take_gather_batch(tx_, current_write_batch_, current_write_views_);
     416                 :             : 
     417                 :         167 :   auto on_write = [self](const boost::system::error_code& ec, std::size_t n) {
     418                 :             :     // Release the buffers immediately
     419                 :         167 :     self->current_write_batch_.clear();
     420                 :             : 
     421   [ +  +  -  +  :         167 :     if (self->closing_ || !self->alive_) return;
                   +  + ]
     422         [ +  - ]:         165 :     if (self->queue_bytes_ >= n) {
     423                 :         165 :       self->queue_bytes_ -= n;
     424                 :             :     } else {
     425                 :           0 :       self->queue_bytes_ = 0;
     426                 :             :     }
     427                 :         165 :     self->report_backpressure(self->queue_bytes_);
     428                 :             : 
     429         [ +  + ]:         165 :     if (ec) {
     430                 :           1 :       self->do_close();
     431                 :           1 :       return;
     432                 :             :     }
     433                 :         164 :     self->stats_.record_sent(n);
     434                 :         164 :     self->reset_idle_timer();
     435                 :         164 :     self->do_write();
     436                 :         176 :   };
     437                 :             : 
     438                 :         176 :   socket_->async_write(current_write_views_, net::bind_executor(strand_, on_write));
     439                 :         176 : }
     440                 :             : 
     441                 :         146 : void TcpServerSession::do_close() {
     442         [ -  + ]:         146 :   if (cleanup_done_.exchange(true)) return;  // Ensures cleanup runs only once
     443                 :             : 
     444                 :         146 :   alive_.store(false);
     445                 :         146 :   closing_.store(true);  // Redundant, but ensures consistency
     446                 :             : 
     447                 :             :   // Safely invoke on_close callback
     448                 :         146 :   auto close_cb = std::move(on_close_);
     449                 :             : 
     450                 :         146 :   WIRESTEAD_LOG_INFO("tcp_server_session", "disconnect", "Client disconnected");
     451                 :         146 :   boost::system::error_code ec;
     452                 :         146 :   socket_->shutdown(tcp::socket::shutdown_both, ec);
     453                 :         146 :   socket_->close(ec);
     454                 :             : 
     455                 :             :   // Drain queued/pending writes and unconditionally clear backpressure,
     456                 :             :   // notifying any waiter directly - shares UdpChannel's terminal-drain
     457                 :             :   // helper (#434). Must run before on_bp_ is cleared below: otherwise a
     458                 :             :   // Reliable-mode caller blocked in send_to_blocking() for this client
     459                 :             :   // would never be woken up when the client disconnects via a read error
     460                 :             :   // or idle timeout (jwsung91/wirestead#452).
     461                 :             :   {
     462                 :         146 :     auto f = bp_fields();
     463                 :         146 :     queue_util::drain_and_clear_backpressure(f, on_bp_, [&]() {
     464                 :         146 :       tx_.clear();
     465                 :         146 :       queue_bytes_ = 0;
     466                 :         146 :       pending_.clear();
     467                 :         146 :       pending_bytes_ = 0;
     468                 :         146 :     });
     469                 :             :   }
     470                 :             : 
     471                 :             :   // Clear all callbacks to prevent any further invocations
     472                 :         146 :   on_bytes_ = nullptr;
     473                 :         146 :   on_bp_ = nullptr;
     474                 :         146 :   on_close_ = nullptr;
     475                 :         146 :   idle_timer_.cancel();
     476                 :             : 
     477         [ +  + ]:         146 :   if (close_cb) {
     478                 :             :     try {
     479                 :         133 :       close_cb();
     480                 :           0 :     } catch (const std::exception& e) {
     481                 :           0 :       WIRESTEAD_LOG_ERROR("tcp_server_session", "on_close", "Exception in on_close callback: " + std::string(e.what()));
     482                 :           0 :     } catch (...) {
     483                 :           0 :       WIRESTEAD_LOG_ERROR("tcp_server_session", "on_close", "Unknown exception in on_close callback");
     484                 :           0 :     }
     485                 :             :   }
     486                 :         146 : }
     487                 :             : 
     488                 :       11969 : queue_util::BackpressureFields TcpServerSession::bp_fields() {
     489                 :       11969 :   return queue_util::BackpressureFields{queue_bytes_, pending_bytes_, backpressure_active_, bp_high_,
     490                 :       11969 :                                         bp_low_,      bp_limit_,      bp_strategy_};
     491                 :             : }
     492                 :             : 
     493                 :       10517 : void TcpServerSession::route_enqueued_buffer(BufferVariant&& buf, size_t added) {
     494                 :       10517 :   auto f = bp_fields();
     495                 :       10517 :   queue_util::DropAccounting dropped;
     496                 :       10517 :   auto decision = queue_util::decide_enqueue(f, added, tx_, dropped);
     497   [ +  +  +  - ]:       10517 :   if (dropped.any()) stats_.record_dropped(dropped.messages, dropped.bytes);
     498                 :             : 
     499         [ -  + ]:       10517 :   if (decision == queue_util::EnqueueDecision::Rejected) {
     500                 :           0 :     WIRESTEAD_LOG_ERROR("tcp_server_session", "write", "Queue limit exceeded, dropping message");
     501                 :             :     // #448: record as dropped so it's reflected in RuntimeStats instead of
     502                 :             :     // silently vanishing after being counted as accepted.
     503                 :           0 :     stats_.record_dropped(1, added);
     504                 :           0 :     queue_util::release_reserved_limit_bytes(write_reserve_mtx_, inflight_bytes_, added);
     505                 :           0 :     report_backpressure(queue_bytes_ + added);
     506                 :       10479 :     return;
     507                 :             :   }
     508         [ +  + ]:       10517 :   if (decision == queue_util::EnqueueDecision::Pending) {
     509                 :       10479 :     queue_util::commit_reserved_limit_bytes(write_reserve_mtx_, pending_bytes_, inflight_bytes_, added);
     510                 :       10479 :     pending_.emplace_back(std::move(buf));
     511                 :       10479 :     observe_queue();
     512                 :       10479 :     return;
     513                 :             :   }
     514                 :          38 :   queue_util::commit_reserved_limit_bytes(write_reserve_mtx_, queue_bytes_, inflight_bytes_, added);
     515                 :          38 :   tx_.emplace_back(std::move(buf));
     516                 :          38 :   observe_queue();
     517                 :          38 :   report_backpressure(queue_bytes_);
     518   [ +  +  +  - ]:          38 :   if (!writing_) do_write();
     519                 :             : }
     520                 :             : 
     521                 :       12963 : void TcpServerSession::observe_queue() {
     522                 :       38889 :   stats_.observe_queue(queue_bytes_.load(std::memory_order_relaxed) + pending_bytes_.load(std::memory_order_relaxed));
     523                 :       12963 : }
     524                 :             : 
     525                 :        1306 : void TcpServerSession::report_backpressure(size_t queued_bytes) {
     526   [ +  -  -  +  :        1306 :   if (closing_ || !alive_) return;
                   -  + ]
     527                 :        1306 :   observe_queue();
     528                 :        1306 :   auto f = bp_fields();
     529                 :        1306 :   queue_util::report_backpressure(
     530                 :        1306 :       f, queued_bytes, on_bp_, stats_,
     531                 :           0 :       [&]() -> size_t {
     532                 :          37 :         const size_t moved = pending_bytes_.exchange(0);
     533         [ +  + ]:          39 :         while (!pending_.empty()) {
     534                 :           2 :           tx_.emplace_back(std::move(pending_.front()));
     535                 :           2 :           pending_.pop_front();
     536                 :             :         }
     537                 :          37 :         return moved;
     538                 :             :       },
     539                 :        1306 :       [&]() {
     540                 :          37 :         observe_queue();
     541         [ -  + ]:          37 :         if (!writing_) do_write();
     542                 :          37 :       });
     543                 :             : }
     544                 :             : 
     545                 :        3182 : void TcpServerSession::reset_idle_timer() {
     546         [ +  + ]:        3182 :   if (idle_timeout_ms_ <= 0) return;
     547                 :             : 
     548                 :             :   // Cancel any existing timer
     549                 :           3 :   idle_timer_.cancel();
     550                 :             : 
     551                 :             :   // Reset timer
     552                 :           3 :   idle_timer_.expires_after(std::chrono::milliseconds(idle_timeout_ms_));
     553                 :             : 
     554                 :           3 :   auto self = shared_from_this();
     555                 :           3 :   idle_timer_.async_wait(net::bind_executor(strand_, [self](const boost::system::error_code& ec) {
     556         [ +  + ]:           3 :     if (ec == boost::asio::error::operation_aborted) return;
     557   [ +  -  -  +  :           1 :     if (!self->alive_ || self->closing_) return;
                   -  + ]
     558                 :             : 
     559                 :           1 :     WIRESTEAD_LOG_WARNING("tcp_server_session", "timeout", "Connection idle timeout expired, closing session");
     560                 :           1 :     self->do_close();
     561                 :             :   }));
     562                 :           3 : }
     563                 :             : 
     564                 :             : }  // namespace transport
     565                 :             : }  // namespace wirestead
        

Generated by: LCOV version 2.0-1