io_context.run () возвращается слишком рано

Я делаю библиотеку api_client на основе boost-beast. Я использую составные операции для объединения http :: async_write () и http :: async_read () в api_client :: async_get ().

Проблема, с которой я столкнулся, заключается в том, что функция io_context.run () теперь ожидает завершения http :: async_read () и возврата до того, как будет вызван обратный вызов async_get ().

Что я делаю неправильно? Есть ли другие предложения по улучшению кода?

Я использую boost 1.74 на Mac

// api_client.h

#ifndef API_CLIENT_H
#define API_CLIENT_H

#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/asio/write.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>

namespace beast = boost::beast;          // from <boost/beast.hpp>
namespace http  = beast::http;           // from <boost/beast/http.hpp>
namespace net   = boost::asio;           // from <boost/asio.hpp>
using tcp       = boost::asio::ip::tcp;  // from <boost/asio/ip/tcp.hpp>

struct async_resolve_initiation
{
  template <typename CompletionHandler>
  void operator()(CompletionHandler &&completion_handler, tcp::resolver &resolver,
                  const std::string &host, const std::string &port) const
  {
    auto executor =
        boost::asio::get_associated_executor(completion_handler, resolver.get_executor());

    resolver.async_resolve(
        host, port,
        boost::asio::bind_executor(executor, std::forward<CompletionHandler>(completion_handler)));
  }
};

template <typename CompletionToken>
auto async_resolve_host(tcp::resolver &resolver, const std::string &host, const std::string &port,
                        CompletionToken &&token) ->
    typename boost::asio::async_result<typename std::decay<CompletionToken>::type,
                                       void(const boost::system::error_code &,
                                            const tcp::resolver::results_type &)>::return_type
{
  return boost::asio::async_initiate<CompletionToken, void(const boost::system::error_code &,
                                                           const tcp::resolver::results_type &)>(
      async_resolve_initiation(), token, std::ref(resolver), host, port);
}

struct async_connect_initiation
{
  template <typename CompletionHandler>
  void operator()(CompletionHandler &&completion_handler, beast::tcp_stream &stream,
                  const tcp::resolver::results_type &results) const
  {

    stream.expires_after(std::chrono::seconds(10));

    auto executor = boost::asio::get_associated_executor(completion_handler, stream.get_executor());

    stream.async_connect(results, std::forward<CompletionHandler>(completion_handler));
  }
};

template <typename CompletionToken>
auto async_connect_host(beast::tcp_stream &stream, const tcp::resolver::results_type &results,
                        CompletionToken &&token) ->
    typename boost::asio::async_result<
        typename std::decay<CompletionToken>::type,
        void(const boost::system::error_code &,
             const tcp::resolver::results_type::endpoint_type &)>::return_type
{
  return boost::asio::async_initiate<CompletionToken,
                                     void(const boost::system::error_code &,
                                          const tcp::resolver::results_type::endpoint_type &)>(
      async_connect_initiation(), token, std::ref(stream), results);
}

struct async_request
{
  beast::tcp_stream &                              stream_;
  std::unique_ptr<http::request<http::empty_body>> req_;
  enum
  {
    request_in_progress,
    waiting_for_response,
  } state_;
  beast::flat_buffer                                 buffer_;
  std::shared_ptr<http::response<http::string_body>> res_;

  async_request(beast::tcp_stream &stream, std::unique_ptr<http::request<http::empty_body>> req)
    : stream_(stream)
    , req_(std::move(req))
    , state_(request_in_progress)
  {
    res_ = std::make_shared<http::response<http::string_body>>();
  }

  template <typename Self>
  void operator()(Self &self)
  {
    stream_.expires_after(std::chrono::seconds(30));
    http::async_write(stream_, *req_, std::move(self));
  }

  template <typename Self>
  void operator()(Self &self, const boost::system::error_code &error,
                  const std::size_t bytes_transferred)
  {
    if (!error)
    {
      switch (state_)
      {
      case request_in_progress:
        http::async_read(stream_, buffer_, *res_, std::move(self));
        state_ = waiting_for_response;
        break;
      case waiting_for_response:
        self.complete(error);
        break;
      }
    }
    else
    {
      self.complete(error);
    }
  }
};

struct api_client
{
  net::io_context & io_;
  beast::tcp_stream stream_;
  tcp::resolver     resolver_;
  const std::string base_url_;
  const std::string port_;

  api_client(net::io_context &io, const std::string &base_url, const std::string &port = "80")
    : io_(io)
    , stream_(net::make_strand(io))
    , resolver_(net::make_strand(io))
    , base_url_(base_url)
    , port_(port)
  {}

