mirror of
https://github.com/esphome/esphome.git
synced 2025-09-26 23:22:21 +01:00
Optimize API connection loop performance (#9184)
This commit is contained in:
@@ -47,6 +47,11 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Schedule reboot if no clients connect within timeout
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
@@ -106,8 +111,6 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback(
|
||||
@@ -121,6 +124,16 @@ void APIServer::setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIServer::schedule_reboot_timeout_() {
|
||||
this->status_set_warning();
|
||||
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
|
||||
if (!global_api_server->is_connected()) {
|
||||
ESP_LOGE(TAG, "No clients; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void APIServer::loop() {
|
||||
// Accept new clients only if the socket exists and has incoming connections
|
||||
if (this->socket_ && this->socket_->ready()) {
|
||||
@@ -130,51 +143,61 @@ void APIServer::loop() {
|
||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
|
||||
// Clear warning status and cancel reboot when first client connects
|
||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||
this->status_clear_warning();
|
||||
this->cancel_timeout("api_reboot");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->clients_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process clients and remove disconnected ones in a single pass
|
||||
if (!this->clients_.empty()) {
|
||||
size_t client_index = 0;
|
||||
while (client_index < this->clients_.size()) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
if (client->remove_) {
|
||||
// Handle disconnection
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
||||
ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
}
|
||||
this->clients_.pop_back();
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
} else {
|
||||
// Process active client
|
||||
client->loop();
|
||||
client_index++; // Move to next client
|
||||
}
|
||||
// Check network connectivity once for all clients
|
||||
if (!network::is_connected()) {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->clients_) {
|
||||
client->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
|
||||
}
|
||||
// Continue to process and clean up the clients below
|
||||
}
|
||||
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (!this->is_connected()) {
|
||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||
ESP_LOGE(TAG, "No client connected; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->last_connected_ = now;
|
||||
this->status_clear_warning();
|
||||
size_t client_index = 0;
|
||||
while (client_index < this->clients_.size()) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
if (!client->remove_) {
|
||||
// Common case: process active client
|
||||
client->loop();
|
||||
client_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rare case: handle disconnection
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
||||
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str());
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
}
|
||||
this->clients_.pop_back();
|
||||
|
||||
// Schedule reboot when last client disconnects
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user