fprime/Drv/Ip/SocketComponentHelper.cpp
Will MacCormack 31b6488723
Print the task name when socket open/recv fails (#4434)
* Log task name of socket that failed to open port

* Give failed to recv the same treatment

* Format my changes
2025-11-18 16:56:22 -08:00

366 lines
13 KiB
C++

// ======================================================================
// \title SocketComponentHelper.cpp
// \author mstarch, crsmith
// \brief cpp file for SocketComponentHelper implementation class
//
// \copyright
// Copyright 2009-2020, by the California Institute of Technology.
// ALL RIGHTS RESERVED. United States Government Sponsorship
// acknowledged.
//
// ======================================================================
#include <Drv/Ip/SocketComponentHelper.hpp>
#include <Fw/Logger/Logger.hpp>
#include <Fw/Types/Assert.hpp>
#include <cerrno>
namespace Drv {
SocketComponentHelper::SocketComponentHelper() {}
SocketComponentHelper::~SocketComponentHelper() {}
void SocketComponentHelper::start(const Fw::ConstStringBase& name,
const FwTaskPriorityType priority,
const Os::Task::ParamType stack,
const Os::Task::ParamType cpuAffinity,
const FwTaskPriorityType priorityReconnect,
const Os::Task::ParamType stackReconnect,
const Os::Task::ParamType cpuAffinityReconnect) {
// Reconnect Thread
FW_ASSERT(m_reconnectTask.getState() ==
Os::Task::State::NOT_STARTED); // It is a coding error to start this task multiple times
this->m_reconnectStop = false;
Fw::String reconnectName;
reconnectName.format("%s_reconnect", name.toChar());
Os::Task::Arguments reconnectArguments(reconnectName, SocketComponentHelper::reconnectTask, this, priorityReconnect,
stackReconnect, cpuAffinityReconnect);
Os::Task::Status reconnectStat = m_reconnectTask.start(reconnectArguments);
FW_ASSERT(Os::Task::OP_OK == reconnectStat, static_cast<FwAssertArgType>(reconnectStat));
// Read Thread
FW_ASSERT(m_task.getState() ==
Os::Task::State::NOT_STARTED); // It is a coding error to start this task multiple times
this->m_stop = false;
// Note: the first step is for the IP socket to open the port
Os::Task::Arguments arguments(name, SocketComponentHelper::readTask, this, priority, stack, cpuAffinity);
Os::Task::Status stat = m_task.start(arguments);
FW_ASSERT(Os::Task::OP_OK == stat, static_cast<FwAssertArgType>(stat));
}
SocketIpStatus SocketComponentHelper::open() {
SocketIpStatus status = SOCK_ANOTHER_THREAD_OPENING;
OpenState local_open = OpenState::OPEN;
// Scope to guard lock
{
Os::ScopeLock scopeLock(m_lock);
if (this->m_open == OpenState::NOT_OPEN) {
this->m_open = OpenState::OPENING;
local_open = this->m_open;
} else {
local_open = OpenState::SKIP;
}
}
if (local_open == OpenState::OPENING) {
FW_ASSERT(this->m_descriptor.fd == -1); // Ensure we are not opening an opened socket
status = this->getSocketHandler().open(this->m_descriptor);
// Lock scope
{
Os::ScopeLock scopeLock(m_lock);
if (Drv::SOCK_SUCCESS == status) {
this->m_open = OpenState::OPEN;
} else {
this->m_open = OpenState::NOT_OPEN;
this->m_descriptor.fd = -1;
}
}
// Notify connection on success outside locked scope
if (Drv::SOCK_SUCCESS == status) {
this->connected();
}
}
return status;
}
bool SocketComponentHelper::isOpened() {
Os::ScopeLock scopedLock(this->m_lock);
bool is_open = this->m_open == OpenState::OPEN;
return is_open;
}
void SocketComponentHelper::setAutomaticOpen(bool auto_open) {
Os::ScopeLock scopedLock(this->m_lock);
this->m_reopen = auto_open;
}
bool SocketComponentHelper::getAutomaticOpen() {
Os::ScopeLock scopedLock(this->m_lock);
return this->m_reopen;
}
SocketIpStatus SocketComponentHelper::reopen() {
SocketIpStatus status = SOCK_SUCCESS;
if (not this->isOpened()) {
// Check for auto-open before attempting to reopen
bool reopen = this->getAutomaticOpen();
if (not reopen) {
status = SOCK_AUTO_CONNECT_DISABLED;
// Open a network connection if it has not already been open
} else {
status = this->open();
if (status == SocketIpStatus::SOCK_ANOTHER_THREAD_OPENING) {
status = SocketIpStatus::SOCK_SUCCESS;
}
}
}
return status;
}
SocketIpStatus SocketComponentHelper::send(const U8* const data, const FwSizeType size) {
SocketIpStatus status = SOCK_SUCCESS;
this->m_lock.lock();
SocketDescriptor descriptor = this->m_descriptor;
this->m_lock.unlock();
// Prevent transmission before connection, or after a disconnect
if (descriptor.fd == -1) {
this->requestReconnect();
SocketIpStatus reconnectStat = this->waitForReconnect();
if (reconnectStat == SOCK_SUCCESS) {
// Refresh local copy after reopen
this->m_lock.lock();
descriptor = this->m_descriptor;
this->m_lock.unlock();
} else {
return reconnectStat;
}
}
status = this->getSocketHandler().send(descriptor, data, size);
if (status == SOCK_DISCONNECTED) {
this->close();
}
return status;
}
void SocketComponentHelper::shutdown() {
Os::ScopeLock scopedLock(this->m_lock);
this->getSocketHandler().shutdown(this->m_descriptor);
}
void SocketComponentHelper::close() {
Os::ScopeLock scopedLock(this->m_lock);
this->getSocketHandler().close(this->m_descriptor);
this->m_descriptor.fd = -1;
this->m_open = OpenState::NOT_OPEN;
}
/* Read Thread */
Os::Task::Status SocketComponentHelper::join() {
Os::Task::Status stat = m_task.join();
Os::Task::Status reconnectStat = this->joinReconnect();
if (stat == Os::Task::Status::OP_OK) {
return reconnectStat;
}
return stat;
}
void SocketComponentHelper::stop() {
// Scope to protect lock
{
Os::ScopeLock scopeLock(m_lock);
this->m_stop = true;
}
this->stopReconnect();
this->shutdown(); // Break out of any receives and fully shutdown
}
bool SocketComponentHelper::running() {
Os::ScopeLock scopedLock(this->m_lock);
bool running = not this->m_stop;
return running;
}
SocketIpStatus SocketComponentHelper::recv(U8* data, FwSizeType& size) {
SocketIpStatus status = SOCK_SUCCESS;
// Check for previously disconnected socket
this->m_lock.lock();
SocketDescriptor descriptor = this->m_descriptor;
this->m_lock.unlock();
if (descriptor.fd == -1) {
return SOCK_DISCONNECTED;
}
status = this->getSocketHandler().recv(descriptor, data, size);
if (status == SOCK_DISCONNECTED) {
this->close();
}
return status;
}
void SocketComponentHelper::readLoop() {
SocketIpStatus status = SOCK_SUCCESS;
do {
// Prevent transmission before connection, or after a disconnect
if ((not this->isOpened()) and this->running()) {
this->requestReconnect();
status = this->waitForReconnect();
// When reopen is disabled, just break as this is a exit condition for the loop
if (status == SOCK_AUTO_CONNECT_DISABLED) {
break;
}
}
// If the network connection is open, read from it
if (this->isOpened() and this->running()) {
Fw::Buffer buffer = this->getBuffer();
U8* data = buffer.getData();
FW_ASSERT(data);
FwSizeType size = buffer.getSize();
// recv blocks, so it may have been a while since its done an isOpened check
status = this->recv(data, size);
if ((status != SOCK_SUCCESS) && (status != SOCK_INTERRUPTED_TRY_AGAIN) &&
(status != SOCK_NO_DATA_AVAILABLE)) {
Fw::Logger::log("[WARNING] %s failed to recv from port with status %d and errno %d\n",
this->m_task.getName().toChar(), status, errno);
this->close();
buffer.setSize(0);
} else {
// Send out received data
buffer.setSize(size);
}
this->sendBuffer(buffer, status);
}
}
// This will loop until stopped. If auto-open is disabled, this will break when reopen returns disabled status
while (this->running());
// Close the socket
this->close(); // Close the port entirely
}
void SocketComponentHelper::readTask(void* pointer) {
FW_ASSERT(pointer);
SocketComponentHelper* self = reinterpret_cast<SocketComponentHelper*>(pointer);
self->readLoop();
}
/* Reconnect Thread */
Os::Task::Status SocketComponentHelper::joinReconnect() {
return m_reconnectTask.join();
}
void SocketComponentHelper::stopReconnect() {
Os::ScopeLock scopeLock(this->m_reconnectLock);
this->m_reconnectState = ReconnectState::NOT_RECONNECTING;
this->m_reconnectStop = true;
}
bool SocketComponentHelper::runningReconnect() {
Os::ScopeLock scopedLock(this->m_reconnectLock);
bool running = not this->m_reconnectStop;
return running;
}
void SocketComponentHelper::reconnectLoop() {
SocketIpStatus status = SOCK_SUCCESS;
while (this->runningReconnect()) {
// Check if we need to reconnect
bool reconnect = false;
{
Os::ScopeLock scopedLock(this->m_reconnectLock);
if (this->m_reconnectState == ReconnectState::REQUEST_RECONNECT) {
this->m_reconnectState = ReconnectState::RECONNECT_IN_PROGRESS;
reconnect = true;
}
// If we were already in or are now in RECONNECT_IN_PROGRESS we
// need to try to reconnect, again
else if (this->m_reconnectState == ReconnectState::RECONNECT_IN_PROGRESS) {
reconnect = true;
}
}
if (reconnect) {
status = this->reopen();
// Reopen Case 1: Auto Connect is disabled, so no longer
// try to reconnect
if (status == SOCK_AUTO_CONNECT_DISABLED) {
Os::ScopeLock scopedLock(this->m_reconnectLock);
this->m_reconnectState = ReconnectState::NOT_RECONNECTING;
}
// Reopen Case 2: Success, so no longer
// try to reconnect
else if (status == SOCK_SUCCESS) {
Os::ScopeLock scopedLock(this->m_reconnectLock);
this->m_reconnectState = ReconnectState::NOT_RECONNECTING;
}
// Reopen Case 3: Keep trying to reconnect - NO reconnect
// state change
else {
Fw::Logger::log("[WARNING] %s failed to open port with status %d and errno %d\n",
this->m_task.getName().toChar(), status, errno);
(void)Os::Task::delay(SOCKET_RETRY_INTERVAL);
}
} else {
// After a brief delay, we will loop again
(void)Os::Task::delay(this->m_reconnectCheckInterval);
}
}
}
void SocketComponentHelper::reconnectTask(void* pointer) {
FW_ASSERT(pointer);
SocketComponentHelper* self = reinterpret_cast<SocketComponentHelper*>(pointer);
self->reconnectLoop();
}
void SocketComponentHelper::requestReconnect() {
Os::ScopeLock scopedLock(this->m_reconnectLock);
if (m_reconnectState == ReconnectState::NOT_RECONNECTING) {
m_reconnectState = ReconnectState::REQUEST_RECONNECT;
}
return;
}
SocketIpStatus SocketComponentHelper::waitForReconnect(Fw::TimeInterval timeout) {
// Do not attempt to reconnect if auto reconnect config flag is disabled
if (!this->getAutomaticOpen()) {
return SOCK_AUTO_CONNECT_DISABLED;
}
Fw::TimeInterval elapsed = Fw::TimeInterval(0, 0);
while (elapsed < timeout) {
// If the reconnect thread is NOT reconnecting, we are done waiting
// If we are no longer running the reconnect thread, we are done waiting
{
Os::ScopeLock scopedLock(this->m_reconnectLock);
if (this->m_reconnectState == ReconnectState::NOT_RECONNECTING) {
break;
}
if (this->m_reconnectStop) {
break;
}
}
// Wait a bit before checking again
(void)Os::Task::delay(this->m_reconnectWaitInterval);
elapsed.add(this->m_reconnectWaitInterval.getSeconds(), this->m_reconnectWaitInterval.getUSeconds());
}
// If we have completed our loop, check if we are connected or if
// auto connect was disabled during our wait
if (this->isOpened()) {
return SOCK_SUCCESS;
}
// Check one more time if auto reconnect config flag got disabled
if (!this->getAutomaticOpen()) {
return SOCK_AUTO_CONNECT_DISABLED;
}
return SOCK_DISCONNECTED; // Indicates failure of this attempt, another reopen needed
}
} // namespace Drv