This commit is contained in:
J. Nick Koston
2026-02-23 21:10:38 -06:00
parent 86642ab9d7
commit 3e0cdc2404
4 changed files with 10 additions and 25 deletions

View File

@@ -126,13 +126,15 @@ def require_wake_loop_threadsafe() -> None:
"""Mark that wake_loop_threadsafe support is required by a component.
Call this from components that need to wake the main event loop from background threads.
On ESP32: Uses FreeRTOS task notifications (<1 us, no socket needed).
On other platforms: Uses a shared UDP loopback socket mechanism (~208 bytes RAM).
This enables the shared UDP loopback socket mechanism (~208 bytes RAM).
The socket is shared across all components that use this feature.
This call is a no-op if networking is not enabled in the configuration.
IMPORTANT: This is for background task context only, NOT ISR context.
IMPORTANT: This is for background thread context only, NOT ISR context.
Socket operations are not safe to call from ISR handlers.
On ESP32, FreeRTOS task notifications are used instead (no socket needed).
Example:
from esphome.components import socket

View File

@@ -611,10 +611,7 @@ void Application::unregister_socket_fd(int fd) {
if (i < this->socket_fds_.size() - 1)
this->socket_fds_[i] = this->socket_fds_.back();
this->socket_fds_.pop_back();
#ifdef USE_ESP32
// Unhook the socket's netconn callback
esphome_lwip_unhook_socket(fd);
#else
#ifndef USE_ESP32
this->socket_fds_changed_ = true;
// Only recalculate max_fd if we removed the current max
if (fd == this->max_fd_) {

View File

@@ -10,7 +10,7 @@
// Thread safety analysis
// ======================
// Three threads interact with this code:
// 1. Main loop task — calls init, has_data, hook, unhook
// 1. Main loop task — calls init, has_data, hook
// 2. LwIP TCP/IP task — calls event_callback (which reads s_original_callback, writes rcvevent)
// 3. Background tasks — call wake_main_loop
//
@@ -31,7 +31,8 @@
// the write), so it always sees the initialized value.
//
// sock->conn->callback (netconn_callback, 4-byte function pointer):
// Written by main loop in hook_socket() and unhook_socket().
// Written by main loop in hook_socket(). Never restored — all LwIP sockets share
// the same static event_callback, so the wrapper stays in place permanently.
// Read by TCP/IP thread when invoking the callback.
// Safe: 32-bit aligned pointer writes are atomic on Xtensa and RISC-V (ESP32).
// The TCP/IP thread will see either the old or new pointer atomically — never a
@@ -133,17 +134,6 @@ void esphome_lwip_hook_socket(int fd) {
sock->conn->callback = esphome_socket_event_callback;
}
void esphome_lwip_unhook_socket(int fd) {
struct lwip_sock *sock = lwip_socket_dbg_get_socket(fd);
if (sock == NULL || sock->conn == NULL)
return;
// Restore original callback. Atomic on ESP32 (32-bit aligned pointer write).
if (s_original_callback != NULL) {
sock->conn->callback = s_original_callback;
}
}
// Wake the main loop from another FreeRTOS task. NOT ISR-safe.
void esphome_lwip_wake_main_loop(void) {
TaskHandle_t task = s_main_loop_task;

View File

@@ -23,10 +23,6 @@ bool esphome_lwip_socket_has_data(int fd);
/// Must be called from the main loop after socket creation.
void esphome_lwip_hook_socket(int fd);
/// Unhook a socket's netconn callback, restoring the original event_callback.
/// Must be called from the main loop before closing the socket.
void esphome_lwip_unhook_socket(int fd);
/// Wake the main loop task from another FreeRTOS task — costs <1 us.
/// NOT ISR-safe — must only be called from task context.
void esphome_lwip_wake_main_loop(void);