Boost.Asio: Timeouts

Идея заключается в том, чтобы создавать для каждого экземпляра самописного класса объект io_context, у которого внутри есть свой Event Loop. А для каждого вызова метода, создавать поток и захватывать io_context по ссылке через лямбду.

Проблема заключается в том, что при использовании блокирующих операций connect, read*, write*, поток может просто зависнуть навсегда. Я нашёл способ, как можно этого избежать. Например:

tcp_socket.async_connect(tcp_endpoint,
                         [&error_code](const bs::error_code& ec) mutable
                         {
                             error_code = ec;
                         });
using namespace std::chrono_literals;
std::size_t handlers = io_context.run_for(5s);
std::clog << "handlers: " << handlers << "\nTime expired!\n";

Не знаю точно, как работают асинхронные методы в Asio, но по идее, если async_connect, async_read*, async_write* зависнут внутри Event Loop, то тоже ничего хорошего не будет, ведь такие запросы будут в нем накапливаться и накапливаться, если я правильно понимаю. Как с этим быть? К тому же, я не понимаю, io_context::run* будут блокирующими для io_context в целом или только для текущего потока?

Подскажите, как нормально реализовать таймауты для этих операций? Потому как если указать неверный (но валидный) IP, то все это наглухо зависает в блокирующем коде, и непонятно как работает в асинхронном коде. Спасибо.


Ответы (1 шт):

Автор решения: KoVadim

Обычно внутри таких систем используются специальные функции операционной системы - select, poll, epoll, kpoll и десятки других разновидностей. Но суть их в одном и том же - Вы формируете список дескрипторов (сокеты) и желаемые события (доступен для чтения, есть возможность писать) и отдаете их данной функции/операционной системе. Список может быть большим (десятки и сотни дескрипторов). Сама функция (select/poll) будет ждать пока не наступит желаемое событие на дескрипторе и сообщит об этом. Также они поддерживают таймаут.

Что будет, если сервер с той стороны "плохой" - событие на сокете никогда не наступит. В целом, теряем немного памяти на него и немного процессорного времени. Если выставить себе хорошие таймауты, то такой дескриптор легко исключить из последующий итераций и ничего плохого не будет.

Может ли вызов read/write "зависнуть"? в принципе да, но всегда можно попросить операционную систему не делать это запрос блокирующим и в том случае, если операционная система определит, что не может выполнить Ваш запрос, она об этом сообщит (обычно кодом EAGAIN).

Прочитайте туториал по select и многое станет на свои места. Да, сейчас, select не так популярен ( у него есть существенные ограничения на кол-во дескрипторов), но он работает более-менее одинаково на разных осях и поможет понять, как работает остальное.

→ Ссылка