Skip to content

Commit 766bd6f

Browse files
committed
EC: sync clients also fire OnLost on read/write error so amuleweb / amulecmd exit cleanly
In sync mode (use_events=false on CECMuleSocket → amulecmd, amuleweb) there's no pending async_read after the auth handshake, so the EOF that fires HandleRead → PostLostEvent for async clients (amulegui) never gets seen. SendRecvPacket detects the failure on its next ReadSync (or WriteSync, eventually after keepalive teardown) and returns NULL, but nothing notifies the EC layer — amuleweb continues serving HTTP template shells forever with no live amuled data, no error visible to the user, no exit. amulecmd sits at its prompt with a dead socket and every command silently failing. Fix: ReadSync / WriteSync, on a non-zero error_code, dispatch OnLost(0) directly through the LibSocket wrapper (DispatchSyncLost helper) — same dispatch path the asio reactor would take for async clients, just synchronous since the sync-mode caller is already on the main thread. From there: * CECMuleSocket::OnLost(int) forwards through static_cast<CECSocket*> to the EC-layer virtual. * CRemoteConnect::OnLost: if m_notifier is set (amulegui), posts the existing wxEVT_EC_CONNECTION event and the GUI handler flips the UI. If NULL (amuleweb, amulecmd), prints "External Connection lost — exiting" and calls _exit(1) so the supervisor (systemd / shell / docker) decides whether to restart and reconnect. Direct synchronous dispatch (rather than PostLostEvent + wxQueueEvent) because amulecmd's main thread is in fgets reading stdin, not in wxApp's event loop, so queued events would never be processed. amuleweb's wxApp::OnRun is the main loop but it's not running while the HTTP request handler is on the stack — direct dispatch from the same sync-mode call site avoids both reactor and event-loop coordination questions. _exit instead of exit() because we're calling from the main thread holding stacks the asio threads will tear down on shutdown; racing static destructors against them risks the kind of UAF amule-project#748 was about. The OS reaps fds / sockets on process exit anyway. Live-verified on Linux VM: * amulegui: TCP-level FIN observed, UI flips to "Connection failure" within ~1s. * amuleweb: First HTTP request after amuled killed returns with stale template, then amuleweb exits before next request can be served (curl gets connection refused). * amulecmd: First "status" command after amuled killed prints "External Connection lost — exiting" to stderr and exits with code 1. User sees their shell prompt return. Refs amule-project#757.
1 parent 48a9850 commit 766bd6f

3 files changed

Lines changed: 55 additions & 4 deletions

File tree

src/LibSocketAsio.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,9 @@ class CAsioSocketImpl : public std::enable_shared_from_this<CAsioSocketImpl>
716716
error_code ec;
717717
uint32 received = read(*m_socket, buffer(buf, bytesToRead), ec);
718718
SetError(ec);
719+
if (ec) {
720+
DispatchSyncLost();
721+
}
719722
return received;
720723
}
721724

@@ -724,9 +727,32 @@ class CAsioSocketImpl : public std::enable_shared_from_this<CAsioSocketImpl>
724727
error_code ec;
725728
uint32 sent = write(*m_socket, buffer(buf, nbytes), ec);
726729
SetError(ec);
730+
if (ec) {
731+
DispatchSyncLost();
732+
}
727733
return sent;
728734
}
729735

