Replaces the single load_bearing counter with three buckets measured
via a bounded (100µs) spin-poll of the task notification value after
the scan finds data:
race (<10µs) — notification arrived within ~10µs: callback-ordering
race between rcvevent write and xTaskNotifyGive a few
instructions later. Scan is noise.
micro (<100µs) — notification arrived within 100µs: still noise at
loop_interval scale.
stall (>=100µs) — notification did not arrive within the poll window.
Only this case could represent a real latency spike
that the scan is rescuing.
The 100µs spin cap is intentional: if we are wrong and this IS a real
stall, we only add 100µs to that one unlucky loop iteration.
The immediate ESP_LOGW on each hit now includes gap_us and the bucket
label so individual events can be investigated.
Adds a non-inline helper note_fast_select_load_bearing_() invoked only
when the scan found data and the task notification counter was 0.
Logs: hit sequence number, lwip_sock pointer, index in
monitored_sockets_, raw rcvevent value, delay_ms, and how many other
sockets also had data at that instant.
Hot path is unchanged — the helper is out-of-line so yield_with_select_
still inlines to the same code as before for the zero-hit path.
LibreTiny's FreeRTOS port predates ulTaskNotifyValueClear (added in
FreeRTOS 10.4.0). Fall back to a pessimistic 0 on non-ESP32 so
load_bearing becomes an upper bound == found_data on LibreTiny. Zero
there is still a valid proof that the scan is unused.
Adds three debug atomic counters around the pre-sleep socket scan in
yield_with_select_():
- fast_select_scan_total_ every scan
- fast_select_scan_found_data_ scan saw a socket with pending data
- fast_select_scan_load_bearing_ scan saw pending data AND the task
notification counter was 0 at scan start
Only the third counter represents a case the scan actually rescues: had
the scan not been present, ulTaskNotifyTake would have stalled up to
loop_interval ms. The other two cases are harmless (Take would have
returned immediately).
The notification value is peeked with ulTaskNotifyValueClear(nullptr, 0)
(a pure read — zero bits cleared, state untouched) BEFORE the scan loop.
Peeking before the scan makes the measurement TOCTOU-free: the value we
compare against is the value at the moment Take would have been called,
exactly the counterfactual we want to measure. Peeking after has_data
would race with the lwip callback firing during the scan.
Stats are logged via ESP_LOGD every 30s from Application::loop().
Background: PR #14475 removed the scan and was reverted because the API
connection's MAX_MESSAGES_PER_LOOP=5 throttle violated the ready()
contract (see #15589, #15590). With #15590 the contract is now
documented and honored, so the scan may now be removable. This PR
gathers evidence; if load_bearing stays 0 across ESP32/LibreTiny under
real workloads, the scan and these counters will be removed in a
follow-up.