LCOV - code coverage report
Current view: top level - wirestead/wrapper/uds_server - uds_server.cc (source / functions) Coverage Total Hit
Test: Wirestead Coverage Report Lines: 93.8 % 640 600
Test Date: 2026-10-29 11:36:05 Functions: 93.9 % 82 77
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 69.0 % 422 291

             Branch data     Line data    Source code
       1                 :             : #include "wirestead/wrapper/uds_server/uds_server.hpp"
       2                 :             : 
       3                 :             : #include <algorithm>
       4                 :             : #include <atomic>
       5                 :             : #include <boost/asio/executor_work_guard.hpp>
       6                 :             : #include <boost/asio/io_context.hpp>
       7                 :             : #include <boost/asio/steady_timer.hpp>
       8                 :             : #include <boost/asio/strand.hpp>
       9                 :             : #include <shared_mutex>
      10                 :             : #include <stop_token>
      11                 :             : #include <thread>
      12                 :             : 
      13                 :             : #include "wirestead/concurrency/io_thread_hook.hpp"
      14                 :             : #include "wirestead/config/validation.hpp"
      15                 :             : #include "wirestead/diagnostics/error_mapping.hpp"
      16                 :             : #include "wirestead/factory/channel_factory.hpp"
      17                 :             : #include "wirestead/framer/line_framer.hpp"
      18                 :             : #include "wirestead/framer/packet_framer.hpp"
      19                 :             : #include "wirestead/transport/uds/uds_server.hpp"
      20                 :             : #include "wirestead/util/input_validator.hpp"
      21                 :             : #include "wirestead/wrapper/callback_guard.hpp"
      22                 :             : #include "wirestead/wrapper/error_context_builder.hpp"
      23                 :             : #include "wirestead/wrapper/send_retry.hpp"
      24                 :             : #include "wirestead/wrapper/send_validation.hpp"
      25                 :             : #include "wirestead/wrapper/session_batch.hpp"
      26                 :             : 
      27                 :             : namespace wirestead {
      28                 :             : namespace wrapper {
      29                 :             : 
      30                 :             : struct UdsServer::Impl : public std::enable_shared_from_this<Impl> {
      31                 :             :   std::string socket_path_;
      32                 :             :   std::shared_ptr<boost::asio::io_context> external_ioc_;
      33                 :             :   std::atomic<bool> use_external_context_{false};
      34                 :             :   std::atomic<bool> manage_external_context_{false};
      35                 :             : 
      36                 :             :   mutable std::shared_mutex mutex_;
      37                 :             :   std::mutex bp_mutex_;
      38                 :             :   std::condition_variable bp_cv_;
      39                 :             :   // D-1: admission gate for this object's user callbacks. Admission, the
      40                 :             :   // running count and the closed flag are one decision, so a stop() cannot
      41                 :             :   // observe an empty gate while a callback is about to start, and a callback
      42                 :             :   // left over from a previous run is refused after a restart.
      43                 :             :   detail::CallbackGate callback_gate_;
      44                 :             :   std::mutex stop_finalize_mutex_;
      45                 :             :   bool injected_channel_ = false;
      46                 :             :   std::atomic<uint64_t> callback_generation_{0};
      47                 :             :   bool stop_requested_ = false;
      48                 :             :   std::atomic<unsigned> stop_callers_{0};
      49                 :             : 
      50                 :             :   // True when this thread is one the target's shutdown needs: a callback of
      51                 :             :   // this object, or any thread currently running the external io_context this
      52                 :             :   // channel was built on (which a callback of another channel sharing it is).
      53                 :             :   // Such a caller requests the shutdown and returns; it cannot wait for work
      54                 :             :   // its own thread has to perform.
      55                 :         583 :   bool shutdown_needs_this_thread() const {
      56   [ +  -  +  + ]:         583 :     if (callback_gate_.active_on_this_thread()) return true;
      57   [ +  +  -  +  :         579 :     if (external_ioc_ && external_ioc_->get_executor().running_in_this_thread()) return true;
             +  +  -  + ]
      58                 :         579 :     std::shared_lock<std::shared_mutex> lock(mutex_);
      59         [ +  + ]:         579 :     if (!server_) return false;
      60                 :         430 :     const auto executor = server_->get_executor();
      61                 :             :     using IoExecutor = boost::asio::io_context::executor_type;
      62   [ +  -  +  + ]:         430 :     if (auto* io = executor.target<IoExecutor>()) return io->running_in_this_thread();
      63   [ +  -  +  - ]:         419 :     if (auto* strand = executor.target<boost::asio::strand<IoExecutor>>())
      64                 :         419 :       return strand->get_inner_executor().running_in_this_thread();
      65                 :           0 :     return false;
      66                 :         579 :   }
      67                 :             : 
      68                 :             :   std::shared_ptr<interface::Channel> server_;
      69                 :             :   std::vector<std::promise<bool>> pending_promises_;
      70                 :             :   std::jthread external_thread_;
      71                 :             :   std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard_;
      72                 :             : 
      73                 :             :   std::atomic<bool> started_{false};
      74                 :             :   std::atomic<bool> is_listening_{false};
      75                 :         170 :   std::shared_ptr<bool> alive_marker_{std::make_shared<bool>(true)};
      76                 :             : 
      77                 :             :   // Configuration
      78                 :             :   std::atomic<bool> auto_start_{false};
      79                 :             :   std::atomic<int> idle_timeout_ms_{static_cast<int>(base::constants::DEFAULT_IDLE_TIMEOUT_MS)};
      80                 :             :   std::atomic<size_t> max_clients_{0};
      81                 :             :   std::atomic<bool> client_limit_enabled_{false};
      82                 :             :   std::atomic<int> socket_permissions_{-1};
      83                 :             :   std::atomic<size_t> backpressure_threshold_{base::constants::DEFAULT_BACKPRESSURE_THRESHOLD};
      84                 :             :   std::atomic<size_t> read_buffer_size_{base::constants::DEFAULT_READ_BUFFER_SIZE};
      85                 :             :   std::atomic<base::constants::BackpressureStrategy> backpressure_strategy_{
      86                 :             :       base::constants::BackpressureStrategy::Reliable};
      87                 :             : 
      88                 :             :   ConnectionHandler client_connect_handler_{nullptr};
      89                 :             :   ConnectionHandler client_disconnect_handler_{nullptr};
      90                 :             :   // Shared snapshots: the io thread copies one out per received chunk, and a
      91                 :             :   // std::function copy allocates whenever the user handler outgrows its
      92                 :             :   // small-object buffer. See interface::SharedCallback.
      93                 :             :   interface::SharedCallback<MessageHandler> data_handler_;
      94                 :             :   interface::SharedCallback<BatchMessageHandler> data_batch_handler_;
      95                 :             :   ErrorHandler error_handler_{nullptr};
      96                 :             :   std::function<void(size_t)> on_backpressure_{nullptr};
      97                 :             :   FramerFactory framer_factory_{nullptr};
      98                 :             :   interface::SharedCallback<MessageHandler> on_message_;
      99                 :             :   interface::SharedCallback<BatchMessageHandler> on_message_batch_;
     100                 :             : 
     101                 :             :   std::unordered_map<ClientId, std::shared_ptr<framer::IFramer>> framers_;
     102                 :             : 
     103                 :             :   // Batching logic
     104                 :             :   bool terminal_error_reported_ = false;
     105                 :         123 :   void require_stopped_config() const {
     106   [ +  +  +  -  :         242 :     if (started_.load() || stop_callers_.load() || (stop_requested_ && alive_marker_) || detail::in_data_callback())
          +  +  +  -  -  
                +  +  + ]
     107                 :           4 :       throw std::logic_error("configuration requires completed stop");
     108                 :         119 :   }
     109                 :             :   ReceiveLimits receive_limits_;
     110                 :             :   std::shared_ptr<detail::ReceiveBudget> receive_budget_{std::make_shared<detail::ReceiveBudget>(receive_limits_)};
     111                 :             :   std::unordered_map<ClientId, std::shared_ptr<detail::SessionBatch>> batches_;
     112                 :             :   size_t max_batch_size_ = 100;
     113                 :         170 :   std::chrono::milliseconds max_batch_latency_{1};
     114                 :             : 
     115                 :          63 :   explicit Impl(const std::string& socket_path) : socket_path_(socket_path) {}
     116                 :             : 
     117                 :           8 :   Impl(const std::string& socket_path, std::shared_ptr<boost::asio::io_context> external_ioc)
     118                 :           8 :       : socket_path_(socket_path),
     119                 :           8 :         external_ioc_(std::move(external_ioc)),
     120                 :           8 :         use_external_context_(external_ioc_ != nullptr),
     121                 :          16 :         manage_external_context_(false) {}
     122                 :             : 
     123                 :         297 :   explicit Impl(std::shared_ptr<interface::Channel> channel) : socket_path_(""), server_(std::move(channel)) {
     124                 :          99 :     injected_channel_ = true;
     125                 :             :     // #450: setup_internal_handlers() captures weak_from_this() - calling it
     126                 :             :     // from inside this constructor would capture an empty weak_ptr, since
     127                 :             :     // enable_shared_from_this isn't wired up until make_shared() finishes
     128                 :             :     // constructing the object. Deferred to UdsServer's own constructor,
     129                 :             :     // which runs after impl_ is a fully-formed shared_ptr<Impl>.
     130                 :          99 :   }
     131                 :             : 
     132                 :         170 :   ~Impl() {
     133                 :             :     try {
     134                 :         170 :       stop();
     135                 :           0 :     } catch (...) {
     136                 :           0 :     }
     137                 :         170 :   }
     138                 :             : 
     139                 :         757 :   void fulfill_all_locked(bool value) {
     140         [ +  + ]:         928 :     for (auto& p : pending_promises_) {
     141                 :             :       try {
     142                 :         171 :         p.set_value(value);
     143                 :           0 :       } catch (...) {
     144                 :           0 :       }
     145                 :             :     }
     146                 :         757 :     pending_promises_.clear();
     147                 :         757 :   }
     148                 :             : 
     149                 :         108 :   void flush_batches(uint64_t generation, ClientId id, const std::shared_ptr<detail::SessionBatch>& state) {
     150                 :         108 :     auto lease = callback_gate_.enter(generation);
     151         [ -  + ]:         108 :     if (!lease.admitted()) return;
     152                 :         108 :     std::unique_lock<std::shared_mutex> lock(mutex_);
     153                 :         108 :     auto found = batches_.find(id);
     154   [ +  -  -  +  :         108 :     if (found == batches_.end() || found->second != state) return;
                   -  + ]
     155                 :         108 :     state->scheduled = false;
     156                 :         108 :     auto data = std::move(state->data);
     157                 :         108 :     auto messages = std::move(state->messages);
     158                 :         108 :     state->data.clear();
     159                 :         108 :     state->messages.clear();
     160                 :         108 :     auto data_handler = data_batch_handler_;
     161                 :         108 :     auto message_handler = on_message_batch_;
     162                 :         108 :     lock.unlock();
     163   [ +  +  +  - ]:         108 :     if (!data.empty()) detail::invoke_user_callback("uds_server", "on_data_batch", data_handler, data);
     164                 :             :     // A callback may stop the server. Recheck admission before another callback.
     165                 :         108 :     auto next = callback_gate_.enter(generation);
     166   [ +  -  +  +  :         108 :     if (next.admitted() && !messages.empty())
                   +  + ]
     167                 :           4 :       detail::invoke_user_callback("uds_server", "on_message_batch", message_handler, messages);
     168                 :         108 :   }
     169                 :             : 
     170                 :             :   // Caller holds mutex_. Final admission rechecks the native lifecycle.
     171                 :         545 :   SendResult send_state(const std::shared_ptr<transport::UdsServer>& ts) {
     172         [ +  + ]:        1090 :     if (stop_callers_.load() != 0) return SendResult::reject(SendRejection::Stopping);
     173         [ +  + ]:         544 :     if (!started_.load()) {
     174         [ +  + ]:          44 :       if (stop_requested_) {
     175         [ -  + ]:          26 :         if (!callback_gate_.idle()) return SendResult::reject(SendRejection::Stopping);
     176         [ +  - ]:          26 :         if (ts) {
     177                 :          26 :           const auto state = ts->target_state();
     178   [ +  -  +  +  :          26 :           if (!state.accepted() && state.reason() == SendRejection::Stopping) return state;
                   +  + ]
     179                 :             :         }
     180                 :             :       }
     181                 :          33 :       return SendResult::reject(SendRejection::NotStarted);
     182                 :             :     }
     183         [ +  + ]:         500 :     if (!ts) return SendResult::reject(SendRejection::NotReady);
     184                 :         495 :     return SendResult::accept();
     185                 :             :   }
     186                 :             : 
     187                 :          94 :   SendResult try_send_to(ClientId client_id, std::string_view data, bool best_effort_send = false) {
     188                 :          94 :     std::shared_lock<std::shared_mutex> lock(mutex_);
     189                 :          94 :     auto ts = std::dynamic_pointer_cast<transport::UdsServer>(server_);
     190                 :           0 :     const auto result = [&]() -> SendResult {
     191                 :             :       auto validation =
     192   [ +  +  +  - ]:          94 :           detail::validate_payload_size(data.size(), ts ? ts->write_queue_limit(client_id) : std::nullopt);
     193         [ +  + ]:          94 :       if (!validation.accepted()) return validation;
     194                 :          83 :       const auto state = send_state(ts);
     195         [ +  + ]:          83 :       if (!state.accepted()) return state;
     196                 :         114 :       auto admitted = ts->write_target(
     197                 :          57 :           client_id, memory::ConstByteSpan(reinterpret_cast<const uint8_t*>(data.data()), data.size()), true);
     198   [ +  +  +  +  :          57 :       if (best_effort_send && !admitted.accepted() && admitted.reason() == SendRejection::WouldBlock)
             +  +  +  + ]
     199                 :           2 :         return SendResult::reject(SendRejection::QueueFull);
     200                 :          55 :       return admitted;
     201                 :          94 :     }();
     202                 :          94 :     lock.unlock();
     203   [ +  +  +  - ]:          94 :     if (auto hook = detail::g_uds_server_send_result_hook.load()) hook(result);
     204                 :          94 :     return result;
     205                 :          94 :   }
     206                 :             : 
     207                 :         128 :   FanoutResult try_broadcast(std::string_view data, bool append_newline = false) {
     208                 :         128 :     std::shared_lock<std::shared_mutex> lock(mutex_);
     209                 :         128 :     auto ts = std::dynamic_pointer_cast<transport::UdsServer>(server_);
     210   [ +  +  +  -  :         128 :     return ts ? ts->broadcast_result({reinterpret_cast<const uint8_t*>(data.data()), data.size()}, send_state(ts),
                   +  - ]
     211                 :             :                                      append_newline)
     212                 :         140 :               : FanoutResult{};
     213                 :         128 :   }
     214                 :             : 
     215                 :         166 :   SendResult send_to(ClientId client_id, std::string_view data) {
     216         [ +  + ]:         166 :     if (backpressure_strategy_.load() == base::constants::BackpressureStrategy::Reliable)
     217                 :         148 :       return send_to_blocking(client_id, data);
     218                 :          18 :     return try_send_to(client_id, data, true);
     219                 :             :   }
     220                 :             : 
     221                 :          78 :   FanoutResult broadcast(std::string_view data) { return try_broadcast(data); }
     222                 :             : 
     223                 :             :   // channel_->on_backpressure()/session-level backpressure callbacks call bp_cv_.notify_all()
     224                 :             :   // from the transport's io_context thread without holding bp_mutex_ - a classic lost-wakeup
     225                 :             :   // race is possible: a waiter can check the predicate, find it still blocking, and be in the
     226                 :             :   // process of registering to wait when the notify fires. Poll with a bounded timeout instead
     227                 :             :   // of an unbounded wait() so a missed notify only costs a short delay rather than a
     228                 :             :   // permanent hang (see #427, #431).
     229                 :             :   //
     230                 :             :   // Returns false without sending instead of waiting if called from the
     231                 :             :   // channel's own io thread while backpressure is active for this client -
     232                 :             :   // e.g. a blocking send_to() called from inside an on_data/on_message
     233                 :             :   // callback. Clearing backpressure requires that same io thread to make
     234                 :             :   // progress, so blocking here would deadlock forever rather than
     235                 :             :   // eventually clear (#449).
     236                 :             :   // #509: see identical rationale in wrapper/tcp_server/tcp_server.cc -
     237                 :             :   // capacity retry rather than a single attempt after the wait exits.
     238                 :             : 
     239                 :         249 :   SendResult send_to_blocking(ClientId client_id, std::string_view data) {
     240                 :         249 :     std::shared_ptr<transport::UdsServer> ts;
     241                 :         249 :     std::shared_ptr<transport::UdsServerSession> session;
     242                 :         249 :     uint64_t generation = 0;
     243                 :         249 :     bool cannot_wait = false;
     244                 :           0 :     const auto result = [&]() -> SendResult {
     245                 :             :       {
     246                 :         249 :         std::shared_lock<std::shared_mutex> lock(mutex_);
     247                 :         249 :         ts = std::dynamic_pointer_cast<transport::UdsServer>(server_);
     248                 :             :         auto validation =
     249   [ +  +  +  - ]:         249 :             detail::validate_payload_size(data.size(), ts ? ts->write_queue_limit(client_id) : std::nullopt);
     250         [ +  + ]:         249 :         if (!validation.accepted()) return validation;
     251                 :         229 :         auto state = send_state(ts);
     252         [ +  + ]:         229 :         if (!state.accepted()) return state;
     253                 :         210 :         state = ts->target_state();
     254         [ -  + ]:         210 :         if (!state.accepted()) return state;
     255                 :         210 :         generation = callback_generation_.load();
     256   [ +  +  +  -  :         210 :         cannot_wait = detail::in_data_callback() || detail::executor_running_here(ts->get_executor());
          +  -  +  +  +  
                +  -  - ]
     257                 :         210 :         session = ts->capture_target(client_id);
     258         [ +  + ]:         210 :         if (!session) return SendResult::reject(SendRejection::NotReady);
     259                 :         249 :       }
     260                 :         211 :       auto wait = [&]() -> SendResult {
     261                 :         211 :         std::unique_lock<std::mutex> bp_lock(bp_mutex_);
     262                 :             :         {
     263                 :         211 :           std::shared_lock<std::shared_mutex> lock(mutex_);
     264                 :             :           // No observed pressure means no capacity wait. Final admission still
     265                 :             :           // checks lifecycle and the exact session selected at entry.
     266   [ +  +  +  -  :         631 :           if (!started_.load() || callback_generation_.load() != generation ||
                   +  + ]
     267                 :         421 :               ts->poll_target_wait(session).has_value())
     268                 :          96 :             return SendResult::accept();
     269                 :         211 :         }
     270         [ +  + ]:         115 :         if (cannot_wait) return SendResult::reject(SendRejection::WouldBlock);
     271   [ +  +  +  - ]:          45 :         if (auto hook = detail::g_uds_server_capacity_wait_hook.load()) hook();
     272                 :          45 :         std::optional<SendResult> outcome;
     273                 :          86 :         auto released = [&] {
     274                 :             :           // The retained session owns its first terminal cause, even after
     275                 :             :           // removal from the map or a later server/wrapper restart.
     276                 :          86 :           outcome = ts->poll_target_wait(session);
     277                 :          86 :           return outcome.has_value();
     278                 :          45 :         };
     279   [ +  -  +  + ]:          61 :         while (!bp_cv_.wait_for(bp_lock, std::chrono::milliseconds(50), released)) {
     280                 :             :         }
     281   [ +  +  +  - ]:          45 :         if (auto hook = detail::g_uds_server_capacity_wait_result_hook.load()) hook(*outcome);
     282                 :          45 :         return *outcome;
     283                 :         211 :       };
     284                 :         199 :       for (bool retry = false;; retry = true) {
     285   [ +  +  +  - ]:         211 :         if (retry) detail::pause_send_retry(bp_cv_, bp_mutex_);
     286                 :         211 :         const auto released = wait();
     287         [ +  + ]:         316 :         if (!released.accepted()) return released;
     288                 :         117 :         std::shared_lock<std::shared_mutex> lock(mutex_);
     289                 :         117 :         const auto state = send_state(ts);
     290         [ +  + ]:         117 :         if (!state.accepted()) return state;
     291         [ +  + ]:         224 :         if (callback_generation_.load() != generation) return SendResult::reject(SendRejection::NotReady);
     292                 :         216 :         const auto admitted = ts->write_target(
     293                 :         108 :             client_id, memory::ConstByteSpan(reinterpret_cast<const uint8_t*>(data.data()), data.size()), false,
     294                 :             :             session);
     295   [ +  +  +  +  :         108 :         if (admitted.accepted() || admitted.reason() != SendRejection::WouldBlock) return admitted;
                   +  + ]
     296         [ +  + ]:          21 :         if (cannot_wait) return admitted;
     297                 :         129 :       }
     298                 :         249 :     }();
     299   [ +  +  +  - ]:         249 :     if (auto hook = detail::g_uds_server_send_result_hook.load()) hook(result);
     300                 :         249 :     return result;
     301                 :         249 :   }
     302                 :             : 
     303                 :             :   // Caller holds mutex_; do not postpone an existing deadline when the
     304                 :             :   // other queue receives its first item.
     305                 :          15 :   void schedule_batch_timer(uint64_t generation, ClientId id) {
     306                 :          15 :     auto found = batches_.find(id);
     307   [ +  -  +  +  :          15 :     if (found == batches_.end() || found->second->scheduled) return;
                   +  + ]
     308                 :          14 :     auto state = found->second;
     309                 :          14 :     state->scheduled = true;
     310                 :          14 :     state->timer.expires_after(max_batch_latency_);
     311                 :          14 :     state->timer.async_wait(
     312                 :          14 :         [weak_impl = weak_from_this(), generation, id,
     313                 :             :          weak_state = std::weak_ptr<detail::SessionBatch>(state)](const boost::system::error_code& ec) {
     314         [ +  + ]:          11 :           if (ec) return;
     315                 :           7 :           auto self = weak_impl.lock();
     316                 :           7 :           auto batch = weak_state.lock();
     317   [ +  -  +  -  :           7 :           if (self && batch) self->flush_batches(generation, id, batch);
             +  -  +  - ]
     318                 :           7 :         });
     319                 :          14 :   }
     320                 :             : 
     321                 :         173 :   std::future<bool> start() {
     322                 :         173 :     std::unique_lock<std::shared_mutex> lock(mutex_);
     323                 :         173 :     stop_requested_ = false;
     324         [ +  + ]:         173 :     if (is_listening_.load()) {
     325                 :           2 :       started_.store(true);
     326                 :           2 :       std::promise<bool> p;
     327                 :           2 :       p.set_value(true);
     328                 :           2 :       return p.get_future();
     329                 :           2 :     }
     330                 :         171 :     std::promise<bool> p;
     331                 :         171 :     auto f = p.get_future();
     332                 :         171 :     pending_promises_.push_back(std::move(p));
     333         [ -  + ]:         171 :     if (started_.exchange(true)) return f;
     334                 :             : 
     335   [ +  +  +  - ]:         171 :     if (!alive_marker_) alive_marker_ = std::make_shared<bool>(true);
     336         [ +  + ]:         171 :     if (!server_) {
     337                 :          63 :       config::UdsServerConfig config;
     338                 :          63 :       config.socket_path = socket_path_;
     339                 :          63 :       config.idle_timeout_ms = idle_timeout_ms_.load();
     340                 :          63 :       config.max_connections =
     341                 :         126 :           static_cast<int>(std::min(max_clients_.load(), static_cast<size_t>(base::constants::MAX_MAX_CONNECTIONS)));
     342                 :          63 :       config.backpressure_threshold = backpressure_threshold_.load();
     343                 :          63 :       config.read_buffer_size = read_buffer_size_.load();
     344                 :          63 :       config.backpressure_strategy = backpressure_strategy_.load();
     345                 :          63 :       config.socket_permissions = socket_permissions_.load();
     346                 :          63 :       server_ = factory::ChannelFactory::create(config, external_ioc_);
     347                 :          63 :     }
     348                 :         171 :     setup_internal_handlers();
     349                 :             : 
     350                 :         171 :     auto channel_copy = server_;
     351                 :         171 :     lock.unlock();
     352                 :         171 :     channel_copy->start();
     353         [ -  + ]:         171 :     if (!started_.load()) return f;
     354                 :             : 
     355   [ +  +  +  +  :         171 :     if (use_external_context_.load() && manage_external_context_.load() && !external_thread_.joinable()) {
             +  -  +  + ]
     356   [ +  -  +  -  :           4 :       if (external_ioc_ && external_ioc_->stopped()) {
             +  +  +  + ]
     357                 :           1 :         external_ioc_->restart();
     358                 :             :       }
     359                 :           8 :       work_guard_ = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(
     360                 :          12 :           boost::asio::make_work_guard(*external_ioc_));
     361                 :           8 :       external_thread_ = std::jthread([ioc = external_ioc_](std::stop_token st) {
     362                 :           4 :         wirestead::concurrency::run_io_thread_init();
     363                 :             :         try {
     364                 :           4 :           std::stop_callback cb(st, [ioc] { ioc->stop(); });
     365                 :           4 :           ioc->run();
     366                 :           4 :         } catch (...) {
     367                 :           0 :         }
     368                 :           8 :       });
     369                 :             :     }
     370                 :             : 
     371                 :         171 :     return f;
     372                 :         173 :   }
     373                 :             : 
     374                 :         583 :   void stop() {
     375                 :         583 :     stop_callers_.fetch_add(1);
     376                 :             :     struct StopCall {
     377                 :             :       std::atomic<unsigned>& count;
     378                 :         583 :       ~StopCall() { count.fetch_sub(1); }
     379                 :         583 :     } stop_call{stop_callers_};
     380                 :         583 :     const bool request_only = shutdown_needs_this_thread();
     381                 :         583 :     callback_gate_.close();
     382                 :         583 :     std::shared_ptr<interface::Channel> channel;
     383                 :             :     {
     384                 :         583 :       std::unique_lock<std::shared_mutex> lock(mutex_);
     385                 :         583 :       stop_requested_ = true;
     386   [ +  +  +  - ]:         583 :       if (auto ts = std::dynamic_pointer_cast<transport::UdsServer>(server_)) ts->cancel_target_waits();
     387                 :         583 :       started_.store(false);
     388                 :         583 :       is_listening_.store(false);
     389                 :         583 :       bp_cv_.notify_all();
     390                 :         583 :       fulfill_all_locked(false);
     391                 :         583 :       channel = server_;
     392                 :         583 :     }
     393                 :             :     // Do not hold wrapper locks while the transport waits for callbacks.
     394   [ +  +  +  - ]:         583 :     if (channel) channel->stop();
     395         [ +  + ]:         583 :     if (request_only) return;
     396                 :             : 
     397                 :         550 :     callback_gate_.wait_until_idle();
     398                 :             :     // All outside callers pass this lock, including those that found the
     399                 :             :     // channel already cleared. Joining and final state reset happen once.
     400                 :         550 :     std::lock_guard<std::mutex> finalize_lock(stop_finalize_mutex_);
     401                 :             :     {
     402                 :         550 :       std::unique_lock<std::shared_mutex> lock(mutex_);
     403                 :         550 :       alive_marker_.reset();
     404                 :         550 :       batches_.clear();
     405         [ +  + ]:         550 :       if (server_) {
     406                 :         399 :         server_->on_bytes(nullptr);
     407                 :         399 :         server_->on_state(nullptr);
     408                 :         399 :         server_->on_backpressure(nullptr);
     409                 :             :       }
     410         [ +  + ]:         550 :       if (!injected_channel_) server_.reset();
     411                 :         550 :       framers_.clear();
     412                 :         550 :     }
     413   [ +  +  +  +  :         550 :     if (use_external_context_.load() && manage_external_context_.load()) {
                   +  + ]
     414         [ +  + ]:          16 :       if (work_guard_) work_guard_.reset();
     415   [ +  -  +  - ]:          16 :       if (external_ioc_) external_ioc_->stop();
     416   [ +  +  +  - ]:          16 :       if (external_thread_.joinable()) external_thread_.join();
     417                 :             :     }
     418                 :         616 :   }
     419                 :             : 
     420                 :         270 :   void setup_internal_handlers() {
     421         [ -  + ]:         270 :     if (!server_) return;
     422                 :             : 
     423                 :         270 :     std::weak_ptr<bool> weak_alive = alive_marker_;
     424                 :         270 :     std::weak_ptr<Impl> weak_impl = weak_from_this();
     425                 :         270 :     receive_budget_->reset_stats();
     426                 :         270 :     terminal_error_reported_ = false;
     427                 :         270 :     const auto generation = callback_gate_.open_new_generation();
     428                 :         270 :     callback_generation_.store(generation);
     429                 :             : 
     430                 :         270 :     auto transport_server = std::dynamic_pointer_cast<transport::UdsServer>(server_);
     431         [ +  + ]:         270 :     if (transport_server) {
     432                 :         526 :       transport_server->on_multi_connect(
     433                 :         526 :           [this, generation, weak_impl, weak_alive,
     434                 :             :            weak_native = std::weak_ptr<transport::UdsServer>(transport_server)](ClientId id, const std::string& info) {
     435                 :         204 :             auto impl_keepalive = weak_impl.lock();
     436         [ -  + ]:         204 :             if (!impl_keepalive) return;
     437                 :         204 :             auto alive = weak_alive.lock();
     438         [ -  + ]:         204 :             if (!alive) return;
     439                 :         204 :             auto lease = callback_gate_.enter(generation);
     440         [ -  + ]:         204 :             if (!lease.admitted()) return;
     441                 :             : 
     442                 :         204 :             auto native = weak_native.lock();
     443   [ +  -  +  - ]:         204 :             auto executor = native ? native->client_executor(id) : std::nullopt;
     444         [ -  + ]:         204 :             if (!executor) return;
     445                 :         204 :             auto receive_scope = receive_budget_->open_scope();
     446         [ +  + ]:         204 :             if (!receive_scope) {
     447                 :           1 :               native->fail_receive(id);
     448                 :           1 :               return;
     449                 :             :             }
     450                 :             : 
     451                 :         203 :             ConnectionHandler handler;
     452                 :             :             {
     453                 :         203 :               std::unique_lock<std::shared_mutex> lock(mutex_);
     454                 :         203 :               batches_[id] = std::make_shared<detail::SessionBatch>(*executor, std::move(receive_scope));
     455         [ +  + ]:         203 :               if (framer_factory_) {
     456                 :          40 :                 framers_[id] = framer_factory_();
     457                 :          40 :                 attach_framer_callback(id);
     458                 :             :               }
     459                 :         203 :               handler = client_connect_handler_;
     460                 :         203 :             }
     461                 :         203 :             detail::invoke_user_callback("uds_server", "on_connect", handler, ConnectionContext(id, info));
     462                 :         209 :           });
     463                 :         526 :       transport_server->on_multi_data(
     464                 :         526 :           [this, generation, weak_impl, weak_alive](ClientId id, memory::ConstByteSpan data_span) {
     465                 :          69 :             auto impl_keepalive = weak_impl.lock();
     466         [ -  + ]:          69 :             if (!impl_keepalive) return;
     467                 :          69 :             auto alive = weak_alive.lock();
     468         [ -  + ]:          69 :             if (!alive) return;
     469                 :          69 :             auto lease = callback_gate_.enter(generation);
     470         [ -  + ]:          69 :             if (!lease.admitted()) return;
     471                 :             : 
     472                 :             :             // #449: everything below runs synchronously on this io thread -
     473                 :             :             // mark it so a blocking send_to() called from within one of these
     474                 :             :             // callbacks fails fast instead of deadlocking.
     475                 :          69 :             detail::CallbackGuard callback_guard;
     476                 :             : 
     477                 :             :             // #441: snapshot the handler/framer pointers under a shared_lock
     478                 :             :             // (not unique_lock) - this is a pure read, matching try_send's
     479                 :             :             // locking level so it no longer blocks concurrent sends even
     480                 :             :             // briefly.
     481                 :             :             bool batch_mode;
     482                 :          69 :             std::shared_ptr<detail::SessionBatch> receive;
     483                 :          69 :             interface::SharedCallback<MessageHandler> handler;
     484                 :          69 :             std::shared_ptr<framer::IFramer> framer_to_push;
     485                 :             :             {
     486                 :          69 :               std::shared_lock<std::shared_mutex> lock(mutex_);
     487                 :          69 :               auto state = batches_.find(id);
     488         [ -  + ]:          69 :               if (state == batches_.end()) return;
     489                 :          69 :               receive = state->second;
     490                 :          69 :               batch_mode = static_cast<bool>(data_batch_handler_);
     491                 :          69 :               handler = data_handler_;
     492                 :          69 :               auto it = framers_.find(id);
     493         [ +  + ]:          69 :               if (it != framers_.end()) {
     494                 :          29 :                 framer_to_push = it->second;
     495                 :             :               }
     496                 :          69 :             }
     497                 :             : 
     498                 :             :             try {
     499                 :          69 :               auto prepared = detail::prepare_receive(receive->receive, framer_to_push, id, data_span, batch_mode);
     500         [ +  + ]:          67 :               if (batch_mode) {
     501                 :             :                 // #441: build the copy before taking the exclusive lock, so the
     502                 :             :                 // lock is only held for the queue mutation itself, not the
     503                 :             :                 // allocation.
     504                 :          11 :                 auto ctx = std::move(*prepared.raw);
     505                 :          11 :                 interface::SharedCallback<BatchMessageHandler> flush_handler;
     506                 :          11 :                 detail::ReceiveBatch batch;
     507                 :             :                 {
     508                 :          11 :                   std::unique_lock<std::shared_mutex> lock(mutex_);
     509                 :          11 :                   auto state = batches_.find(id);
     510   [ +  -  -  +  :          11 :                   if (state == batches_.end() || callback_generation_ != generation) return;
                   -  + ]
     511                 :          11 :                   state->second->data.emplace_back(std::move(ctx));
     512   [ +  -  +  + ]:          11 :                   if (batches_.at(id)->data.size() >= max_batch_size_) {
     513                 :           4 :                     flush_handler = data_batch_handler_;
     514                 :           4 :                     batch = std::move(batches_.at(id)->data);
     515                 :           4 :                     batches_.at(id)->data.clear();
     516   [ +  -  +  - ]:           7 :                   } else if (batches_.at(id)->data.size() == 1) {
     517                 :           7 :                     schedule_batch_timer(generation, id);
     518                 :             :                   }
     519                 :          11 :                 }
     520                 :          11 :                 detail::invoke_user_callback("uds_server", "on_data_batch", flush_handler, batch);
     521                 :          11 :               } else {
     522                 :         112 :                 detail::invoke_user_callback("uds_server", "on_data", handler, MessageContext(id, data_span));
     523                 :             :               }
     524                 :             : 
     525                 :          67 :               prepared.deliver();
     526                 :          69 :             } catch (const detail::ReceiveOverflow& overflow) {
     527                 :           2 :               receive->receive.scope->overflow(data_span.size(), overflow.reason);
     528                 :             : 
     529                 :             :               {
     530                 :           2 :                 std::unique_lock<std::shared_mutex> lock(mutex_);
     531                 :           2 :                 receive->data.clear();
     532                 :           2 :                 receive->messages.clear();
     533                 :           2 :                 receive->receive.reset(framer_to_push.get());
     534                 :           2 :               }
     535   [ +  -  +  - ]:           2 :               if (auto native = std::dynamic_pointer_cast<transport::UdsServer>(server_)) native->fail_receive(id);
     536                 :             : 
     537                 :           2 :             } catch (const std::bad_alloc&) {
     538                 :           0 :               receive->receive.scope->overflow(data_span.size(), ReceiveOverflowReason::AllocationFailure);
     539                 :             : 
     540                 :             :               {
     541                 :           0 :                 std::unique_lock<std::shared_mutex> lock(mutex_);
     542                 :           0 :                 receive->data.clear();
     543                 :           0 :                 receive->messages.clear();
     544                 :           0 :                 receive->receive.reset(framer_to_push.get());
     545                 :           0 :               }
     546   [ -  -  -  - ]:           0 :               if (auto native = std::dynamic_pointer_cast<transport::UdsServer>(server_)) native->fail_receive(id);
     547                 :           0 :             }
     548                 :          69 :           });
     549                 :         263 :       transport_server->on_multi_disconnect([this, generation, weak_impl, weak_alive](ClientId id) {
     550                 :         102 :         auto impl_keepalive = weak_impl.lock();
     551         [ -  + ]:         102 :         if (!impl_keepalive) return;
     552                 :         102 :         auto alive = weak_alive.lock();
     553         [ -  + ]:         102 :         if (!alive) return;
     554                 :         102 :         auto lease = callback_gate_.enter(generation);
     555         [ -  + ]:         102 :         if (!lease.admitted()) return;
     556                 :             : 
     557                 :         102 :         std::shared_ptr<detail::SessionBatch> pending;
     558                 :             :         {
     559                 :         102 :           std::shared_lock<std::shared_mutex> lock(mutex_);
     560                 :         102 :           auto it = batches_.find(id);
     561         [ +  + ]:         102 :           if (it != batches_.end()) pending = it->second;
     562                 :         102 :         }
     563         [ +  + ]:         102 :         if (!pending) return;
     564                 :         101 :         flush_batches(generation, id, pending);
     565                 :         101 :         auto after_flush = callback_gate_.enter(generation);
     566         [ -  + ]:         101 :         if (!after_flush.admitted()) return;
     567                 :         101 :         ConnectionHandler handler;
     568                 :             :         {
     569                 :         101 :           std::unique_lock<std::shared_mutex> lock(mutex_);
     570                 :         101 :           batches_.erase(id);
     571                 :         101 :           framers_.erase(id);
     572                 :         101 :           handler = client_disconnect_handler_;
     573                 :         101 :         }
     574                 :         101 :         detail::invoke_user_callback("uds_server", "on_disconnect", handler, ConnectionContext(id));
     575                 :         105 :       });
     576                 :             : 
     577                 :         263 :       transport_server->on_backpressure([this, generation, weak_impl, weak_alive](size_t queued) {
     578                 :         164 :         auto impl_keepalive = weak_impl.lock();
     579         [ -  + ]:         164 :         if (!impl_keepalive) return;
     580                 :         164 :         bp_cv_.notify_all();
     581                 :         164 :         auto alive = weak_alive.lock();
     582         [ -  + ]:         164 :         if (!alive) return;
     583                 :         164 :         auto lease = callback_gate_.enter(generation);
     584         [ +  + ]:         164 :         if (!lease.admitted()) return;
     585                 :         163 :         std::function<void(size_t)> handler;
     586                 :             :         {
     587                 :         163 :           std::shared_lock<std::shared_mutex> lock(mutex_);
     588                 :         163 :           handler = on_backpressure_;
     589                 :         163 :         }
     590                 :         163 :         detail::invoke_user_callback("uds_server", "on_backpressure", handler, queued);
     591                 :         166 :       });
     592                 :             :     }
     593                 :             : 
     594                 :         270 :     server_->on_state([this, generation, weak_impl, weak_alive](base::LinkState state) {
     595                 :         176 :       auto impl_keepalive = weak_impl.lock();
     596         [ -  + ]:         176 :       if (!impl_keepalive) return;
     597                 :         176 :       auto alive = weak_alive.lock();
     598         [ +  + ]:         176 :       if (!alive) return;
     599                 :         175 :       auto lease = callback_gate_.enter(generation);
     600         [ +  + ]:         175 :       if (!lease.admitted()) return;
     601                 :             : 
     602         [ +  + ]:         174 :       if (state == base::LinkState::Listening) {
     603                 :         170 :         is_listening_.store(true);
     604                 :         170 :         std::unique_lock<std::shared_mutex> lock(mutex_);
     605                 :         170 :         terminal_error_reported_ = false;
     606                 :         170 :         fulfill_all_locked(true);
     607   [ -  +  -  -  :         174 :       } else if (state == base::LinkState::Error || state == base::LinkState::Closed ||
                   -  - ]
     608                 :             :                  state == base::LinkState::Idle) {
     609                 :           4 :         ErrorHandler handler;
     610                 :           4 :         is_listening_.store(false);
     611                 :             :         {
     612                 :           4 :           std::unique_lock<std::shared_mutex> lock(mutex_);
     613                 :           4 :           fulfill_all_locked(false);
     614   [ +  -  +  - ]:           4 :           if (state == base::LinkState::Error && !terminal_error_reported_) {
     615                 :           4 :             terminal_error_reported_ = true;
     616                 :           4 :             handler = error_handler_;
     617                 :             :           }
     618                 :           4 :         }
     619         [ +  - ]:           4 :         detail::invoke_user_callback("uds_server", "on_error", handler,
     620   [ +  -  +  -  :           8 :                                      server_ ? detail::build_error_context(*server_, "Server error")
                   -  - ]
     621                 :             :                                              : ErrorContext(ErrorCode::IoError, "Server error"));
     622                 :           4 :       }
     623                 :         179 :     });
     624                 :         270 :   }
     625                 :             : 
     626                 :        1974 :   RuntimeStats stats() const {
     627                 :        1974 :     std::shared_lock<std::shared_mutex> lock(mutex_);
     628   [ +  -  +  - ]:        3948 :     return server_ ? server_->stats() : RuntimeStats{};
     629                 :        1974 :   }
     630                 :             : 
     631                 :           3 :   void reset_stats() {
     632                 :           3 :     std::shared_lock<std::shared_mutex> lock(mutex_);
     633                 :           3 :     receive_budget_->reset_stats();
     634   [ +  -  +  - ]:           3 :     if (server_) server_->reset_stats();
     635                 :           3 :   }
     636                 :             : 
     637                 :          40 :   void attach_framer_callback(ClientId id) {
     638                 :          40 :     auto it = framers_.find(id);
     639         [ -  + ]:          40 :     if (it == framers_.end()) return;
     640                 :             : 
     641                 :          40 :     const auto generation = callback_generation_.load();
     642                 :          40 :     it->second->on_message([this, id, generation](memory::ConstByteSpan msg) {
     643                 :          23 :       auto message_lease = callback_gate_.enter(generation);
     644         [ +  + ]:          23 :       if (!message_lease.admitted()) return;
     645                 :          22 :       auto prepared = detail::take_prepared_message(id, msg);
     646                 :             :       // #441: snapshot under a shared_lock (pure read), build the copy
     647                 :             :       // before taking the exclusive lock for queue mutation.
     648                 :             :       bool batch_mode;
     649                 :          22 :       interface::SharedCallback<MessageHandler> handler;
     650                 :             :       {
     651                 :          22 :         std::shared_lock<std::shared_mutex> lock(mutex_);
     652                 :          22 :         batch_mode = static_cast<bool>(on_message_batch_);
     653                 :          22 :         handler = on_message_;
     654                 :          22 :       }
     655                 :             : 
     656         [ +  + ]:          22 :       if (batch_mode) {
     657                 :          12 :         std::shared_ptr<detail::SessionBatch> receive;
     658                 :             :         {
     659                 :          12 :           std::shared_lock<std::shared_mutex> lock(mutex_);
     660                 :          12 :           auto state = batches_.find(id);
     661         [ -  + ]:          12 :           if (state == batches_.end()) return;
     662                 :          12 :           receive = state->second;
     663                 :          12 :         }
     664   [ +  -  -  - ]:          12 :         auto ctx = prepared ? std::move(*prepared) : detail::retain_received(receive->receive.scope, id, msg);
     665                 :          12 :         interface::SharedCallback<BatchMessageHandler> flush_handler;
     666                 :          12 :         detail::ReceiveBatch batch;
     667                 :             :         {
     668                 :          12 :           std::unique_lock<std::shared_mutex> lock(mutex_);
     669                 :          12 :           auto state = batches_.find(id);
     670   [ +  -  -  +  :          12 :           if (state == batches_.end() || callback_generation_ != generation) return;
                   -  + ]
     671                 :          12 :           state->second->messages.emplace_back(std::move(ctx));
     672   [ +  -  +  + ]:          12 :           if (batches_.at(id)->messages.size() >= max_batch_size_) {
     673                 :           4 :             flush_handler = on_message_batch_;
     674                 :           4 :             batch = std::move(batches_.at(id)->messages);
     675                 :           4 :             batches_.at(id)->messages.clear();
     676   [ +  -  +  - ]:           8 :           } else if (batches_.at(id)->messages.size() == 1) {
     677                 :           8 :             schedule_batch_timer(generation, id);
     678                 :             :           }
     679                 :          12 :         }
     680                 :          12 :         detail::invoke_user_callback("uds_server", "on_message_batch", flush_handler, batch);
     681                 :          12 :         return;
     682                 :          12 :       }
     683                 :             : 
     684                 :          10 :       detail::invoke_user_callback("uds_server", "on_message", handler, MessageContext(id, msg));
     685                 :          47 :     });
     686                 :             :   }
     687                 :             : };
     688                 :             : 
     689                 :          63 : UdsServer::UdsServer(const std::string& socket_path) : impl_(std::make_shared<Impl>(socket_path)) {
     690                 :          63 :   config::detail::require(util::InputValidator::is_valid_uds_path(socket_path), "invalid UDS path");
     691                 :          64 : }
     692                 :             : 
     693                 :           8 : UdsServer::UdsServer(const std::string& socket_path, std::shared_ptr<boost::asio::io_context> external_ioc)
     694                 :           8 :     : impl_(std::make_shared<Impl>(socket_path, std::move(external_ioc))) {
     695                 :           8 :   config::detail::require(util::InputValidator::is_valid_uds_path(socket_path), "invalid UDS path");
     696                 :           8 : }
     697                 :             : 
     698                 :          99 : UdsServer::UdsServer(std::shared_ptr<interface::Channel> channel) : impl_(std::make_shared<Impl>(std::move(channel))) {
     699                 :          99 :   impl_->setup_internal_handlers();
     700                 :          99 : }
     701                 :             : 
     702                 :             : // Stop on the destroying thread first. A callback in flight holds the Impl
     703                 :             : // alive, and if its reference were the last one, ~Impl would run on the io
     704                 :             : // thread, where the shutdown cannot join that thread (#613).
     705                 :         210 : UdsServer::~UdsServer() {
     706         [ -  + ]:         169 :   if (!impl_) return;
     707                 :             :   try {
     708                 :         169 :     impl_->stop();
     709                 :           0 :   } catch (...) {
     710                 :           0 :   }
     711                 :         210 : }
     712                 :             : 
     713                 :           0 : UdsServer::UdsServer(UdsServer&&) noexcept = default;
     714                 :             : // The previous Impl is handed to a temporary, whose destructor stops it here.
     715                 :           0 : UdsServer& UdsServer::operator=(UdsServer&& other) noexcept {
     716         [ #  # ]:           0 :   if (this != &other) {
     717                 :           0 :     UdsServer previous(std::move(*this));
     718                 :           0 :     impl_ = std::move(other.impl_);
     719                 :           0 :   }
     720                 :           0 :   return *this;
     721                 :             : }
     722                 :             : 
     723                 :           9 : UdsServer& UdsServer::receive_limits(ReceiveLimits limits) {
     724                 :           9 :   limits.validate();
     725                 :           8 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     726   [ +  +  +  -  :          22 :   if (impl_->started_ || impl_->stop_callers_.load() || (impl_->stop_requested_ && impl_->alive_marker_) ||
          +  +  +  -  -  
                +  +  + ]
     727                 :           7 :       detail::in_data_callback())
     728                 :           1 :     throw std::logic_error("receive limits require completed stop");
     729                 :           7 :   auto budget = std::make_shared<detail::ReceiveBudget>(limits);
     730                 :           7 :   impl_->receive_limits_ = limits;
     731                 :           7 :   impl_->receive_budget_ = std::move(budget);
     732                 :           7 :   return *this;
     733                 :           8 : }
     734                 :          32 : ReceiveMemoryStats UdsServer::receive_stats() const {
     735                 :          32 :   std::shared_lock<std::shared_mutex> lock(impl_->mutex_);
     736                 :          64 :   return impl_->receive_budget_->stats();
     737                 :          32 : }
     738                 :             : 
     739                 :         173 : std::future<bool> UdsServer::start() { return impl_->start(); }
     740                 :             : 
     741                 :         244 : void UdsServer::stop() { impl_->stop(); }
     742                 :             : 
     743                 :          55 : bool UdsServer::listening() const { return impl_->is_listening_.load(); }
     744                 :        1974 : RuntimeStats UdsServer::stats() const { return impl_->stats(); }
     745                 :           3 : void UdsServer::reset_stats() { impl_->reset_stats(); }
     746                 :             : 
     747                 :          78 : FanoutResult UdsServer::broadcast(std::string_view data) { return impl_->broadcast(data); }
     748                 :          30 : FanoutResult UdsServer::try_broadcast(std::string_view data) { return impl_->try_broadcast(data); }
     749                 :         166 : SendResult UdsServer::send_to(ClientId client_id, std::string_view data) { return impl_->send_to(client_id, data); }
     750                 :          76 : SendResult UdsServer::try_send_to(ClientId client_id, std::string_view data) {
     751                 :          76 :   return impl_->try_send_to(client_id, data);
     752                 :             : }
     753                 :             : 
     754                 :         101 : SendResult UdsServer::send_to_blocking(ClientId client_id, std::string_view data) {
     755                 :         101 :   return impl_->send_to_blocking(client_id, data);
     756                 :             : }
     757                 :             : 
     758                 :           6 : FanoutResult UdsServer::broadcast_line(std::string_view line) { return impl_->try_broadcast(line, true); }
     759                 :          82 : SendResult UdsServer::send_to_line(ClientId client_id, std::string_view line) {
     760                 :         246 :   return send_to(client_id, std::string(line) + "\n");
     761                 :             : }
     762                 :          14 : FanoutResult UdsServer::try_broadcast_line(std::string_view line) { return impl_->try_broadcast(line, true); }
     763                 :          19 : SendResult UdsServer::try_send_to_line(ClientId client_id, std::string_view line) {
     764                 :          57 :   return try_send_to(client_id, std::string(line) + "\n");
     765                 :             : }
     766                 :             : 
     767                 :         136 : UdsServer& UdsServer::on_connect(ConnectionHandler handler) {
     768                 :         136 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     769                 :         136 :   impl_->client_connect_handler_ = std::move(handler);
     770                 :         136 :   return *this;
     771                 :         136 : }
     772                 :             : 
     773                 :          13 : UdsServer& UdsServer::on_disconnect(ConnectionHandler handler) {
     774                 :          13 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     775                 :          13 :   impl_->client_disconnect_handler_ = std::move(handler);
     776                 :          13 :   return *this;
     777                 :          13 : }
     778                 :             : 
     779                 :          20 : UdsServer& UdsServer::on_data(MessageHandler handler) {
     780                 :          20 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     781                 :          20 :   impl_->data_handler_ = interface::share_callback(std::move(handler));
     782                 :          20 :   return *this;
     783                 :          20 : }
     784                 :             : 
     785                 :          10 : UdsServer& UdsServer::on_data_batch(BatchMessageHandler handler) {
     786                 :          10 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     787                 :          10 :   impl_->data_batch_handler_ = interface::share_callback(std::move(handler));
     788                 :          10 :   return *this;
     789                 :          10 : }
     790                 :             : 
     791                 :          21 : UdsServer& UdsServer::on_error(ErrorHandler handler) {
     792                 :          21 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     793                 :          21 :   impl_->error_handler_ = std::move(handler);
     794                 :          21 :   return *this;
     795                 :          21 : }
     796                 :             : 
     797                 :          32 : UdsServer& UdsServer::on_backpressure(std::function<void(size_t)> handler) {
     798                 :          32 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     799                 :          32 :   impl_->on_backpressure_ = std::move(handler);
     800                 :          32 :   return *this;
     801                 :          32 : }
     802                 :             : 
     803                 :          29 : UdsServer& UdsServer::framer(FramerFactory factory) {
     804                 :          29 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     805                 :          29 :   impl_->framer_factory_ = std::move(factory);
     806                 :          29 :   return *this;
     807                 :          29 : }
     808                 :             : 
     809                 :          12 : UdsServer& UdsServer::on_message(MessageHandler handler) {
     810                 :          12 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     811                 :          12 :   impl_->on_message_ = interface::share_callback(std::move(handler));
     812                 :          12 :   return *this;
     813                 :          12 : }
     814                 :             : 
     815                 :           8 : UdsServer& UdsServer::on_message_batch(BatchMessageHandler handler) {
     816                 :           8 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     817                 :           8 :   impl_->on_message_batch_ = interface::share_callback(std::move(handler));
     818                 :           8 :   return *this;
     819                 :           8 : }
     820                 :             : 
     821                 :         100 : size_t UdsServer::client_count() const {
     822                 :         100 :   std::shared_lock<std::shared_mutex> lock(impl_->mutex_);
     823                 :         100 :   auto ts = std::dynamic_pointer_cast<transport::UdsServer>(impl_->server_);
     824   [ +  +  +  - ]:         200 :   return ts ? ts->client_count() : 0;
     825                 :         100 : }
     826                 :             : 
     827                 :           6 : std::vector<ClientId> UdsServer::connected_clients() const {
     828                 :           6 :   std::shared_lock<std::shared_mutex> lock(impl_->mutex_);
     829                 :           6 :   auto ts = std::dynamic_pointer_cast<transport::UdsServer>(impl_->server_);
     830   [ +  +  +  - ]:          12 :   return ts ? ts->connected_clients() : std::vector<ClientId>{};
     831                 :           6 : }
     832                 :             : 
     833                 :          28 : std::optional<RuntimeStats> UdsServer::client_stats(ClientId client_id) const {
     834                 :          28 :   std::shared_lock<std::shared_mutex> lock(impl_->mutex_);
     835                 :          28 :   auto ts = std::dynamic_pointer_cast<transport::UdsServer>(impl_->server_);
     836   [ +  -  +  - ]:          56 :   return ts ? ts->client_stats(client_id) : std::nullopt;
     837                 :          28 : }
     838                 :             : 
     839                 :           1 : UdsServer& UdsServer::auto_start(bool manage) {
     840                 :           1 :   impl_->auto_start_.store(manage);
     841   [ +  -  +  -  :           1 :   if (impl_->auto_start_.load() && !impl_->started_.load()) {
                   +  - ]
     842                 :           1 :     start();
     843                 :             :   }
     844                 :           1 :   return *this;
     845                 :             : }
     846                 :             : 
     847                 :           5 : UdsServer& UdsServer::idle_timeout(std::chrono::milliseconds timeout) {
     848                 :           5 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     849                 :           5 :   impl_->require_stopped_config();
     850                 :           4 :   config::detail::duration(timeout, base::constants::MIN_IDLE_TIMEOUT_MS, base::constants::MAX_IDLE_TIMEOUT_MS, true,
     851                 :             :                            "invalid idle_timeout");
     852                 :           4 :   impl_->idle_timeout_ms_.store(static_cast<int>(timeout.count()));
     853                 :           4 :   return *this;
     854                 :           5 : }
     855                 :             : 
     856                 :           7 : UdsServer& UdsServer::max_clients(size_t max) {
     857                 :           7 :   config::detail::range(max, 0, base::constants::MAX_MAX_CONNECTIONS, "invalid client limit");
     858                 :           6 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     859                 :           6 :   impl_->max_clients_.store(max);
     860         [ -  + ]:           6 :   if (max == 0) {
     861                 :           0 :     impl_->client_limit_enabled_.store(false);
     862                 :             :   } else {
     863                 :           6 :     impl_->client_limit_enabled_.store(true);
     864                 :             :   }
     865                 :           6 :   auto ts = std::dynamic_pointer_cast<transport::UdsServer>(impl_->server_);
     866   [ +  +  +  - ]:           6 :   if (ts) ts->set_client_limit(max);
     867                 :           6 :   return *this;
     868                 :           6 : }
     869                 :             : 
     870                 :           0 : UdsServer& UdsServer::socket_permissions(int mode) {
     871                 :           0 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     872                 :           0 :   impl_->require_stopped_config();
     873   [ #  #  #  #  :           0 :   config::detail::require(mode == -1 || (mode >= 0 && mode <= 0777), "invalid socket permissions");
             #  #  #  # ]
     874                 :           0 :   impl_->socket_permissions_.store(mode);
     875                 :           0 :   return *this;
     876                 :           0 : }
     877                 :             : 
     878                 :          67 : UdsServer& UdsServer::backpressure_threshold(size_t threshold) {
     879                 :          67 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     880                 :          67 :   impl_->require_stopped_config();
     881                 :          66 :   config::detail::range(threshold, base::constants::MIN_BACKPRESSURE_THRESHOLD,
     882                 :             :                         base::constants::MAX_BACKPRESSURE_THRESHOLD, "invalid backpressure_threshold");
     883                 :          66 :   impl_->backpressure_threshold_.store(threshold);
     884                 :          66 :   return *this;
     885                 :          67 : }
     886                 :             : 
     887                 :           0 : UdsServer& UdsServer::read_buffer_size(size_t bytes) {
     888                 :           0 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     889                 :           0 :   impl_->require_stopped_config();
     890                 :           0 :   config::detail::range(bytes, base::constants::MIN_READ_BUFFER_SIZE, base::constants::MAX_READ_BUFFER_SIZE,
     891                 :             :                         "invalid read_buffer_size");
     892                 :           0 :   impl_->read_buffer_size_.store(bytes);
     893                 :           0 :   return *this;
     894                 :           0 : }
     895                 :             : 
     896                 :          44 : UdsServer& UdsServer::backpressure_strategy(base::constants::BackpressureStrategy strategy) {
     897                 :          44 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     898                 :          44 :   impl_->require_stopped_config();
     899                 :          43 :   config::detail::strategy(strategy);
     900                 :          43 :   impl_->backpressure_strategy_.store(strategy);
     901                 :          43 :   return *this;
     902                 :          44 : }
     903                 :             : 
     904                 :          10 : size_t UdsServer::backpressure_threshold() const { return impl_->backpressure_threshold_.load(); }
     905                 :             : 
     906                 :           3 : base::constants::BackpressureStrategy UdsServer::backpressure_strategy() const {
     907                 :           3 :   return impl_->backpressure_strategy_.load();
     908                 :             : }
     909                 :             : 
     910                 :           7 : UdsServer& UdsServer::manage_external_context(bool manage) {
     911                 :           7 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     912                 :           7 :   impl_->require_stopped_config();
     913                 :           6 :   impl_->manage_external_context_.store(manage);
     914                 :           6 :   return *this;
     915                 :           7 : }
     916                 :             : 
     917                 :          23 : UdsServer& UdsServer::batch_size(size_t size) {
     918         [ +  - ]:          23 :   config::detail::require(size > 0, "batch size must be positive");
     919                 :          23 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     920                 :          23 :   impl_->max_batch_size_ = size;
     921                 :          23 :   return *this;
     922                 :          23 : }
     923                 :             : 
     924                 :          23 : UdsServer& UdsServer::batch_latency(std::chrono::milliseconds latency) {
     925                 :          23 :   config::detail::duration(latency, 0, std::numeric_limits<int>::max(), true, "invalid batch latency");
     926                 :          23 :   std::unique_lock<std::shared_mutex> lock(impl_->mutex_);
     927                 :          23 :   impl_->max_batch_latency_ = latency;
     928                 :          23 :   return *this;
     929                 :          23 : }
     930                 :             : 
     931                 :             : }  // namespace wrapper
     932                 :             : }  // namespace wirestead
        

Generated by: LCOV version 2.0-1