Skip to content

[api] Extract cold code from APIServer::loop() hot path#13902

Merged
bdraco merged 12 commits intodevfrom
api-server-extract-accept
Feb 12, 2026
Merged

[api] Extract cold code from APIServer::loop() hot path#13902
bdraco merged 12 commits intodevfrom
api-server-extract-accept

Conversation

@bdraco
Copy link
Member

@bdraco bdraco commented Feb 10, 2026

What does this implement/fix?

Extract cold code from APIServer::loop() into separate noinline helpers to reduce icache pressure in the hot path.

1. Extract accept_new_connections_():
The socket accept loop (accept, getpeername, new APIConnection, emplace_back, status_clear_warning) only runs when new clients connect, which is rare compared to ~7000 loop iterations per minute.

2. Extract remove_client_():
The client removal path (unregister actions, close socket, swap-and-pop, status_set_warning, disconnect trigger) only runs when a client disconnects.

After extraction, loop() hot path is: socket ready check → client empty check → is_connected check → iterate clients calling loop().

Requires #13540

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Developer breaking change (an API change that could break external components)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

Pull request in esphome-docs with documentation (if applicable):

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Example entry for config.yaml:

# No configuration changes - internal optimization only

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

@bdraco bdraco requested a review from a team as a code owner February 10, 2026 02:34
@github-actions
Copy link
Contributor

👋 Hi there! This PR modifies 2 file(s) with codeowners.

@esphome/core - As codeowner(s) of the affected files, your review would be appreciated! 🙏

Note: Automatic review request may have failed, but you're still welcome to review.

@github-actions
Copy link
Contributor

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#13902
    components: [api]
    refresh: 1h

(Added by the PR bot)

@esphome esphome bot added chained-pr This PR is chained to another PR which must be merged before this one code-quality component: api labels Feb 10, 2026
@codecov-commenter
Copy link

codecov-commenter commented Feb 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.07%. Comparing base (38bba3f) to head (fd311d1).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #13902      +/-   ##
==========================================
- Coverage   74.10%   74.07%   -0.03%     
==========================================
  Files          55       55              
  Lines       11587    11587              
  Branches     1577     1577              
==========================================
- Hits         8586     8583       -3     
- Misses       2599     2601       +2     
- Partials      402      403       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 10, 2026

Memory Impact Analysis

Components: api
Platform: esp8266-ard

Metric Target Branch This PR Change
RAM 32,596 bytes 32,596 bytes ➡️ +0 bytes (0.00%)
Flash 448,959 bytes 449,007 bytes 📈 🔸 +48 bytes (+0.01%)
📊 Component Memory Breakdown
Component Target Flash PR Flash Change
[esphome]api 25,131 bytes 25,175 bytes 📈 🔸 +44 bytes (+0.18%)
🔍 Symbol-Level Changes (click to expand)

Changed Symbols

Symbol Target Size PR Size Change
esphome::api::APIServer::loop() 548 bytes 197 bytes 📉 -351 bytes (-64.05%)
esphome::api::APIServer::loop()::__pstr__ 21 bytes 25 bytes 📈 +4 bytes (+19.05%)

New Symbols (top 15)

Symbol Size
esphome::api::APIServer::accept_new_connections_() 239 bytes
esphome::api::APIServer::remove_client_(unsigned int) 156 bytes
esphome::api::APIServer::remove_client_(unsigned int)::__pstr__ 21 bytes
esphome::api::APIServer::accept_new_connections_()::__pstr__ 10 bytes

Note: This analysis measures static RAM and Flash usage only (compile-time allocation).
Dynamic memory (heap) cannot be measured automatically.
⚠️ You must test this PR on a real device to measure free heap and ensure no runtime memory issues.

This analysis runs automatically when components change. Memory usage is measured from a representative test configuration.

@bdraco bdraco changed the title [api] Extract accept_new_connections_() from APIServer::loop() hot path [api] Extract cold code from APIServer::loop() hot path Feb 10, 2026
Base automatically changed from peername_no_double_ram to dev February 10, 2026 18:23
@esphome esphome bot added component: voice_assistant and removed chained-pr This PR is chained to another PR which must be merged before this one labels Feb 10, 2026
…ccept

# Conflicts:
#	esphome/components/api/api_server.cpp
Copilot AI review requested due to automatic review settings February 11, 2026 12:48
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors APIServer::loop() to move rarely-executed connection-management logic out of the steady-state hot path, reducing instruction-cache pressure during frequent loop iterations in the API server component.

Changes:

  • Extracted the incoming-connection accept loop into APIServer::accept_new_connections_() (marked noinline).
  • Extracted disconnected-client cleanup into APIServer::remove_client_() (marked noinline) while preserving swap-and-pop behavior.
  • Simplified the main loop() flow to focus on readiness checks and per-client loop() calls.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
esphome/components/api/api_server.h Declares two new noinline helper methods for accept/remove paths.
esphome/components/api/api_server.cpp Moves cold accept/removal code out of APIServer::loop() into the new helpers.

@bdraco
Copy link
Member Author

bdraco commented Feb 12, 2026

thanks

@bdraco bdraco merged commit 0e14333 into dev Feb 12, 2026
30 checks passed
@bdraco bdraco deleted the api-server-extract-accept branch February 12, 2026 17:04
@jesserockz jesserockz added this to the 2026.2.0b2 milestone Feb 14, 2026
@github-actions github-actions bot locked and limited conversation to collaborators Feb 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants