Boost (Beast) websocket: синхронная запись зависает

Я столкнулся с проблемой в потоковом веб-сокете. Эта проблема возникает периодически, когда я пытаюсь выполнить запись в поток, в котором удаленная конечная точка перестала отвечать (в частности, из-за того, что удаленный физически отключен от сети).

Когда возникает эта проблема, синхронный вызов stream.write() в конечном итоге зависает на очень долгое время (минуты), пока сокет в конечном итоге не закроется. Я понимаю, что такое поведение, скорее всего, связано с тем, что моя программа продолжает запись в поток без удаленных подтверждений до тех пор, пока буфер отправки не будет заполнен. Мне интересно, есть ли способ применить тайм-аут к вызову записи или есть интерфейс, более похожий на stream.try_write(), где я могу поднять обработку ошибок до уровня пользователя.

Я понимаю, что одним из вариантов является использование интерфейса async_write. Однако я обеспокоен тем, что это отрицательно скажется на моей производительности отправки, отложив операцию записи в сокет до следующей итерации io_context.

Ниже приведена трассировка стека потока, когда вызов завис.

#0  0x00007f468cf33624 in poll () from /lib64/libc.so.6
#1  0x000000000043e5a7 in boost::asio::detail::socket_ops::poll_write (ec=..., msec=-1, state=0 '\000', s=16)
    at /usr/include/boost/asio/detail/impl/socket_ops.ipp:1898
#2  boost::asio::detail::socket_ops::sync_send (ec=..., all_empty=<optimized out>, flags=0, count=<optimized out>, bufs=0x7fff43c17e20, 
    state=<optimized out>, s=<optimized out>) at /usr/include/boost/asio/detail/impl/socket_ops.ipp:1224
#3  boost::asio::detail::reactive_socket_service_base::send<boost::asio::detail::prepared_buffers<boost::asio::const_buffer, 64ul> > (impl=..., 
    buffers=..., ec=..., this=<optimized out>, flags=0) at /usr/include/boost/asio/detail/reactive_socket_service_base.hpp:245
#4  0x0000000000481c71 in boost::asio::basic_stream_socket<boost::asio::ip::tcp>::write_some<boost::asio::detail::prepared_buffers<boost::asio::const_buffer, 64ul> > (ec=..., buffers=..., this=0x108ad50) at /usr/include/boost/asio/buffer.hpp:941
#5  boost::asio::detail::write_buffer_sequence<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > >, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > >::const_iterator, boost::asio::detail::transfer_all_t> (completion_condition=..., ec=..., buffers=..., s=...)
    at /usr/include/boost/asio/impl/write.hpp:53
#6  boost::asio::write<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > >, boost::asio::detail::transfer_all_t> (ec=..., buffers=..., s=..., completion_condition=...) at /usr/include/boost/asio/impl/write.hpp:69
#7  boost::asio::write<boost::asio::basic_stream_socket<boost::asio::ip::tcp>, boost::beast::buffers_cat_view<boost::asio::mutable_buffer, boost::beast::buffers_prefix_view<boost::beast::buffers_suffix<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> > > > (ec=..., buffers=..., s=...)
    at /usr/include/boost/asio/impl/write.hpp:92
#8  boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::write_some<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> (this=this@entry=0x108ad50, fin=fin@entry=true, buffers=..., ec=...) at /usr/include/boost/beast/websocket/impl/write.ipp:625
#9  0x000000000042c5e1 in boost::beast::websocket::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::write<boost::beast::basic_multi_buffer<std::allocator<char> >::const_buffers_type> (ec=..., buffers=..., this=0x108ad50)

2 ответа

Beast websockets не поддерживает неблокирующий режим. Реализация websocket::stream будет вызывать неопределенное поведение в некоторых случаях, если вы установите этот режим для сокета, используемого с потоком веб-сокетов. Отсутствие тайм-аутов является общей проблемой синхронного кода. У вас действительно нет другого выбора, кроме как использовать асинхронные операции. Вы сказали, что хотите отправлять буферы непосредственно из стека, это легко сделать в асинхронном контексте с помощью сопрограмм (см. boost::asio::spawn а также boost::asio::yield_context).

Существует множество методов, которые позволяют асинхронному вводу / выводу выполнять то же или лучше, чем синхронный ввод / вывод, в том числе для случая низкой желаемой задержки.

Вот совет от автора Boost.Asio по достижению сверхнизкой задержки: https://groups.google.com/a/isocpp.org/d/msg/sg14/FoLFHXqZSck/i4rdO-O3BQAJ

Я смог решить эту проблему, переведя базовый потоковый сокет в неблокирующий режим:

socket.non_blocking(true);

В этом режиме, когда буфер отправки заполнится, write() звонок вернется boost::system::error_code::try_again (он же посикс EAGAIN) немедленно.

Другие вопросы по тегам