Adopt the shared connect poll in async_tcp and name the refused implementation

This commit is contained in:
J. Nick Koston
2026-09-03 18:54:23 +02:00
parent 900638463a
commit 30da0e81fa
5 changed files with 19 additions and 43 deletions
+3 -4
View File
@@ -480,11 +480,10 @@ def _validate_outgoing_socket_implementation(config: ConfigType) -> ConfigType:
socket_conf = fv.full_config.get().get("socket") or {}
if (
socket_conf.get(socket.CONF_IMPLEMENTATION)
in socket.IMPLEMENTATIONS_WITHOUT_CONNECT
):
impl := socket_conf.get(socket.CONF_IMPLEMENTATION)
) in socket.IMPLEMENTATIONS_WITHOUT_CONNECT:
raise cv.Invalid(
"outgoing_connection is not supported with the lwip_tcp socket "
f"outgoing_connection is not supported with the {impl} socket "
"implementation because it cannot make outgoing connections",
path=[CONF_OUTGOING_CONNECTION],
)
@@ -97,45 +97,22 @@ void AsyncClient::loop() {
return;
if (connecting_) {
// For connecting, we need to check writability, not readability
// The Application's select() only monitors read FDs, so we do our own check here
// For ESP platforms lwip_select() might be faster, but this code isn't used
// on those platforms anyway. If it was, we'd fix the Application select()
// to report writability instead of doing it this way.
int fd = socket_->get_fd();
if (fd < 0) {
ESP_LOGW(TAG, "Invalid socket fd");
close();
return;
}
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);
struct timeval tv = {0, 0};
int ret = select(fd + 1, nullptr, &writefds, nullptr, &tv);
if (ret > 0 && FD_ISSET(fd, &writefds)) {
int error = 0;
socklen_t len = sizeof(error);
if (socket_->getsockopt(SOL_SOCKET, SO_ERROR, &error, &len) == 0 && error == 0) {
int err = 0;
switch (socket::poll_connect(*socket_, err)) {
case socket::ConnectPollResult::CONNECT_POLL_PENDING:
break;
case socket::ConnectPollResult::CONNECT_POLL_CONNECTED:
connecting_ = false;
connected_ = true;
if (connect_cb_)
connect_cb_(connect_arg_, this);
} else {
ESP_LOGW(TAG, "Connection failed: %d", error);
break;
case socket::ConnectPollResult::CONNECT_POLL_ERROR:
ESP_LOGW(TAG, "Connection failed: %d", err);
close();
if (error_cb_)
error_cb_(error_arg_, this, error);
}
} else if (ret < 0) {
const int err = errno;
ESP_LOGE(TAG, "Select error: %d", err);
close();
if (error_cb_)
error_cb_(error_arg_, this, err);
error_cb_(error_arg_, this, err);
break;
}
} else if (connected_) {
// For connected sockets, use the Application's select() results
+2 -2
View File
@@ -15,10 +15,10 @@ CODEOWNERS = ["@esphome/core"]
CONF_IMPLEMENTATION = "implementation"
IMPLEMENTATION_LWIP_TCP = "lwip_tcp"
# Implementations whose sockets cannot make outgoing connections
IMPLEMENTATIONS_WITHOUT_CONNECT = frozenset({IMPLEMENTATION_LWIP_TCP})
IMPLEMENTATION_LWIP_SOCKETS = "lwip_sockets"
IMPLEMENTATION_BSD_SOCKETS = "bsd_sockets"
# Implementations whose sockets cannot make outgoing connections
IMPLEMENTATIONS_WITHOUT_CONNECT = frozenset({IMPLEMENTATION_LWIP_TCP})
# Socket tracking infrastructure
# Components register their socket needs and platforms read this to configure appropriately
+1 -1
View File
@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components import binary_sensor, sensor
from esphome.components.const import CONF_HOST
import esphome.config_validation as cv
from esphome.const import (
CONF_BINARY_SENSORS,
@@ -14,7 +15,6 @@ AUTO_LOAD = ["socket"]
CODEOWNERS = ["@Links2004"]
DEPENDENCIES = ["network"]
CONF_HOST = "host"
CONF_PREFIX = "prefix"
statsd_component_ns = cg.esphome_ns.namespace("statsd")
+3 -3
View File
@@ -16,9 +16,9 @@ async def test_api_reboot_timeout(
"""Test that the device reboots when no API clients connect within the timeout."""
loop = asyncio.get_running_loop()
reboot_future = loop.create_future()
# The harness port probe counts as an unauthenticated client, so the
# reboot may report either form
reboot_pattern = re.compile(r"(No clients|none authenticated); rebooting")
# The harness port probe always connects without authenticating, so the
# reboot deterministically reports the unauthenticated form
reboot_pattern = re.compile(r"none authenticated; rebooting")
def check_output(line: str) -> None:
"""Check output for reboot message."""