Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-17 09:00:18

0001 /* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
0002  *
0003  * Distributed under the Boost Software License, Version 1.0. (See
0004  * accompanying file LICENSE.txt)
0005  */
0006 
0007 #include <boost/redis/detail/multiplexer.hpp>
0008 #include <boost/redis/ignore.hpp>
0009 #include <boost/redis/request.hpp>
0010 
0011 #include <boost/asio/error.hpp>
0012 #include <boost/assert.hpp>
0013 
0014 #include <cstddef>
0015 #include <memory>
0016 
0017 namespace boost::redis::detail {
0018 
0019 multiplexer::elem::elem(request const& req, any_adapter adapter)
0020 : req_{&req}
0021 , adapter_{std::move(adapter)}
0022 , remaining_responses_{req.get_expected_responses()}
0023 , status_{status::waiting}
0024 , ec_{}
0025 , read_size_{0}
0026 { }
0027 
0028 auto multiplexer::elem::notify_error(system::error_code ec) noexcept -> void
0029 {
0030    if (!ec_) {
0031       ec_ = ec;
0032    }
0033 
0034    notify_done();
0035 }
0036 
0037 auto multiplexer::elem::commit_response(std::size_t read_size) -> void
0038 {
0039    read_size_ += read_size;
0040    --remaining_responses_;
0041 }
0042 
0043 void multiplexer::elem::mark_abandoned()
0044 {
0045    req_ = nullptr;
0046    adapter_ = any_adapter();  // A default-constructed any_adapter ignores all nodes
0047    set_done_callback([] { });
0048 }
0049 
0050 multiplexer::multiplexer()
0051 {
0052    // Reserve some memory to avoid excessive memory allocations in
0053    // the first reads.
0054    read_buffer_.reserve(4096u);
0055 }
0056 
0057 void multiplexer::cancel(std::shared_ptr<elem> const& ptr)
0058 {
0059    if (ptr->is_waiting()) {
0060       // We can safely remove it from the queue, since it hasn't been sent yet
0061       reqs_.erase(std::remove(std::begin(reqs_), std::end(reqs_), ptr));
0062    } else {
0063       // Removing the request would cause trouble when the response arrived.
0064       // Mark it as abandoned, so the response is discarded when it arrives
0065       ptr->mark_abandoned();
0066    }
0067 }
0068 
0069 bool multiplexer::commit_write(std::size_t bytes_written)
0070 {
0071    BOOST_ASSERT(!cancel_run_called_);
0072    BOOST_ASSERT(bytes_written + write_offset_ <= write_buffer_.size());
0073 
0074    usage_.bytes_sent += bytes_written;
0075    write_offset_ += bytes_written;
0076 
0077    // Are there still more bytes to write?
0078    if (write_offset_ < write_buffer_.size())
0079       return false;
0080 
0081    // We've written all the bytes in the write buffer.
0082    write_buffer_.clear();
0083 
0084    // There is small optimization possible here: traverse only the
0085    // partition of unwritten requests instead of them all.
0086    std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
0087       BOOST_ASSERT_MSG(ptr != nullptr, "Expects non-null pointer.");
0088       if (ptr->is_staged()) {
0089          ptr->mark_written();
0090       }
0091    });
0092 
0093    release_push_requests();
0094 
0095    return true;
0096 }
0097 
0098 void multiplexer::add(std::shared_ptr<elem> const& info)
0099 {
0100    BOOST_ASSERT(!info->is_abandoned());
0101 
0102    reqs_.push_back(info);
0103 
0104    if (request_access::has_priority(info->get_request())) {
0105       auto rend = std::partition_point(std::rbegin(reqs_), std::rend(reqs_), [](auto const& e) {
0106          return e->is_waiting();
0107       });
0108 
0109       std::rotate(std::rbegin(reqs_), std::rbegin(reqs_) + 1, rend);
0110    }
0111 }
0112 
0113 consume_result multiplexer::consume_impl(system::error_code& ec)
0114 {
0115    // We arrive here in two states:
0116    //
0117    //    1. While we are parsing a message. In this case we
0118    //       don't want to determine the type of the message in the
0119    //       buffer (i.e. response vs push) but leave it untouched
0120    //       until the parsing of a complete message ends.
0121    //
0122    //    2. On a new message, in which case we have to determine
0123    //       whether the next message is a push or a response.
0124    //
0125 
0126    auto const data = read_buffer_.get_commited();
0127    BOOST_ASSERT(!data.empty());
0128 
0129    if (!on_push_)  // Prepare for new message.
0130       on_push_ = is_next_push(data);
0131 
0132    if (on_push_) {
0133       if (!resp3::parse(parser_, data, receive_adapter_, ec))
0134          return consume_result::needs_more;
0135 
0136       return consume_result::got_push;
0137    }
0138 
0139    BOOST_ASSERT(!reqs_.empty());
0140    BOOST_ASSERT(reqs_.front() != nullptr);
0141    BOOST_ASSERT(reqs_.front()->get_remaining_responses() != 0);
0142    BOOST_ASSERT(!reqs_.front()->is_waiting());
0143 
0144    if (!resp3::parse(parser_, data, reqs_.front()->get_adapter(), ec))
0145       return consume_result::needs_more;
0146 
0147    if (ec) {
0148       reqs_.front()->notify_error(ec);
0149       reqs_.pop_front();
0150       return consume_result::got_response;
0151    }
0152 
0153    reqs_.front()->commit_response(parser_.get_consumed());
0154    if (reqs_.front()->get_remaining_responses() == 0) {
0155       // Done with this request.
0156       reqs_.front()->notify_done();
0157       reqs_.pop_front();
0158    }
0159 
0160    return consume_result::got_response;
0161 }
0162 
0163 std::pair<consume_result, std::size_t> multiplexer::consume(system::error_code& ec)
0164 {
0165    BOOST_ASSERT(!cancel_run_called_);
0166 
0167    auto const ret = consume_impl(ec);
0168    auto const consumed = parser_.get_consumed();
0169    if (ec) {
0170       return std::make_pair(ret, consumed);
0171    }
0172 
0173    if (ret != consume_result::needs_more) {
0174       parser_.reset();
0175       auto const res = read_buffer_.consume(consumed);
0176       commit_usage(ret == consume_result::got_push, res);
0177       return std::make_pair(ret, res.consumed);
0178    }
0179 
0180    return std::make_pair(consume_result::needs_more, consumed);
0181 }
0182 
0183 auto multiplexer::prepare_read() noexcept -> system::error_code { return read_buffer_.prepare(); }
0184 
0185 auto multiplexer::get_prepared_read_buffer() noexcept -> read_buffer::span_type
0186 {
0187    return read_buffer_.get_prepared();
0188 }
0189 
0190 void multiplexer::commit_read(std::size_t bytes_read) { read_buffer_.commit(bytes_read); }
0191 
0192 auto multiplexer::get_read_buffer_size() const noexcept -> std::size_t
0193 {
0194    return read_buffer_.get_commited().size();
0195 }
0196 
0197 void multiplexer::reset()
0198 {
0199    read_buffer_.clear();
0200    write_buffer_.clear();
0201    write_offset_ = 0u;
0202    parser_.reset();
0203    on_push_ = false;
0204    cancel_run_called_ = false;
0205 }
0206 
0207 std::size_t multiplexer::prepare_write()
0208 {
0209    BOOST_ASSERT(!cancel_run_called_);
0210 
0211    // Coalesces the requests and marks them staged. After a
0212    // successful write staged requests will be marked as written.
0213    auto const point = std::partition_point(
0214       std::cbegin(reqs_),
0215       std::cend(reqs_),
0216       [](auto const& ri) {
0217          return !ri->is_waiting();
0218       });
0219 
0220    std::for_each(point, std::cend(reqs_), [this](const std::shared_ptr<elem>& ri) {
0221       // Stage the request.
0222       BOOST_ASSERT(!ri->is_abandoned());
0223       write_buffer_ += ri->get_request().payload();
0224       ri->mark_staged();
0225       usage_.commands_sent += ri->get_request().get_commands();
0226    });
0227 
0228    write_offset_ = 0u;
0229 
0230    auto const d = std::distance(point, std::cend(reqs_));
0231    return static_cast<std::size_t>(d);
0232 }
0233 
0234 std::size_t multiplexer::cancel_waiting()
0235 {
0236    auto f = [](auto const& ptr) {
0237       BOOST_ASSERT(ptr != nullptr);
0238       return !ptr->is_waiting();
0239    };
0240 
0241    auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), f);
0242 
0243    auto const ret = std::distance(point, std::end(reqs_));
0244 
0245    std::for_each(point, std::end(reqs_), [](auto const& ptr) {
0246       ptr->notify_error({asio::error::operation_aborted});
0247    });
0248 
0249    reqs_.erase(point, std::end(reqs_));
0250    return ret;
0251 }
0252 
0253 void multiplexer::cancel_on_conn_lost()
0254 {
0255    // Should only be called once per reconnection.
0256    // See https://github.com/boostorg/redis/issues/181
0257    BOOST_ASSERT(!cancel_run_called_);
0258    cancel_run_called_ = true;
0259 
0260    // Must return false if the request should be removed.
0261    auto cond = [](const std::shared_ptr<elem>& ptr) {
0262       BOOST_ASSERT(ptr != nullptr);
0263 
0264       // Abandoned requests only make sense because a response for them might arrive.
0265       // They should be discarded after the connection is lost
0266       if (ptr->is_abandoned())
0267          return false;
0268 
0269       if (ptr->is_waiting()) {
0270          return !ptr->get_request().get_config().cancel_on_connection_lost;
0271       } else {
0272          return !ptr->get_request().get_config().cancel_if_unresponded;
0273       }
0274    };
0275 
0276    auto point = std::stable_partition(std::begin(reqs_), std::end(reqs_), cond);
0277 
0278    std::for_each(point, std::end(reqs_), [](auto const& ptr) {
0279       ptr->notify_error({asio::error::operation_aborted});
0280    });
0281 
0282    reqs_.erase(point, std::end(reqs_));
0283 
0284    std::for_each(std::begin(reqs_), std::end(reqs_), [](auto const& ptr) {
0285       return ptr->mark_waiting();
0286    });
0287 }
0288 
0289 void multiplexer::commit_usage(bool is_push, read_buffer::consume_result res)
0290 {
0291    if (is_push) {
0292       usage_.pushes_received += 1;
0293       usage_.push_bytes_received += res.consumed;
0294       on_push_ = false;
0295    } else {
0296       usage_.responses_received += 1;
0297       usage_.response_bytes_received += res.consumed;
0298    }
0299 
0300    usage_.bytes_rotated += res.rotated;
0301 }
0302 
0303 bool multiplexer::is_next_push(std::string_view data) const noexcept
0304 {
0305    // Useful links to understand the heuristics below.
0306    //
0307    // - https://github.com/redis/redis/issues/11784
0308    // - https://github.com/redis/redis/issues/6426
0309    // - https://github.com/boostorg/redis/issues/170
0310 
0311    // Test if the message resp3 type is a push.
0312    BOOST_ASSERT(!data.empty());
0313    if (resp3::to_type(data.front()) == resp3::type::push)
0314       return true;
0315 
0316    // This is non-push type and the requests queue is empty. I have
0317    // noticed this is possible, for example with -MISCONF. I don't
0318    // know why they are not sent with a push type so we can
0319    // distinguish them from responses to commands. If we are lucky
0320    // enough to receive them when the command queue is empty they
0321    // can be treated as server pushes, otherwise it is impossible
0322    // to handle them properly
0323    if (reqs_.empty())
0324       return true;
0325 
0326    // The request does not expect any response but we got one. This
0327    // may happen if for example, subscribe with wrong syntax.
0328    if (reqs_.front()->get_remaining_responses() == 0)
0329       return true;
0330 
0331    // Added to deal with MONITOR and also to fix PR170 which
0332    // happens under load and on low-latency networks, where we
0333    // might start receiving responses before the write operation
0334    // completed and the request is still marked as staged and not
0335    // written.
0336    return reqs_.front()->is_waiting();
0337 }
0338 
0339 void multiplexer::release_push_requests()
0340 {
0341    auto point = std::stable_partition(
0342       std::begin(reqs_),
0343       std::end(reqs_),
0344       [](const std::shared_ptr<elem>& ptr) {
0345          return !(ptr->is_written() && ptr->get_remaining_responses() == 0u);
0346       });
0347 
0348    std::for_each(point, std::end(reqs_), [](auto const& ptr) {
0349       ptr->notify_done();
0350    });
0351 
0352    reqs_.erase(point, std::end(reqs_));
0353 }
0354 
0355 void multiplexer::set_receive_adapter(any_adapter adapter)
0356 {
0357    receive_adapter_ = std::move(adapter);
0358 }
0359 
0360 void multiplexer::set_config(config const& cfg)
0361 {
0362    read_buffer_.set_config({cfg.read_buffer_append_size, cfg.max_read_size});
0363 }
0364 
0365 auto make_elem(request const& req, any_adapter adapter) -> std::shared_ptr<multiplexer::elem>
0366 {
0367    return std::make_shared<multiplexer::elem>(req, std::move(adapter));
0368 }
0369 
0370 }  // namespace boost::redis::detail