Mam serwer aplikacji, która wykorzystuje boost ASIO do komunikacji z wieloma klientami. Serwer aplikacja działa na serwerze Linux, a klienci-na stołach roboczych Windows.
Aktualna konstrukcja многопоточна, choć jest tylko jeden boost ASIO thead (który działa boost::asio::io_context
). Strumień boost ASIO odpowiada tylko za odczyt, zapis i niektóre rzadkie wysyłki. Odczyt odbywa się za pomocą boost::asio::async_read
ale kopiuje otrzymaną wiadomość, aby inny wątek mógł wykonać pracę w zakresie obróbki. Pisanie odbywa się za pomocą boost::asio::write
ale wiadomość już skopiowana i przekazana w strumień boost ASIO
W większości przypadków, gdy klient wyłącza, boost ASIO zgłasza błąd, wyłączam odpowiednie gniazdo, a inne gniazda nadal pracować. Jednak, jeśli na pulpicie systemu Windows klienta, awaria zasilania podczas boost::asio::write
pisze do nich, boost nie wykryje problem i zawiesza się boost::asio::write
. Czasami zawiesza się prawie na 20 minut i w tym czasie serwer nie może komunikować się z innymi klientami
Z tego co wyczytałem w Internecie, autorzy boost ASIO nie zamierzają wprowadzać ustawienie limitu czasu. Próbowałem ustawić wartość SO_SNDTIMEO równy 5 sekund, ale to w żaden sposób nie wpłynęło na zawiesza się wpisów. W tej chwili jestem najlepiej domyślam się, że aby rozwiązać ten problem, należy dostarczyć do każdego gniazda osobny wątek, aby jeden klient nie mógł wyłączyć innych klientów. Czy są jakieś lepsze opcje niż ten? Jeśli dam każdemu gniazda swój własny wątek, czy to oznacza, że będę potrzebował boost::asio::io_context
dla każdego strumienia, aby uniknąć zawieszenia wpisów?
Zmiana: Po przejrzeniu komentarzy próbowałem powtórzyć funkcję, która powoduje boost::asio::write
z boost::asio::async_write
. Poniżej mam kod, który został uproszczony do SO, ale nadal pokazuje, jaka była całkowita zmiana:
Początkowo z boost::asio::write
:
inline void MessagingServer::writeMessage(
GuiSession* const a_guiSession,
const PB::Message& a_msg
) {
boost::asio::dispatch(m_guiIoIoContext, [this, a_guiSession, a_msg]() {
// I removed code that writes a_msg's bytes into m_guiIoWriteBuf
// and sets totalSize to simplify for SO
boost::system::error_code error;
boost::asio::write(a_guiSession->m_guiIoGsSocket, boost::asio::buffer(m_guiIoWriteBuf, totalSize), error);
if (UNLIKELY(error))
ERRLOG << a_guiSession->m_gsSessionId << " write failed: " << error.message();
});
}
Przerobione z boost::asio::async_write
:
inline void MessagingServer::writeMessage(
GuiSession* const a_guiSession,
const PB::Message& a_msg
) {
a_guiSession->m_tempMutex.lock();
boost::asio::dispatch(m_guiIoIoContext, [this, a_guiSession, a_msg]() {
// I removed code that writes a_msg's bytes into m_guiIoWriteBuf
// and sets totalSize to simplify for SO
boost::asio::async_write(
a_guiSession->m_guiIoGsSocket,
boost::asio::buffer(m_guiIoWriteBuf, totalSize),
[this, a_guiSession](const boost::system::error_code& a_error, std::size_t) {
if (UNLIKELY(a_error))
ERRLOG << a_guiSession->m_gsSessionId << " write failed: " << a_error.message();
a_guiSession->m_tempMutex.unlock();
}
);
});
}
Blokada została wprowadzona w drugim kodzie, aby zagwarantować tylko jedno połączenie boost::asio::async_write
był aktywny w czasie (wiem, że są lepsze sposoby na to, ale to jest łatwiejsze dla testów). Oba te kody mają jeden i ten sam problem z zawieszaniem się komputera boost ASIO w przypadku awarii zasilania klienta. Jednak oni spędzają na różne sposoby, asynchroniczny kod na boost ASIO wykonywać inne czynności, po prostu nie nagrywa dalej, aż zawiesza się, nie doprowadzi do błędu
Podczas pojedynczego eksperymentu próbowałem zainstalować SO_KEEPALIVE
ale to również nie rozwiązało problem z zawieszaniem się komputera