TCP: Implemented ACK return stream, as a feedback channel (to be read properly!)
This commit is contained in:
@@ -197,6 +197,50 @@ bool TCPStreamPusherSocket::SendAll(const void *buf, size_t len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TCPStreamPusherSocket::ReadExact(void *buf, size_t len) {
|
||||
auto *p = static_cast<uint8_t *>(buf);
|
||||
size_t got = 0;
|
||||
|
||||
while (got < len) {
|
||||
if (!active)
|
||||
return false;
|
||||
|
||||
int local_fd = fd.load();
|
||||
if (local_fd < 0)
|
||||
return false;
|
||||
|
||||
pollfd pfd{};
|
||||
pfd.fd = local_fd;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
const int prc = poll(&pfd, 1, 100); // 100 ms interruptibility window
|
||||
if (prc == 0)
|
||||
continue;
|
||||
if (prc < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
if ((pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
|
||||
return false;
|
||||
if ((pfd.revents & POLLIN) == 0)
|
||||
continue;
|
||||
|
||||
ssize_t rc = ::recv(local_fd, p + got, len - got, 0);
|
||||
if (rc == 0)
|
||||
return false;
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
got += static_cast<size_t>(rc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TCPStreamPusherSocket::SendPayloadZC(const uint8_t *data, size_t size, ZeroCopyReturnValue *z) {
|
||||
#if defined(MSG_ZEROCOPY) && defined(SO_ZEROCOPY)
|
||||
int local_fd = fd.load();
|
||||
@@ -358,10 +402,89 @@ void TCPStreamPusherSocket::CompletionThread() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void TCPStreamPusherSocket::AckThread() {
|
||||
while (active) {
|
||||
TcpFrameHeader h{};
|
||||
if (!ReadExact(&h, sizeof(h))) {
|
||||
if (active) {
|
||||
broken = true;
|
||||
logger.Error("TCP ACK reader disconnected on " + endpoint);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (h.magic != JFJOCH_TCP_MAGIC || h.version != JFJOCH_TCP_VERSION || static_cast<TCPFrameType>(h.type) != TCPFrameType::ACK) {
|
||||
broken = true;
|
||||
logger.Error("Invalid ACK frame on " + endpoint);
|
||||
break;
|
||||
}
|
||||
|
||||
std::string error_text;
|
||||
if (h.payload_size > 0) {
|
||||
error_text.resize(h.payload_size);
|
||||
if (!ReadExact(error_text.data(), error_text.size())) {
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto ack_for = static_cast<TCPFrameType>(h.ack_for);
|
||||
const bool ok = (h.flags & TCP_ACK_FLAG_OK) != 0;
|
||||
const bool fatal = (h.flags & TCP_ACK_FLAG_FATAL) != 0;
|
||||
const auto code = static_cast<TCPAckCode>(h.ack_code);
|
||||
|
||||
{
|
||||
std::unique_lock ul(ack_state_mutex);
|
||||
last_ack_code = code;
|
||||
if (!error_text.empty())
|
||||
last_ack_error = error_text;
|
||||
|
||||
if (ack_for == TCPFrameType::START) {
|
||||
start_ack_received = true;
|
||||
start_ack_ok = ok;
|
||||
if (!ok && error_text.empty())
|
||||
last_ack_error = "START rejected";
|
||||
} else if (ack_for == TCPFrameType::END) {
|
||||
end_ack_received = true;
|
||||
end_ack_ok = ok;
|
||||
if (!ok && error_text.empty())
|
||||
last_ack_error = "END rejected";
|
||||
} else if (ack_for == TCPFrameType::CANCEL) {
|
||||
cancel_ack_received = true;
|
||||
cancel_ack_ok = ok;
|
||||
if (!ok && error_text.empty())
|
||||
last_ack_error = "CANCEL rejected";
|
||||
} else if (ack_for == TCPFrameType::DATA && (!ok || fatal)) {
|
||||
broken = true;
|
||||
if (error_text.empty())
|
||||
last_ack_error = "DATA fatal ACK";
|
||||
logger.Error("Received fatal DATA ACK on " + endpoint + ": " + last_ack_error);
|
||||
}
|
||||
}
|
||||
ack_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPStreamPusherSocket::StartWriterThread() {
|
||||
if (active)
|
||||
return;
|
||||
|
||||
{
|
||||
std::unique_lock ul(ack_state_mutex);
|
||||
start_ack_received = false;
|
||||
start_ack_ok = false;
|
||||
end_ack_received = false;
|
||||
end_ack_ok = false;
|
||||
cancel_ack_received = false;
|
||||
cancel_ack_ok = false;
|
||||
last_ack_error.clear();
|
||||
last_ack_code = TCPAckCode::None;
|
||||
}
|
||||
|
||||
active = true;
|
||||
send_future = std::async(std::launch::async, &TCPStreamPusherSocket::WriterThread, this);
|
||||
completion_future = std::async(std::launch::async, &TCPStreamPusherSocket::CompletionThread, this);
|
||||
ack_future = std::async(std::launch::async, &TCPStreamPusherSocket::AckThread, this);
|
||||
}
|
||||
|
||||
void TCPStreamPusherSocket::StopWriterThread() {
|
||||
@@ -369,11 +492,14 @@ void TCPStreamPusherSocket::StopWriterThread() {
|
||||
return;
|
||||
active = false;
|
||||
queue.PutBlocking({.end = true});
|
||||
ack_cv.notify_all();
|
||||
|
||||
if (send_future.valid())
|
||||
send_future.get();
|
||||
if (completion_future.valid())
|
||||
completion_future.get();
|
||||
if (ack_future.valid())
|
||||
ack_future.get();
|
||||
|
||||
// Keep fd open: END frame may still be sent after writer thread stops.
|
||||
// Socket is closed in destructor / explicit close path.
|
||||
@@ -403,3 +529,46 @@ bool TCPStreamPusherSocket::Send(const uint8_t *data, size_t size, TCPFrameType
|
||||
|
||||
return SendFrame(data, size, type, image_number, nullptr);
|
||||
}
|
||||
|
||||
bool TCPStreamPusherSocket::WaitForAck(TCPFrameType ack_for, std::chrono::milliseconds timeout, std::string *error_text) {
|
||||
std::unique_lock ul(ack_state_mutex);
|
||||
const bool ok = ack_cv.wait_for(ul, timeout, [&] {
|
||||
if (ack_for == TCPFrameType::START)
|
||||
return start_ack_received || broken.load();
|
||||
if (ack_for == TCPFrameType::END)
|
||||
return end_ack_received || broken.load();
|
||||
if (ack_for == TCPFrameType::CANCEL)
|
||||
return cancel_ack_received || broken.load();
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!ok) {
|
||||
if (error_text)
|
||||
*error_text = "ACK timeout";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (broken) {
|
||||
if (error_text)
|
||||
*error_text = last_ack_error.empty() ? "Socket broken" : last_ack_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ack_ok = false;
|
||||
if (ack_for == TCPFrameType::START)
|
||||
ack_ok = start_ack_ok;
|
||||
else if (ack_for == TCPFrameType::END)
|
||||
ack_ok = end_ack_ok;
|
||||
else if (ack_for == TCPFrameType::CANCEL)
|
||||
ack_ok = cancel_ack_ok;
|
||||
|
||||
if (!ack_ok && error_text)
|
||||
*error_text = last_ack_error.empty() ? "ACK rejected" : last_ack_error;
|
||||
|
||||
return ack_ok;
|
||||
}
|
||||
|
||||
std::string TCPStreamPusherSocket::GetLastAckError() const {
|
||||
std::unique_lock ul(ack_state_mutex);
|
||||
return last_ack_error;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user