  template <typename CompletionToken>
  auto async_get(const std::string &path, CompletionToken &&token) ->
      typename boost::asio::async_result<typename std::decay<CompletionToken>::type,
                                         void(const boost::system::error_code &)>::return_type
  {
    std::unique_ptr<http::request<http::empty_body>> req =
        std::make_unique<http::request<http::empty_body>>();

    req->version(11);
    req->method(http::verb::get);
    req->target(path);
    req->set(http::field::host, base_url_);

    return boost::asio::async_compose<CompletionToken, void(const boost::system::error_code &)>(
        async_request(std::ref(stream_), std::move(req)), token);
  }

  template <typename CompletionToken>
  auto async_resolve_host(CompletionToken &&token) ->
      typename boost::asio::async_result<typename std::decay<CompletionToken>::type,
                                         void(const boost::system::error_code &,
                                              const tcp::resolver::results_type &)>::return_type
  {
    return boost::asio::async_initiate<CompletionToken, void(const boost::system::error_code &,
                                                             const tcp::resolver::results_type &)>(
        async_resolve_initiation(), token, std::ref(resolver_), base_url_, port_);
  }

  template <typename CompletionToken>
  auto async_connect_host(const tcp::resolver::results_type &results, CompletionToken &&token) ->
      typename boost::asio::async_result<
          typename std::decay<CompletionToken>::type,
          void(const boost::system::error_code &,
               const tcp::resolver::results_type::endpoint_type &)>::return_type
  {
    return boost::asio::async_initiate<CompletionToken,
                                       void(const boost::system::error_code &,
                                            const tcp::resolver::results_type::endpoint_type &)>(
        async_connect_initiation(), token, std::ref(stream_), results);
  }
};

#endif

/// main.cpp

#include "api_client.h"

#include <iostream>

int main(void)
{
  net::io_context io_context;

  api_client client(io_context, "www.google.com");

  client.async_resolve_host(
      [&client](const boost::system::error_code &ec, const tcp::resolver::results_type &results) {
        if (!ec)
        {
          client.async_connect_host(
              results, [&client](const boost::system::error_code &                 ec,
                                 const tcp::resolver::results_type::endpoint_type &endpoint) {
                if (!ec)
                {
                  std::cout << "connected at " << endpoint << std::endl;
                  client.async_get("/", [](const boost::system::error_code &ec) {
                    std::cout << "async_get: " << ec.message() << "\n";
                  });
                }
              });
        }
      });
  ////////////////////////////////////////////////////////
  io_context.run();  // PROBLEM: this function returns before callback of async_get() is called
  ////////////////////////////////////////////////////////

  return EXIT_SUCCESS;
}

# CMakeLists.txt
project(main)

add_executable(${PROJECT_NAME} main.cpp)
find_package(Boost REQUIRED COMPONENTS system)
target_link_libraries(${PROJECT_NAME} Boost::system)

person goto    schedule 02.11.2020    source источник
comment
Вы можете увидеть такое поведение, если async_connect_host или async_resolve_host вернутся с ошибкой.   -  person janm    schedule 02.11.2020
comment
@janm Это комментарий или вопрос?   -  person goto    schedule 02.11.2020
comment
Престижность за отличный образец. Прочитав его, я кое-чему научился (я еще не усвоил async_compose, так что здесь я отстаю :))   -  person sehe    schedule 02.11.2020
comment
@goto Это комментарий. Вы не распечатываете сообщение об ошибке, поэтому, если произойдет сбой, процесс завершится, потому что в обработчике нет новой работы. Я не стал читать остальную часть вашего кода, но, похоже, есть ответ, который охватывает проблемы.   -  person janm    schedule 02.11.2020


Ответы (1)


Мне кажется, что он ДЕЙСТВИТЕЛЬНО работает, но вылетает. В частности, он печатает (в моей системе в отладочной сборке):

connected at 216.58.214.4:80
async_get: buffer overflow
Segmentation fault (core dumped)

Источник УБ

Проблема, похоже, в том, что ваша async_request операция имеет тип значения и перемещается (много раз).

Наблюдайте за этим, измеряя вручную:

~async_request() { std::clog << __PRETTY_FUNCTION__ << std::endl; }
async_request(async_request&&) = default;

Большинство участников имеют возможность перемещения, но flat_buffer просто больше не существует по исходному адресу. Это также подтверждается при запуске с включенной ASAN:

connected at 216.58.208.100:80
=================================================================
==14000==ERROR: AddressSanitizer: heap-use-after-free on address 0x615000000300 at pc 0x7fdc5aff11f9 bp 0x7fff92b42110 sp 0x7fff92b418b8
WRITE of size 512 at 0x615000000300 thread T0
    #0 0x7fdc5aff11f8  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x3b1f8)
    #1 0x7fdc5b02a5f4  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x745f4)
    #2 0x7fdc5b02acf3 in __interceptor_recvmsg (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x74cf3)
    #3 0x55d8dc9fcba4 in boost::asio::detail::socket_ops::recv(int, iovec*, unsigned long, int, boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:773
    #4 0x55d8dc9fcf73 in boost::asio::detail::socket_ops::non_blocking_recv(int, iovec*, unsigned long, int, bool, boost::system::error_code&, unsigned long&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:940
    #5 0x55d8dcbd2137 in boost::asio::detail::reactive_socket_recv_op_base<boost::beast::buffers_prefix_view<boost::asio::mutable_buffer> >::do_perform(boost::asio::detail::reactor_op*) /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactive_socket_recv_op.hpp:72
    #6 0x55d8dc9dd461 in boost::asio::detail::reactor_op::perform() /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactor_op.hpp:44
    #7 0x55d8dc9ee468 in boost::asio::detail::epoll_reactor::descriptor_state::perform_io(unsigned int) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:743
    #8 0x55d8dc9ee829 in boost::asio::detail::epoll_reactor::descriptor_state::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:774
    #9 0x55d8dc9d711e in boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/scheduler_operation.hpp:40
    #10 0x55d8dc9f748d in boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:481
    #11 0x55d8dc9f46a4 in boost::asio::detail::scheduler::run(boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:204
    #12 0x55d8dc9fa2e1 in boost::asio::io_context::run() /home/sehe/custom/boost_1_74_0/boost/asio/impl/io_context.ipp:63
    #13 0x55d8dc9024ff in main /home/sehe/Projects/stackoverflow/test.cpp:232
    #14 0x7fdc59871b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #15 0x55d8dc901929 in _start (/home/sehe/Projects/stackoverflow/sotest+0x49e929)

0x615000000300 is located 0 bytes inside of 512-byte region [0x615000000300,0x615000000500)
freed by thread T0 here:
==14000==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cpp:177 "((res.trace)) != (0)" (0x0, 0x0)
    #0 0x7fdc5b06dba4  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb7ba4)
    #1 0x7fdc5b08d1da  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xd71da)
    #2 0x7fdc5afe2adc  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x2cadc)
    #3 0x7fdc5afe483a  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x2e83a)
    #4 0x7fdc5afe7edb  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x31edb)
    #5 0x7fdc5b06d99b  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb799b)
    #6 0x7fdc5b06d255  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb7255)
    #7 0x7fdc5aff1221  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x3b221)
    #8 0x7fdc5b02a5f4  (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x745f4)
    #9 0x7fdc5b02acf3 in __interceptor_recvmsg (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x74cf3)
    #10 0x55d8dc9fcba4 in boost::asio::detail::socket_ops::recv(int, iovec*, unsigned long, int, boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:773
    #11 0x55d8dc9fcf73 in boost::asio::detail::socket_ops::non_blocking_recv(int, iovec*, unsigned long, int, bool, boost::system::error_code&, unsigned long&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:940
    #12 0x55d8dcbd2137 in boost::asio::detail::reactive_socket_recv_op_base<boost::beast::buffers_prefix_view<boost::asio::mutable_buffer> >::do_perform(boost::asio::detail::reactor_op*) /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactive_socket_recv_op.hpp:72
    #13 0x55d8dc9dd461 in boost::asio::detail::reactor_op::perform() /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactor_op.hpp:44
    #14 0x55d8dc9ee468 in boost::asio::detail::epoll_reactor::descriptor_state::perform_io(unsigned int) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:743
    #15 0x55d8dc9ee829 in boost::asio::detail::epoll_reactor::descriptor_state::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:774
    #16 0x55d8dc9d711e in boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/scheduler_operation.hpp:40
    #17 0x55d8dc9f748d in boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:481
    #18 0x55d8dc9f46a4 in boost::asio::detail::scheduler::run(boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:204
    #19 0x55d8dc9fa2e1 in boost::asio::io_context::run() /home/sehe/custom/boost_1_74_0/boost/asio/impl/io_context.ipp:63
    #20 0x55d8dc9024ff in main /home/sehe/Projects/stackoverflow/test.cpp:232
    #21 0x7fdc59871b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #22 0x55d8dc901929 in _start (/home/sehe/Projects/stackoverflow/sotest+0x49e929)

Дешевый способ обойти это - добавить еще один умный указатель. Тем не мение:

Еще одна тонкая проблема

После исправления вышеуказанного есть еще одна проблема:

http::async_read(stream_, buffer_, *res_, std::move(self));
state_ = waiting_for_response;

Вы обновляете state_ ПОСЛЕ переезда. Это использование за ходом. Это не UB, потому что это элемент примитивного типа. Но это тоже не то, что вы хотели. Изменить порядок:

state_ = waiting_for_response;
http::async_read(stream_, *buffer_, *res_, std::move(self));

Минимальная фиксированная версия:

В прямом эфире на Coliru

// api_client.h
#ifndef API_CLIENT_H
#define API_CLIENT_H

#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <functional>
#include <iostream>
#include <memory>

namespace beast = boost::beast;          // from <boost/beast.hpp>
namespace http  = beast::http;           // from <boost/beast/http.hpp>
namespace net   = boost::asio;           // from <boost/asio.hpp>
using tcp       = boost::asio::ip::tcp;  // from <boost/asio/ip/tcp.hpp>

struct async_resolve_initiation
{
  template <typename CompletionHandler>
  void operator()(CompletionHandler &&completion_handler, tcp::resolver &resolver,
                  const std::string &host, const std::string &port) const
  {
    auto executor =
        boost::asio::get_associated_executor(completion_handler, resolver.get_executor());

    resolver.async_resolve(
        host, port,
        boost::asio::bind_executor(executor, std::forward<CompletionHandler>(completion_handler)));
  }
};

template <typename CompletionToken>
auto async_resolve_host(tcp::resolver &resolver, const std::string &host, const std::string &port,
                        CompletionToken &&token) ->
    typename boost::asio::async_result<typename std::decay<CompletionToken>::type,
                                       void(const boost::system::error_code &,
                                            const tcp::resolver::results_type &)>::return_type
{
  return boost::asio::async_initiate<CompletionToken, void(const boost::system::error_code &,
                                                           const tcp::resolver::results_type &)>(
      async_resolve_initiation(), token, std::ref(resolver), host, port);
}

struct async_connect_initiation
{
  template <typename CompletionHandler>
  void operator()(CompletionHandler &&completion_handler, beast::tcp_stream &stream,
                  const tcp::resolver::results_type &results) const
  {

    stream.expires_after(std::chrono::seconds(10));

    auto executor = boost::asio::get_associated_executor(completion_handler, stream.get_executor());

    stream.async_connect(results, std::forward<CompletionHandler>(completion_handler));
  }
};

template <typename CompletionToken>
auto async_connect_host(beast::tcp_stream &stream, const tcp::resolver::results_type &results,
                        CompletionToken &&token) ->
    typename boost::asio::async_result<
        typename std::decay<CompletionToken>::type,
        void(const boost::system::error_code &,
             const tcp::resolver::results_type::endpoint_type &)>::return_type
{
  return boost::asio::async_initiate<CompletionToken,
                                     void(const boost::system::error_code &,
                                          const tcp::resolver::results_type::endpoint_type &)>(
      async_connect_initiation(), token, std::ref(stream), results);
}

struct async_request
{
  beast::tcp_stream &                              stream_;
  std::unique_ptr<http::request<http::empty_body>> req_;
  enum
  {
    request_in_progress,
    waiting_for_response,
  } state_;
  std::unique_ptr<beast::flat_buffer>                buffer_;
  std::shared_ptr<http::response<http::string_body>> res_;

  async_request(beast::tcp_stream &stream, std::unique_ptr<http::request<http::empty_body>> req)
    : stream_(stream)
    , req_(std::move(req))
    , state_(request_in_progress)
  {
    res_ = std::make_shared<http::response<http::string_body>>();
    buffer_ = std::make_unique<boost::beast::flat_buffer>();
  }

  template <typename Self>
  void operator()(Self &self)
  {
    stream_.expires_after(std::chrono::seconds(30));
    http::async_write(stream_, *req_, std::move(self));
  }

  template <typename Self>
  void operator()(Self &self, const boost::system::error_code &error,
                  const std::size_t /*bytes_transferred*/)
  {
    if (!error)
    {
      switch (state_)
      {
      case request_in_progress:
        state_ = waiting_for_response;
        http::async_read(stream_, *buffer_, *res_, std::move(self));
        break;
      case waiting_for_response:
        self.complete(error);
        break;
      }
    }
    else
    {
      self.complete(error);
    }
  }
};

struct api_client
{
  net::io_context & io_;
  beast::tcp_stream stream_;
  tcp::resolver     resolver_;
  const std::string base_url_;
  const std::string port_;

  api_client(net::io_context &io, std::string base_url, std::string port = "80")
    : io_(io)
    , stream_(net::make_strand(io))
    , resolver_(net::make_strand(io))
    , base_url_(std::move(base_url))
    , port_(std::move(port))
  {}

  template <typename CompletionToken>
  auto async_get(const std::string &path, CompletionToken &&token) ->
      typename boost::asio::async_result<typename std::decay<CompletionToken>::type,
                                         void(const boost::system::error_code &)>::return_type
  {
    std::unique_ptr<http::request<http::empty_body>> req =
        std::make_unique<http::request<http::empty_body>>();

    req->version(11);
    req->method(http::verb::get);
    req->target(path);
    req->set(http::field::host, base_url_);

    return boost::asio::async_compose<CompletionToken, void(const boost::system::error_code &)>(
        async_request(std::ref(stream_), std::move(req)), token);
  }

  template <typename CompletionToken>
  auto async_resolve_host(CompletionToken &&token) ->
      typename boost::asio::async_result<typename std::decay<CompletionToken>::type,
                                         void(const boost::system::error_code &,
                                              const tcp::resolver::results_type &)>::return_type
  {
    return boost::asio::async_initiate<CompletionToken, void(const boost::system::error_code &,
                                                             const tcp::resolver::results_type &)>(
        async_resolve_initiation(), token, std::ref(resolver_), base_url_, port_);
  }

  template <typename CompletionToken>
  auto async_connect_host(const tcp::resolver::results_type &results, CompletionToken &&token) ->
      typename boost::asio::async_result<
          typename std::decay<CompletionToken>::type,
          void(const boost::system::error_code &,
               const tcp::resolver::results_type::endpoint_type &)>::return_type
  {
    return boost::asio::async_initiate<CompletionToken,
                                       void(const boost::system::error_code &,
                                            const tcp::resolver::results_type::endpoint_type &)>(
        async_connect_initiation(), token, std::ref(stream_), results);
  }
};

#endif

/// main.cpp

//#include "api_client.h"

#include <iostream>

int main()
{
  net::io_context io_context;

  api_client client(io_context, "www.google.com");

  client.async_resolve_host(
      [&client](const boost::system::error_code &ec, const tcp::resolver::results_type &results) {
        //std::cout << "resolved " << ec.message() << std::endl;
        if (!ec)
        {
          client.async_connect_host(
              results, [&client](const boost::system::error_code &                 ec,
                                 const tcp::resolver::results_type::endpoint_type &endpoint) {
                if (!ec)
                {
                  std::cout << "connected at " << endpoint << std::endl;
                  client.async_get("/", [](const boost::system::error_code &ec) {
                    std::cout << "async_get: " << ec.message() << "\n";
                  });
                }
              });
        }
      });

  io_context.run();
}
    

Печать:

connected at 216.58.208.100:80
async_get: Success
person sehe    schedule 02.11.2020
comment
В случайном порядке рассмотрите возможность использования asio::async_connect для составления танцевального решения / соединения. - person sehe; 02.11.2020
comment
Спасибо за рабочую версию. Я использую boost 1.74 на Mac, поэтому это объясняет различное поведение. Я считаю, что настоящая проблема заключается в передаче дополнительных исполнителей, таких как boost :: asio :: async_compose ‹CompletionToken, void (const boost :: system :: error_code &)› (async_request (std :: ref (stream_), std :: move (req)), токен, stream_, io_); - person goto; 02.11.2020
comment
Осторожность! Неопределенное поведение - это проклятие: он может все. Таким образом, даже если это объясняет различное поведение, это не означает, что UB не является серьезной проблемой. - person sehe; 03.11.2020
comment
И да, я не видел, чтобы вторичный исполнитель проходил мимо, но это действительно может привести к описанной проблеме: любая операция выполняет охрану исполнителя на протяжении всей операции. Однако, если вторичный исполнитель не зависит от главного исполнителя (из вашего исходного контекста выполнения), он не сможет удерживать его занятым. - person sehe; 03.11.2020
comment
у вас есть идеи, как использовать enable_shared_from_this с составными операциями? - person goto; 03.11.2020