1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-30 14:43:51 +00:00

Merge remote-tracking branch 'upstream/dev' into heap_scheduler_stress_component

This commit is contained in:
J. Nick Koston
2025-07-06 17:21:36 -05:00
13 changed files with 672 additions and 97 deletions

View File

@@ -0,0 +1,109 @@
esphome:
name: defer-fifo-simple
host:
logger:
level: DEBUG
api:
services:
- service: test_set_timeout
then:
- lambda: |-
// Test set_timeout with 0 delay (direct scheduler call)
static int set_timeout_order = 0;
static bool set_timeout_passed = true;
// Reset for this test
set_timeout_order = 0;
set_timeout_passed = true;
ESP_LOGD("defer_test", "Testing set_timeout(0) for FIFO order...");
for (int i = 0; i < 10; i++) {
int expected = i;
App.scheduler.set_timeout((Component*)nullptr, nullptr, 0, [expected]() {
ESP_LOGD("defer_test", "set_timeout(0) item %d executed, order %d", expected, set_timeout_order);
if (set_timeout_order != expected) {
ESP_LOGE("defer_test", "FIFO violation in set_timeout: expected %d but got execution order %d", expected, set_timeout_order);
set_timeout_passed = false;
}
set_timeout_order++;
if (set_timeout_order == 10) {
if (set_timeout_passed) {
ESP_LOGI("defer_test", "✓ Test PASSED - set_timeout(0) maintains FIFO order");
id(test_result)->trigger("passed");
} else {
ESP_LOGE("defer_test", "✗ Test FAILED - set_timeout(0) executed out of order");
id(test_result)->trigger("failed");
}
id(test_complete)->trigger("test_finished");
}
});
}
ESP_LOGD("defer_test", "Deferred 10 items using set_timeout(0), waiting for execution...");
- service: test_defer
then:
- lambda: |-
// Test defer() method (component method)
static int defer_order = 0;
static bool defer_passed = true;
// Reset for this test
defer_order = 0;
defer_passed = true;
ESP_LOGD("defer_test", "Testing defer() for FIFO order...");
// Create a test component class that exposes defer()
class TestComponent : public Component {
public:
void test_defer() {
for (int i = 0; i < 10; i++) {
int expected = i;
this->defer([expected]() {
ESP_LOGD("defer_test", "defer() item %d executed, order %d", expected, defer_order);
if (defer_order != expected) {
ESP_LOGE("defer_test", "FIFO violation in defer: expected %d but got execution order %d", expected, defer_order);
defer_passed = false;
}
defer_order++;
if (defer_order == 10) {
if (defer_passed) {
ESP_LOGI("defer_test", "✓ Test PASSED - defer() maintains FIFO order");
id(test_result)->trigger("passed");
} else {
ESP_LOGE("defer_test", "✗ Test FAILED - defer() executed out of order");
id(test_result)->trigger("failed");
}
id(test_complete)->trigger("test_finished");
}
});
}
}
};
// Use a static instance so it doesn't go out of scope
static TestComponent test_component;
test_component.test_defer();
ESP_LOGD("defer_test", "Deferred 10 items using defer(), waiting for execution...");
event:
- platform: template
name: "Test Complete"
id: test_complete
device_class: button
event_types:
- "test_finished"
- platform: template
name: "Test Result"
id: test_result
device_class: button
event_types:
- "passed"
- "failed"

View File

@@ -0,0 +1,38 @@
esphome:
name: defer-stress-test
external_components:
- source:
type: local
path: EXTERNAL_COMPONENT_PATH
components: [defer_stress_component]
host:
logger:
level: VERBOSE
defer_stress_component:
id: defer_stress
api:
services:
- service: run_stress_test
then:
- lambda: |-
id(defer_stress)->run_multi_thread_test();
event:
- platform: template
name: "Test Complete"
id: test_complete
device_class: button
event_types:
- "test_finished"
- platform: template
name: "Test Result"
id: test_result
device_class: button
event_types:
- "passed"
- "failed"

View File

@@ -0,0 +1,19 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
defer_stress_component_ns = cg.esphome_ns.namespace("defer_stress_component")
DeferStressComponent = defer_stress_component_ns.class_(
"DeferStressComponent", cg.Component
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(DeferStressComponent),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -0,0 +1,75 @@
#include "defer_stress_component.h"
#include "esphome/core/log.h"
#include <thread>
#include <atomic>
#include <vector>
#include <chrono>
namespace esphome {
namespace defer_stress_component {
static const char *const TAG = "defer_stress";
void DeferStressComponent::setup() { ESP_LOGCONFIG(TAG, "DeferStressComponent setup"); }
void DeferStressComponent::run_multi_thread_test() {
// Use member variables instead of static to avoid issues
this->total_defers_ = 0;
this->executed_defers_ = 0;
static constexpr int NUM_THREADS = 10;
static constexpr int DEFERS_PER_THREAD = 100;
ESP_LOGI(TAG, "Starting defer stress test - multi-threaded concurrent defers");
// Ensure we're starting clean
ESP_LOGI(TAG, "Initial counters: total=%d, executed=%d", this->total_defers_.load(), this->executed_defers_.load());
// Track start time
auto start_time = std::chrono::steady_clock::now();
// Create threads
std::vector<std::thread> threads;
ESP_LOGI(TAG, "Creating %d threads, each will defer %d callbacks", NUM_THREADS, DEFERS_PER_THREAD);
threads.reserve(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
threads.emplace_back([this, i]() {
ESP_LOGV(TAG, "Thread %d starting", i);
// Each thread directly calls defer() without any locking
for (int j = 0; j < DEFERS_PER_THREAD; j++) {
int defer_id = this->total_defers_.fetch_add(1);
ESP_LOGV(TAG, "Thread %d calling defer for request %d", i, defer_id);
// Capture this pointer safely for the lambda
auto *component = this;
// Directly call defer() from this thread - no locking!
this->defer([component, i, j, defer_id]() {
component->executed_defers_.fetch_add(1);
ESP_LOGV(TAG, "Executed defer %d (thread %d, index %d)", defer_id, i, j);
});
ESP_LOGV(TAG, "Thread %d called defer for request %d successfully", i, defer_id);
// Small random delay to increase contention
if (j % 10 == 0) {
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
}
ESP_LOGV(TAG, "Thread %d finished", i);
});
}
// Wait for all threads to complete
for (auto &t : threads) {
t.join();
}
auto end_time = std::chrono::steady_clock::now();
auto thread_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
ESP_LOGI(TAG, "All threads finished in %lldms. Created %d defer requests", thread_time, this->total_defers_.load());
}
} // namespace defer_stress_component
} // namespace esphome

View File

@@ -0,0 +1,20 @@
#pragma once
#include "esphome/core/component.h"
#include <atomic>
namespace esphome {
namespace defer_stress_component {
class DeferStressComponent : public Component {
public:
void setup() override;
void run_multi_thread_test();
private:
std::atomic<int> total_defers_{0};
std::atomic<int> executed_defers_{0};
};
} // namespace defer_stress_component
} // namespace esphome