最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Client theoretically capable of sending multiple requests actually incapable of doing that, probably because it fails to m

programmeradmin3浏览0评论

I am dealing with code for a tcp client making asynchronous http requests.

The code uses boost::asio:

#include <boost/asio.hpp>
#include <future>
//...#include <..etc etc ..>

using ResolveResult = boost::asio::ip::tcp::resolver::results_type;
using Endpoint = boost::asio::ip::tcp::endpoint;

Quite canonically, the program defines a struct Request which handles the connection to server:

struct Request {
  // constructor
  explicit Request(boost::asio::io_context& io_context, std::string host)
      : resolver{ io_context }
      , socket{ io_context }
      , host{ std::move(host) } {/*...*/}

  // declarations of methods
  void resolution_handler(boost::system::error_code ec, const ResolveResult& results);
  void connection_handler(boost::system::error_code ec, const Endpoint& endpoint);
  size_t write_handler(boost::system::error_code ec, size_t transferred);
  void read_handler(boost::system::error_code ec, size_t transferred);
  const std::string& get_response() const noexcept;
  const std::string& get_request() const noexcept;

private:
  boost::asio::ip::tcp::resolver resolver;
  boost::asio::ip::tcp::socket socket;
  std::string request, response;
  const std::string host;
};

main() generates Requests to example and handles dealing with asynchronous tasks:

int main() {
  size_t n_requests{ 1 }, n_threads{ 1 };
  boost::asio::io_context io_context{ static_cast<int>(n_threads) };

  std::vector<Request> requests;
  std::generate_n(std::back_inserter(requests), n_requests, [&io_context] {
    return Request{ io_context, "example" };
  });

  std::cout << "debug:\n " << requests[0].get_request() << std::endl;

  std::vector<std::future<void>> futures;

  for (auto i=0; i!=n_requests; ++i) {
    futures.emplace_back(std::async(std::launch::async, [&io_context] { io_context.run(); }));
  }

  for(auto& future : futures)
    future.get();
  for(const auto& request : requests)
    std::cout << "Response had length " << request.get_response().size();
}

Here is the Godbolt link to the whole program.

As it can be seen, the request generates error output on godbolt. On my station, sometimes the request simply hangs, sometimes it immediately exits with a Segmentation fault error.

This happens no matter the number > 0 of threads (n_threads) or requests (n_requests) generated.

I suspect the problem lying with the fact that, going by the book,std::futures cannot be copied, only moved.

Inside the code, construction of the std::vector<std::future> container for these futures with std::vector::emplace_back() might fail to abide to that constraint.

I am dealing with code for a tcp client making asynchronous http requests.

The code uses boost::asio:

#include <boost/asio.hpp>
#include <future>
//...#include <..etc etc ..>

using ResolveResult = boost::asio::ip::tcp::resolver::results_type;
using Endpoint = boost::asio::ip::tcp::endpoint;

Quite canonically, the program defines a struct Request which handles the connection to server:

struct Request {
  // constructor
  explicit Request(boost::asio::io_context& io_context, std::string host)
      : resolver{ io_context }
      , socket{ io_context }
      , host{ std::move(host) } {/*...*/}

  // declarations of methods
  void resolution_handler(boost::system::error_code ec, const ResolveResult& results);
  void connection_handler(boost::system::error_code ec, const Endpoint& endpoint);
  size_t write_handler(boost::system::error_code ec, size_t transferred);
  void read_handler(boost::system::error_code ec, size_t transferred);
  const std::string& get_response() const noexcept;
  const std::string& get_request() const noexcept;

private:
  boost::asio::ip::tcp::resolver resolver;
  boost::asio::ip::tcp::socket socket;
  std::string request, response;
  const std::string host;
};

main() generates Requests to example and handles dealing with asynchronous tasks:

int main() {
  size_t n_requests{ 1 }, n_threads{ 1 };
  boost::asio::io_context io_context{ static_cast<int>(n_threads) };

  std::vector<Request> requests;
  std::generate_n(std::back_inserter(requests), n_requests, [&io_context] {
    return Request{ io_context, "example" };
  });

  std::cout << "debug:\n " << requests[0].get_request() << std::endl;

  std::vector<std::future<void>> futures;

  for (auto i=0; i!=n_requests; ++i) {
    futures.emplace_back(std::async(std::launch::async, [&io_context] { io_context.run(); }));
  }

  for(auto& future : futures)
    future.get();
  for(const auto& request : requests)
    std::cout << "Response had length " << request.get_response().size();
}

Here is the Godbolt link to the whole program.

As it can be seen, the request generates error output on godbolt. On my station, sometimes the request simply hangs, sometimes it immediately exits with a Segmentation fault error.

This happens no matter the number > 0 of threads (n_threads) or requests (n_requests) generated.

I suspect the problem lying with the fact that, going by the book,std::futures cannot be copied, only moved.

Inside the code, construction of the std::vector<std::future> container for these futures with std::vector::emplace_back() might fail to abide to that constraint.

Share Improve this question asked Mar 18 at 18:04 GiogreGiogre 1,6149 silver badges19 bronze badges 11
  • Side note : Are you sure you mean this : for (auto i=0; i!=n_requests; ++i)? And not i<n_requests? – Pepijn Kramer Commented Mar 18 at 18:21
  • @PepijnKramer I thought that for all practical purposes, != and < achieve the same effect here – Giogre Commented Mar 18 at 18:24
  • Take one step back.. and first check if single threaded is working, somehow your solution "feels wrong" ... but it also has been too long ago since I used boost::asio to put my finger on it... Sorry I can't be more specific. – Pepijn Kramer Commented Mar 18 at 18:24
  • Probably != is ok, but not idiomatic. So it triggers a... what's going on here moment for me :) – Pepijn Kramer Commented Mar 18 at 18:26
  • 1 At least the godbolt clearly shows UB in the output, and sanitize=address,undefined confirms. I'll give it another look when I come back in 3 hours – sehe Commented Mar 18 at 18:36
 |  Show 6 more comments

1 Answer 1

Reset to default 4

Your Request isn't properly move-constructible which is required for

std::generate_n(std::back_inserter(requests), n_requests, [&io_context] {
    return Request{io_context, "example"};
});

to work. When the Request is created, it does

resolver.async_resolve(
    this->host, "http",
    [this](boost::system::error_code ec, const ResolveResult& results) {
//   ^^^^
        resolution_handler(ec, results);
    });

which captures the temporary Request. This temporary Request is destroyed when the push_back by the back_inserter into the vector is done. When the resolution_handler is later called on this Request, it will be gone (the this pointer is dangling) and you have undefined behavior.

A temporary fix to this is to not move Requests into the vector<Request> but to emplace_back them:

std::vector<Request> requests;
requests.reserve(n_requests);
for(size_t i = 0; i != n_requests; ++i) {
    requests.emplace_back(io_context, "example");
}

I would also delete the move constructor to not accidentally run into this problem again until you've figured out how to make Request properly moveable (if that's even wanted). If you do delete the move constructor, you can store Requests in a std::list<Request> instead:

std::list<Request> requests;
for (size_t i = 0; i != n_requests; ++i) {
    requests.emplace_back(io_context, "example");
}
std::cout << "debug:\n" << requests.front().get_request() << std::endl;

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论