fix(picloud-dispatcher): drop data to a connection whose write is stalled
While the peer does not drain its receive buffer, every forwarded packet would re-stall the reader thread for up to writeTimeout(), so a queued Disconnect frame could wait behind the whole backlog. Remember a stalled target (send slower than 2 s) and drop data for a short cooldown instead of stalling; keep dispatcher writes bounded at 3 s. Control frames (Connected/Disconnected) are unaffected.
This commit is contained in:
@@ -9,7 +9,17 @@ CloudServer::CloudServer(DispatcherClient * c, const PIByteArray & sname): serve
|
|||||||
mutex_clients.lock();
|
mutex_clients.lock();
|
||||||
last_ping.reset();
|
last_ping.reset();
|
||||||
DispatcherClient * cl = index_clients.value(id, nullptr);
|
DispatcherClient * cl = index_clients.value(id, nullptr);
|
||||||
if (cl) cl->sendData(ba);
|
if (cl) {
|
||||||
|
// a healthy forward is fast: a slow one means
|
||||||
|
// the peer is not draining, remember it and
|
||||||
|
// drop further data instead of stalling
|
||||||
|
PITimeMeasurer tm;
|
||||||
|
cl->sendData(ba);
|
||||||
|
if (tm.elapsed_s() > 2.0) {
|
||||||
|
cl->markWriteStalled();
|
||||||
|
piCoutObj << "write stalled to client" << id;
|
||||||
|
}
|
||||||
|
}
|
||||||
mutex_clients.unlock();
|
mutex_clients.unlock();
|
||||||
}));
|
}));
|
||||||
connects << CONNECTL(c, pingReceived, [this]() {
|
connects << CONNECTL(c, pingReceived, [this]() {
|
||||||
@@ -43,7 +53,12 @@ void CloudServer::addClient(DispatcherClient * c) {
|
|||||||
server->sendConnected(cid);
|
server->sendConnected(cid);
|
||||||
CONNECTL(c, dataReaded, ([this, cid](PIByteArray & ba) {
|
CONNECTL(c, dataReaded, ([this, cid](PIByteArray & ba) {
|
||||||
// piCoutObj << c->clientId() << "dataReaded";
|
// piCoutObj << c->clientId() << "dataReaded";
|
||||||
|
PITimeMeasurer tm;
|
||||||
server->sendDataToClient(ba, cid);
|
server->sendDataToClient(ba, cid);
|
||||||
|
if (tm.elapsed_s() > 2.0) {
|
||||||
|
server->markWriteStalled();
|
||||||
|
piCoutObj << "write stalled to server" << cid;
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ DispatcherClient::DispatcherClient(PIEthernet * eth_, int id)
|
|||||||
, tcp(&streampacker)
|
, tcp(&streampacker)
|
||||||
, client_id(id) {
|
, client_id(id) {
|
||||||
eth->setName(PIString::fromNumber(id));
|
eth->setName(PIString::fromNumber(id));
|
||||||
|
// bound the EAGAIN retry loop in writeDevice: a write to a peer that
|
||||||
|
// does not drain its receive buffer must not block the reader thread
|
||||||
|
// for longer than this
|
||||||
|
eth->setWriteTimeout(PISystemTime::fromSeconds(3));
|
||||||
CONNECTU(&streampacker, packetReceiveEvent, this, readed);
|
CONNECTU(&streampacker, packetReceiveEvent, this, readed);
|
||||||
CONNECTU(eth, disconnected, this, disconnected);
|
CONNECTU(eth, disconnected, this, disconnected);
|
||||||
piCoutObj << "client connected" << client_id << eth->sendAddress();
|
piCoutObj << "client connected" << client_id << eth->sendAddress();
|
||||||
@@ -58,6 +62,10 @@ void DispatcherClient::sendDisconnected(uint client_id) {
|
|||||||
|
|
||||||
void DispatcherClient::sendData(const PIByteArray & data) {
|
void DispatcherClient::sendData(const PIByteArray & data) {
|
||||||
if (tcp.role() == PICloud::TCP::Client) {
|
if (tcp.role() == PICloud::TCP::Client) {
|
||||||
|
if (isWriteStalled()) {
|
||||||
|
piCoutObj << "sendData dropped, write stalled";
|
||||||
|
return;
|
||||||
|
}
|
||||||
PIMutexLocker send_lock(mutex_send);
|
PIMutexLocker send_lock(mutex_send);
|
||||||
tcp.sendData(data);
|
tcp.sendData(data);
|
||||||
} else
|
} else
|
||||||
@@ -67,6 +75,10 @@ void DispatcherClient::sendData(const PIByteArray & data) {
|
|||||||
|
|
||||||
void DispatcherClient::sendDataToClient(const PIByteArray & data, uint client_id) {
|
void DispatcherClient::sendDataToClient(const PIByteArray & data, uint client_id) {
|
||||||
if (tcp.role() == PICloud::TCP::Server) {
|
if (tcp.role() == PICloud::TCP::Server) {
|
||||||
|
if (isWriteStalled()) {
|
||||||
|
piCoutObj << "sendDataToClient dropped, write stalled";
|
||||||
|
return;
|
||||||
|
}
|
||||||
PIMutexLocker send_lock(mutex_send);
|
PIMutexLocker send_lock(mutex_send);
|
||||||
tcp.sendData(data, client_id);
|
tcp.sendData(data, client_id);
|
||||||
} else
|
} else
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ public:
|
|||||||
void sendDataToClient(const PIByteArray & data, uint client_id);
|
void sendDataToClient(const PIByteArray & data, uint client_id);
|
||||||
void authorise(bool ok);
|
void authorise(bool ok);
|
||||||
bool isAuthorised() const { return authorised; }
|
bool isAuthorised() const { return authorised; }
|
||||||
|
// forwarding to this connection stalled (peer not draining the send
|
||||||
|
// buffer) recently: drop data instead of stalling the reader thread
|
||||||
|
bool isWriteStalled() const { return (PISystemTime::current().toMilliseconds() - write_stalled_at) < 5000.0; }
|
||||||
|
void markWriteStalled() { write_stalled_at = PISystemTime::current().toMilliseconds(); }
|
||||||
PICloud::TCP::Role role() const { return tcp.role(); }
|
PICloud::TCP::Role role() const { return tcp.role(); }
|
||||||
PIString address();
|
PIString address();
|
||||||
uint clientId() const { return client_id; }
|
uint clientId() const { return client_id; }
|
||||||
@@ -38,6 +42,7 @@ private:
|
|||||||
EVENT_HANDLER1(void, disconnected, bool, withError);
|
EVENT_HANDLER1(void, disconnected, bool, withError);
|
||||||
|
|
||||||
std::atomic_bool authorised;
|
std::atomic_bool authorised;
|
||||||
|
std::atomic<double> write_stalled_at{0.0};
|
||||||
PIEthernet * eth;
|
PIEthernet * eth;
|
||||||
PIStreamPacker streampacker;
|
PIStreamPacker streampacker;
|
||||||
PICloud::TCP tcp;
|
PICloud::TCP tcp;
|
||||||
|
|||||||
Reference in New Issue
Block a user