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

            Line data    Source code
       1              : #include "flow_control.hpp"
       2              : #include <algorithm>
       3              : 
       4              : namespace thinger::http {
       5              : 
       6         3669 :     write_flow::write_flow(boost::asio::any_io_executor executor, write_limits limits) :
       7         3669 :         executor_(std::move(executor)),
       8         3669 :         state_(std::make_shared<shared_state>()),
       9         3669 :         drained_(executor_),
      10              :         // a member: never called once this is gone
      11         7970 :         write_timer_(executor_, [this]{ write_expired(); })
      12              :     {
      13         3669 :         set_limits(limits);
      14         3669 :     }
      15              : 
      16         3669 :     write_flow::~write_flow(){
      17              :         // notifications still posted find it closed
      18         3669 :         state_->closed = true;
      19         3669 :     }
      20              : 
      21         4374 :     void write_flow::set_limits(const write_limits& limits){
      22         4374 :         auto timed = limits_.write_timeout.count() > 0;
      23         4374 :         limits_ = limits;
      24              :         // the low watermark is never above the high one
      25         4374 :         limits_.low_watermark = std::min(limits_.low_watermark, limits_.high_watermark);
      26         4374 :         update_watermarks();
      27              :         // a pending write gets the new deadline at once: from its last progress, or from now
      28              :         // if it had none (0 disarms it)
      29         4374 :         if(write_watched_ && !closed_){
      30           24 :             if(!timed) write_progress_ = std::chrono::steady_clock::now();
      31           24 :             arm_write_timer();
      32              :         }
      33         4374 :     }
      34              : 
      35      6102685 :     bool write_flow::overflows(size_t size, size_t dropped_bytes, size_t dropped_messages) const{
      36      6102685 :         auto messages = queued_messages() - std::min(dropped_messages, queued_messages());
      37      6102685 :         auto bytes = queued_bytes() - std::min(dropped_bytes, queued_bytes());
      38      6102685 :         if(limits_.max_messages && messages >= limits_.max_messages) return true;
      39      2488623 :         return limits_.max_bytes && bytes > 0 && bytes + size > limits_.max_bytes;
      40              :     }
      41              : 
      42      1291329 :     void write_flow::add(size_t size){
      43              :         // single writer (the connection thread): relaxed stores, for readers on other threads
      44      1291329 :         queued_bytes_.store(queued_bytes() + size, std::memory_order_relaxed);
      45      1291329 :         queued_messages_.store(queued_messages() + 1, std::memory_order_relaxed);
      46      1291329 :         update_watermarks();
      47      1291329 :     }
      48              : 
      49      1259247 :     void write_flow::remove(size_t bytes, size_t messages){
      50      1259247 :         queued_bytes_.store(queued_bytes() - std::min(bytes, queued_bytes()), std::memory_order_relaxed);
      51      1259247 :         queued_messages_.store(queued_messages() - std::min(messages, queued_messages()), std::memory_order_relaxed);
      52      1259247 :         update_watermarks();
      53      1259247 :     }
      54              : 
      55         3630 :     void write_flow::clear(){
      56         3630 :         queued_bytes_.store(0, std::memory_order_relaxed);
      57         3630 :         queued_messages_.store(0, std::memory_order_relaxed);
      58         3630 :     }
      59              : 
      60      2554950 :     void write_flow::update_watermarks(){
      61      2554950 :         if(closed_) return;
      62      2554768 :         auto queued = queued_bytes();
      63      2554768 :         if(!congested_ && limits_.high_watermark && queued >= limits_.high_watermark){
      64          249 :             congested_ = true;
      65          249 :             drained_.reset();
      66          249 :             notify(true);
      67      2554519 :         }else if(congested_ && (!limits_.high_watermark || queued <= limits_.low_watermark)){
      68          237 :             congested_ = false;
      69          237 :             drained_.set();
      70          237 :             notify(false);
      71              :         }
      72              :     }
      73              : 
      74          486 :     void write_flow::notify(bool high){
      75          486 :         if(!(high ? state_->on_high : state_->on_low)) return;
      76              :         // never from within a send or a drop: whatever the callback does (sending, closing...)
      77              :         // finds the queue consistent. Dropped if the connection closes meanwhile
      78           66 :         boost::asio::post(executor_, [state = state_, high]{
      79           66 :             if(state->closed) return;
      80           66 :             auto callback = high ? state->on_high : state->on_low;
      81           66 :             if(callback) callback();
      82           66 :         });
      83              :     }
      84              : 
      85         9633 :     awaitable<bool> write_flow::wait_writable(){
      86              :         while(congested_ && !closed_){
      87              :             co_await drained_.wait();
      88              :             auto state = co_await boost::asio::this_coro::cancellation_state;
      89              :             if(state.cancelled() != boost::asio::cancellation_type::none) co_return false;
      90              :         }
      91              :         co_return !closed_;
      92        19266 :     }
      93              : 
      94          759 :     void write_flow::dispatch(std::shared_ptr<void> owner, sync_callback<void()> operation){
      95          759 :         boost::asio::dispatch(executor_, [owner = std::move(owner), operation = std::move(operation)]{
      96          759 :             operation();
      97          759 :         });
      98          759 :     }
      99              : 
     100          693 :     void write_flow::set_limits(std::shared_ptr<void> owner, const write_limits& limits){
     101         1386 :         dispatch(std::move(owner), [this, limits]{ set_limits(limits); });
     102          693 :     }
     103              : 
     104           27 :     void write_flow::on_high_watermark(std::shared_ptr<void> owner, sync_callback<void()> callback){
     105           27 :         dispatch(std::move(owner), [this, callback = std::move(callback)]() mutable {
     106           27 :             if(!closed_) state_->on_high = std::move(callback);
     107           27 :         });
     108           27 :     }
     109              : 
     110           39 :     void write_flow::on_low_watermark(std::shared_ptr<void> owner, sync_callback<void()> callback){
     111           39 :         dispatch(std::move(owner), [this, callback = std::move(callback)]() mutable {
     112           39 :             if(!closed_) state_->on_low = std::move(callback);
     113           39 :         });
     114           39 :     }
     115              : 
     116         9633 :     awaitable<bool> write_flow::wait_writable(std::shared_ptr<void> owner){
     117         9633 :         return on_executor(executor_, std::move(owner), wait_writable());
     118              :     }
     119              : 
     120        55570 :     void write_flow::write_started(sync_callback<void()> on_timeout){
     121        55570 :         if(closed_) return;
     122              :         // watched also without a deadline, which set_limits() may give it meanwhile
     123        55570 :         write_watched_ = true;
     124        55570 :         on_write_timeout_ = std::move(on_timeout);
     125        55570 :         write_progressed();
     126              :     }
     127              : 
     128        80980 :     void write_flow::write_progressed(){
     129        80980 :         if(!write_watched_ || limits_.write_timeout.count() <= 0) return;
     130        80467 :         write_progress_ = std::chrono::steady_clock::now();
     131        80467 :         backlog_checked_ = false;
     132        80467 :         arm_write_timer();
     133              :     }
     134              : 
     135         3642 :     void write_flow::set_send_backlog(sync_callback<size_t()> backlog){
     136         3642 :         send_backlog_ = std::move(backlog);
     137         3642 :     }
     138              : 
     139              :     namespace {
     140          491 :         std::chrono::milliseconds backlog_interval(std::chrono::milliseconds timeout){
     141          491 :             return std::max(timeout / write_flow::BACKLOG_CHECKS, std::chrono::milliseconds(1));
     142              :         }
     143              :     }
     144              : 
     145        80491 :     void write_flow::arm_write_timer(){
     146        80491 :         if(limits_.write_timeout.count() <= 0){
     147            3 :             write_timer_.cancel();
     148            3 :             return;
     149              :         }
     150              :         // a later deadline does not touch the timer (see watchdog): the backlog is checked only
     151              :         // once it passes, so a write that progresses within it costs no timer operation
     152        80488 :         write_timer_.expires_at(write_progress_ + limits_.write_timeout);
     153              :     }
     154              : 
     155          632 :     void write_flow::write_expired(){
     156              :         // the deadline of a write already finished (see write_finished())
     157         1156 :         if(!write_watched_) return;
     158          605 :         if(send_backlog_ && limits_.write_timeout.count() > 0){
     159          587 :             auto now = std::chrono::steady_clock::now();
     160          587 :             auto backlog = send_backlog_();
     161              :             // the first check, at the deadline, watches the backlog for BACKLOG_WATCH more (a peer
     162              :             // may take bytes in bursts, e.g. a window of one segment, 64 KiB over loopback); then
     163              :             // the peer taking bytes since the last check is progress, as the socket taking some
     164          587 :             auto deadline = now + limits_.write_timeout / BACKLOG_WATCH;
     165          587 :             if(backlog_checked_){
     166          491 :                 if(backlog < backlog_seen_) write_progress_ = now;
     167          491 :                 deadline = std::min(now + backlog_interval(limits_.write_timeout), write_progress_ + limits_.write_timeout);
     168              :             }
     169          587 :             backlog_seen_ = backlog;
     170          587 :             backlog_checked_ = true;
     171          587 :             if(now < deadline){
     172          524 :                 write_timer_.expires_at(deadline);
     173          524 :                 return;
     174              :             }
     175              :         }
     176              :         // on_timeout may destroy this, so nothing of it is touched after the call
     177           81 :         auto on_timeout = std::move(on_write_timeout_);
     178           81 :         on_write_timeout_ = nullptr;
     179           81 :         if(on_timeout) on_timeout();
     180           81 :     }
     181              : 
     182        55536 :     void write_flow::write_finished(){
     183        55536 :         if(!write_watched_) return;
     184        55536 :         write_watched_ = false;
     185              :         // the timer is left armed: the next write's deadline, later, does not touch it (see
     186              :         // watchdog), so a stream of writes costs no timer operation each; an expiry with no
     187              :         // write pending does nothing
     188        55536 :         on_write_timeout_ = nullptr;
     189              :     }
     190              : 
     191         3630 :     void write_flow::close(){
     192         3630 :         closed_ = true;
     193         3630 :         congested_ = false;
     194         3630 :         state_->closed = true;
     195         3630 :         state_->on_high = nullptr;
     196         3630 :         state_->on_low = nullptr;
     197         3630 :         drained_.set();
     198         3630 :         write_timer_.cancel();
     199         3630 :         on_write_timeout_ = nullptr;
     200         3630 :     }
     201              : 
     202              : }
        

Generated by: LCOV version 2.0-1