736+
// Sync clients (amulecmd, amuleweb) don't have an async_read pending
737+
// after auth, so the EOF that fires HandleRead → PostLostEvent for
738+
// async clients never gets seen. Detection happens here instead, in
739+
// ReadSync / WriteSync. PostLostEvent + wxQueueEvent would round-
740+
// trip through the wx event loop — which amuleweb has (wxApp::OnRun)
741+
// but amulecmd doesn't (its main thread is in fgets reading stdin,
742+
// not in wxApp's event loop, so queued events are never processed).
743+
// Direct synchronous dispatch through the same wrapper->OnLost(0)
744+
// path the async reactor uses covers both: CECMuleSocket::OnLost(int)
745+
// forwards to the EC-layer CECSocket::OnLost virtual, CRemoteConnect's
746+
// override fires (NULL notifier → _exit fallback) and the headless
747+
// EC client exits cleanly instead of serving stale data in limp mode.
748+
void DispatchSyncLost()
749+
{
750+
CLibSocket * wrapper = m_libSocket.load(std::memory_order_acquire);
751+
if (wrapper && !m_destroying.load(std::memory_order_acquire) && !m_closed) {
752+
wrapper->OnLost(0);
753+
}
754+
}
755+
730756
//
731757
// Access to even const & wxString is apparently not thread-safe.
732758
// Locks are set/removed in wx and reference counts can go astray.

src/libs/ec/cpp/ECMuleSocket.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,11 @@ bool CECMuleSocket::ConnectSocket(amuleIPV4Address& address)
5959
// constants used by CECServerSocket on the amuled side so detection
6060
// is symmetric. Numbers picked to balance responsiveness against the
6161
// keepalive packet overhead (one probe per 10s after 30s idle).
62-
namespace { const int EC_KEEPALIVE_IDLE_SEC = 30; }
63-
namespace { const int EC_KEEPALIVE_INTERVAL_SEC = 10; }
64-
namespace { const int EC_KEEPALIVE_PROBE_COUNT = 3; }
62+
namespace {
63+
const int EC_KEEPALIVE_IDLE_SEC = 30;
64+
const int EC_KEEPALIVE_INTERVAL_SEC = 10;
65+
const int EC_KEEPALIVE_PROBE_COUNT = 3;
66+
}
6567

6668
bool CECMuleSocket::InternalConnect(uint32_t ip, uint16_t port, bool wait) {
6769
amuleIPV4Address addr;

src/libs/ec/cpp/RemoteConnect.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131
#include "../../../amuleIPV4Address.h"
3232

3333
#include <wx/intl.h>
34+
#include <common/StringFunctions.h> // unicode2char for stderr message
35+
#ifdef __WINDOWS__
36+
#include <process.h> // _exit
37+
#else
38+
#include <unistd.h> // _exit
39+
#endif
3440

3541
wxDEFINE_EVENT(wxEVT_EC_CONNECTION, wxEvent);
3642
CECLoginPacket::CECLoginPacket(const wxString& client, const wxString& version,
@@ -200,10 +206,27 @@ void CRemoteConnect::OnConnect() {
200206

201207
void CRemoteConnect::OnLost() {
202208
if (m_notifier) {
203-
// Notify app of failure
209+
// Notify app of failure — amulegui's wxEvent handler flips the
210+
// UI to "disconnected" and stops trying to update.
204211
wxECSocketEvent event(wxEVT_EC_CONNECTION,false,_("Connection failure"));
205212
m_notifier->AddPendingEvent(event);
213+
return;
206214
}
215+
// Headless EC clients (amulecmd, amuleweb) construct CRemoteConnect
216+
// with NULL m_notifier. Continuing to run would mean serving stale
217+
// data in amuleweb (HTTP requests still return the template shell
218+
// without live amuled data, no error visible to the user) or
219+
// sitting at the amulecmd prompt with a dead socket. Failing fast
220+
// is the right semantic — supervisor (systemd unit / docker /
221+
// shell loop) is the recovery layer and decides whether to restart.
222+
fprintf(stderr, "%s\n",
223+
(const char *)unicode2char(_("External Connection lost — exiting.")));
224+
fflush(stderr);
225+
// _exit instead of exit() because the asio worker thread that
226+
// just fired this is mid-callback; racing static destructors
227+
// against it risks the kind of use-after-free #748 was about.
228+
// The OS reaps file descriptors / sockets on exit anyway.
229+
_exit(1);
207230
}
208231

209232
const CECPacket *CRemoteConnect::OnPacketReceived(const CECPacket *packet, uint32 trueSize)

0 commit comments

Comments
 (0)