fix(tcp): real task join in stop() + atomic _last_connect_attempt (greptile)

- stop() now waits on a _task_done flag the task sets right before exiting,
  instead of a fixed sleep. Closes a use-after-free window where an in-flight
  connect() overrunning CONNECT_TIMEOUT_MS (slow DNS) could touch `this` after
  ~TCPClientInterface() freed it.
- _last_connect_attempt is now std::atomic<uint32_t> — it's read/written by
  task_loop() (core 0) and handle_disconnect() (core 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
This commit is contained in:
torlando-agent[bot]
2026-06-19 22:23:03 -04:00
co-authored by Claude Opus 4.8
parent 988e42c52c
commit 7148859582
2 changed files with 17 additions and 4 deletions
+11 -4
View File
@@ -265,7 +265,9 @@ void TCPClientInterface::handle_disconnect() {
#ifdef ARDUINO
/*static*/ void TCPClientInterface::tcp_task(void* arg) {
static_cast<TCPClientInterface*>(arg)->task_loop();
auto* self = static_cast<TCPClientInterface*>(arg);
self->task_loop();
self->_task_done = true; // let stop() join before the object is freed
vTaskDelete(nullptr);
}
@@ -299,11 +301,16 @@ void TCPClientInterface::task_loop() {
/*virtual*/ void TCPClientInterface::stop() {
#ifdef ARDUINO
// Stop the connect task first; wait long enough for any in-flight connect()
// (bounded by CONNECT_TIMEOUT_MS) to finish so we don't close _client under it.
// Join the task: signal it, then wait until it has actually left task_loop()
// before tearing anything down. An in-flight connect() can overrun
// CONNECT_TIMEOUT_MS on a slow DNS server, and ~TCPClientInterface() calls
// stop() — returning early would risk a use-after-free on `this`.
_task_running = false;
if (_task_handle != nullptr) {
vTaskDelay(pdMS_TO_TICKS(CONNECT_TIMEOUT_MS + 500));
uint32_t deadline = millis() + CONNECT_TIMEOUT_MS + 2000;
while (!_task_done && (int32_t)(millis() - deadline) < 0) {
vTaskDelay(pdMS_TO_TICKS(20));
}
_task_handle = nullptr;
}
_conn_state.store(DISCONNECTED);