mirror of
https://github.com/esphome/esphome.git
synced 2025-07-30 15:16:37 +00:00
[scheduler] Eliminate more runtime string allocations from retry (#9930)
This commit is contained in:
parent
f9453f9642
commit
85435e6b5f
@ -83,6 +83,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
item->type = type;
|
item->type = type;
|
||||||
item->callback = std::move(func);
|
item->callback = std::move(func);
|
||||||
item->remove = false;
|
item->remove = false;
|
||||||
|
item->is_retry = is_retry;
|
||||||
|
|
||||||
#ifndef ESPHOME_THREAD_SINGLE
|
#ifndef ESPHOME_THREAD_SINGLE
|
||||||
// Special handling for defer() (delay = 0, type = TIMEOUT)
|
// Special handling for defer() (delay = 0, type = TIMEOUT)
|
||||||
@ -134,8 +135,8 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
|
|
||||||
// For retries, check if there's a cancelled timeout first
|
// For retries, check if there's a cancelled timeout first
|
||||||
if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT &&
|
if (is_retry && name_cstr != nullptr && type == SchedulerItem::TIMEOUT &&
|
||||||
(has_cancelled_timeout_in_container_(this->items_, component, name_cstr) ||
|
(has_cancelled_timeout_in_container_(this->items_, component, name_cstr, /* match_retry= */ true) ||
|
||||||
has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr))) {
|
has_cancelled_timeout_in_container_(this->to_add_, component, name_cstr, /* match_retry= */ true))) {
|
||||||
// Skip scheduling - the retry was cancelled
|
// Skip scheduling - the retry was cancelled
|
||||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr);
|
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item", name_cstr);
|
||||||
@ -198,25 +199,27 @@ void retry_handler(const std::shared_ptr<RetryArgs> &args) {
|
|||||||
// second execution of `func` happens after `initial_wait_time`
|
// second execution of `func` happens after `initial_wait_time`
|
||||||
args->scheduler->set_timer_common_(
|
args->scheduler->set_timer_common_(
|
||||||
args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval,
|
args->component, Scheduler::SchedulerItem::TIMEOUT, false, &args->name, args->current_interval,
|
||||||
[args]() { retry_handler(args); }, true);
|
[args]() { retry_handler(args); }, /* is_retry= */ true);
|
||||||
// backoff_increase_factor applied to third & later executions
|
// backoff_increase_factor applied to third & later executions
|
||||||
args->current_interval *= args->backoff_increase_factor;
|
args->current_interval *= args->backoff_increase_factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
|
void HOT Scheduler::set_retry_common_(Component *component, bool is_static_string, const void *name_ptr,
|
||||||
uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
|
uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
float backoff_increase_factor) {
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor) {
|
||||||
if (!name.empty())
|
const char *name_cstr = this->get_name_cstr_(is_static_string, name_ptr);
|
||||||
this->cancel_retry(component, name);
|
|
||||||
|
if (name_cstr != nullptr)
|
||||||
|
this->cancel_retry(component, name_cstr);
|
||||||
|
|
||||||
if (initial_wait_time == SCHEDULER_DONT_RUN)
|
if (initial_wait_time == SCHEDULER_DONT_RUN)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)",
|
ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)",
|
||||||
name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor);
|
name_cstr ? name_cstr : "", initial_wait_time, max_attempts, backoff_increase_factor);
|
||||||
|
|
||||||
if (backoff_increase_factor < 0.0001) {
|
if (backoff_increase_factor < 0.0001) {
|
||||||
ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str());
|
ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name_cstr ? name_cstr : "");
|
||||||
backoff_increase_factor = 1;
|
backoff_increase_factor = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,15 +228,36 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin
|
|||||||
args->retry_countdown = max_attempts;
|
args->retry_countdown = max_attempts;
|
||||||
args->current_interval = initial_wait_time;
|
args->current_interval = initial_wait_time;
|
||||||
args->component = component;
|
args->component = component;
|
||||||
args->name = "retry$" + name;
|
args->name = name_cstr ? name_cstr : ""; // Convert to std::string for RetryArgs
|
||||||
args->backoff_increase_factor = backoff_increase_factor;
|
args->backoff_increase_factor = backoff_increase_factor;
|
||||||
args->scheduler = this;
|
args->scheduler = this;
|
||||||
|
|
||||||
// First execution of `func` immediately
|
// First execution of `func` immediately - use set_timer_common_ with is_retry=true
|
||||||
this->set_timeout(component, args->name, 0, [args]() { retry_handler(args); });
|
this->set_timer_common_(
|
||||||
|
component, SchedulerItem::TIMEOUT, false, &args->name, 0, [args]() { retry_handler(args); },
|
||||||
|
/* is_retry= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
|
||||||
|
uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
|
||||||
|
float backoff_increase_factor) {
|
||||||
|
this->set_retry_common_(component, false, &name, initial_wait_time, max_attempts, std::move(func),
|
||||||
|
backoff_increase_factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HOT Scheduler::set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor) {
|
||||||
|
this->set_retry_common_(component, true, name, initial_wait_time, max_attempts, std::move(func),
|
||||||
|
backoff_increase_factor);
|
||||||
}
|
}
|
||||||
bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
|
bool HOT Scheduler::cancel_retry(Component *component, const std::string &name) {
|
||||||
return this->cancel_timeout(component, "retry$" + name);
|
return this->cancel_retry(component, name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HOT Scheduler::cancel_retry(Component *component, const char *name) {
|
||||||
|
// Cancel timeouts that have is_retry flag set
|
||||||
|
LockGuard guard{this->lock_};
|
||||||
|
return this->cancel_item_locked_(component, name, SchedulerItem::TIMEOUT, /* match_retry= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
||||||
@ -479,7 +503,8 @@ bool HOT Scheduler::cancel_item_(Component *component, bool is_static_string, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper to cancel items by name - must be called with lock held
|
// Helper to cancel items by name - must be called with lock held
|
||||||
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type) {
|
bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_cstr, SchedulerItem::Type type,
|
||||||
|
bool match_retry) {
|
||||||
// Early return if name is invalid - no items to cancel
|
// Early return if name is invalid - no items to cancel
|
||||||
if (name_cstr == nullptr) {
|
if (name_cstr == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
@ -492,7 +517,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
// Only check defer queue for timeouts (intervals never go there)
|
// Only check defer queue for timeouts (intervals never go there)
|
||||||
if (type == SchedulerItem::TIMEOUT) {
|
if (type == SchedulerItem::TIMEOUT) {
|
||||||
for (auto &item : this->defer_queue_) {
|
for (auto &item : this->defer_queue_) {
|
||||||
if (this->matches_item_(item, component, name_cstr, type)) {
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||||
item->remove = true;
|
item->remove = true;
|
||||||
total_cancelled++;
|
total_cancelled++;
|
||||||
}
|
}
|
||||||
@ -502,7 +527,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
|
|
||||||
// Cancel items in the main heap
|
// Cancel items in the main heap
|
||||||
for (auto &item : this->items_) {
|
for (auto &item : this->items_) {
|
||||||
if (this->matches_item_(item, component, name_cstr, type)) {
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||||
item->remove = true;
|
item->remove = true;
|
||||||
total_cancelled++;
|
total_cancelled++;
|
||||||
this->to_remove_++; // Track removals for heap items
|
this->to_remove_++; // Track removals for heap items
|
||||||
@ -511,7 +536,7 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
|
|||||||
|
|
||||||
// Cancel items in to_add_
|
// Cancel items in to_add_
|
||||||
for (auto &item : this->to_add_) {
|
for (auto &item : this->to_add_) {
|
||||||
if (this->matches_item_(item, component, name_cstr, type)) {
|
if (this->matches_item_(item, component, name_cstr, type, match_retry)) {
|
||||||
item->remove = true;
|
item->remove = true;
|
||||||
total_cancelled++;
|
total_cancelled++;
|
||||||
// Don't track removals for to_add_ items
|
// Don't track removals for to_add_ items
|
||||||
|
@ -61,7 +61,10 @@ class Scheduler {
|
|||||||
bool cancel_interval(Component *component, const char *name);
|
bool cancel_interval(Component *component, const char *name);
|
||||||
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
|
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
||||||
|
void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
||||||
bool cancel_retry(Component *component, const std::string &name);
|
bool cancel_retry(Component *component, const std::string &name);
|
||||||
|
bool cancel_retry(Component *component, const char *name);
|
||||||
|
|
||||||
// Calculate when the next scheduled item should run
|
// Calculate when the next scheduled item should run
|
||||||
// @param now Fresh timestamp from millis() - must not be stale/cached
|
// @param now Fresh timestamp from millis() - must not be stale/cached
|
||||||
@ -98,11 +101,18 @@ class Scheduler {
|
|||||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||||
bool remove : 1;
|
bool remove : 1;
|
||||||
bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
|
bool name_is_dynamic : 1; // True if name was dynamically allocated (needs delete[])
|
||||||
// 5 bits padding
|
bool is_retry : 1; // True if this is a retry timeout
|
||||||
|
// 4 bits padding
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
SchedulerItem()
|
SchedulerItem()
|
||||||
: component(nullptr), interval(0), next_execution_(0), type(TIMEOUT), remove(false), name_is_dynamic(false) {
|
: component(nullptr),
|
||||||
|
interval(0),
|
||||||
|
next_execution_(0),
|
||||||
|
type(TIMEOUT),
|
||||||
|
remove(false),
|
||||||
|
name_is_dynamic(false),
|
||||||
|
is_retry(false) {
|
||||||
name_.static_name = nullptr;
|
name_.static_name = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +166,10 @@ class Scheduler {
|
|||||||
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
|
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
|
||||||
uint32_t delay, std::function<void()> func, bool is_retry = false);
|
uint32_t delay, std::function<void()> func, bool is_retry = false);
|
||||||
|
|
||||||
|
// Common implementation for retry
|
||||||
|
void set_retry_common_(Component *component, bool is_static_string, const void *name_ptr, uint32_t initial_wait_time,
|
||||||
|
uint8_t max_attempts, std::function<RetryResult(uint8_t)> func, float backoff_increase_factor);
|
||||||
|
|
||||||
uint64_t millis_64_(uint32_t now);
|
uint64_t millis_64_(uint32_t now);
|
||||||
// Cleanup logically deleted items from the scheduler
|
// Cleanup logically deleted items from the scheduler
|
||||||
// Returns the number of items remaining after cleanup
|
// Returns the number of items remaining after cleanup
|
||||||
@ -165,7 +179,7 @@ class Scheduler {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to cancel items by name - must be called with lock held
|
// Helper to cancel items by name - must be called with lock held
|
||||||
bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type);
|
bool cancel_item_locked_(Component *component, const char *name, SchedulerItem::Type type, bool match_retry = false);
|
||||||
|
|
||||||
// Helper to extract name as const char* from either static string or std::string
|
// Helper to extract name as const char* from either static string or std::string
|
||||||
inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) {
|
inline const char *get_name_cstr_(bool is_static_string, const void *name_ptr) {
|
||||||
@ -177,8 +191,9 @@ class Scheduler {
|
|||||||
|
|
||||||
// Helper function to check if item matches criteria for cancellation
|
// Helper function to check if item matches criteria for cancellation
|
||||||
inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
|
inline bool HOT matches_item_(const std::unique_ptr<SchedulerItem> &item, Component *component, const char *name_cstr,
|
||||||
SchedulerItem::Type type, bool skip_removed = true) const {
|
SchedulerItem::Type type, bool match_retry, bool skip_removed = true) const {
|
||||||
if (item->component != component || item->type != type || (skip_removed && item->remove)) {
|
if (item->component != component || item->type != type || (skip_removed && item->remove) ||
|
||||||
|
(match_retry && !item->is_retry)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const char *item_name = item->get_name();
|
const char *item_name = item->get_name();
|
||||||
@ -206,10 +221,11 @@ class Scheduler {
|
|||||||
|
|
||||||
// Template helper to check if any item in a container matches our criteria
|
// Template helper to check if any item in a container matches our criteria
|
||||||
template<typename Container>
|
template<typename Container>
|
||||||
bool has_cancelled_timeout_in_container_(const Container &container, Component *component,
|
bool has_cancelled_timeout_in_container_(const Container &container, Component *component, const char *name_cstr,
|
||||||
const char *name_cstr) const {
|
bool match_retry) const {
|
||||||
for (const auto &item : container) {
|
for (const auto &item : container) {
|
||||||
if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, false)) {
|
if (item->remove && this->matches_item_(item, component, name_cstr, SchedulerItem::TIMEOUT, match_retry,
|
||||||
|
/* skip_removed= */ false)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,15 @@ globals:
|
|||||||
- id: multiple_same_name_counter
|
- id: multiple_same_name_counter
|
||||||
type: int
|
type: int
|
||||||
initial_value: '0'
|
initial_value: '0'
|
||||||
|
- id: const_char_retry_counter
|
||||||
|
type: int
|
||||||
|
initial_value: '0'
|
||||||
|
- id: static_char_retry_counter
|
||||||
|
type: int
|
||||||
|
initial_value: '0'
|
||||||
|
- id: mixed_cancel_result
|
||||||
|
type: bool
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
# Using different component types for each test to ensure isolation
|
# Using different component types for each test to ensure isolation
|
||||||
sensor:
|
sensor:
|
||||||
@ -229,6 +238,56 @@ script:
|
|||||||
return RetryResult::RETRY;
|
return RetryResult::RETRY;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
# Test 8: Const char* overloads
|
||||||
|
- logger.log: "=== Test 8: Const char* overloads ==="
|
||||||
|
- lambda: |-
|
||||||
|
auto *component = id(simple_retry_sensor);
|
||||||
|
|
||||||
|
// Test 8a: Direct string literal
|
||||||
|
App.scheduler.set_retry(component, "const_char_test", 30, 2,
|
||||||
|
[](uint8_t retry_countdown) {
|
||||||
|
id(const_char_retry_counter)++;
|
||||||
|
ESP_LOGI("test", "Const char retry %d", id(const_char_retry_counter));
|
||||||
|
return RetryResult::DONE;
|
||||||
|
});
|
||||||
|
|
||||||
|
# Test 9: Static const char* variable
|
||||||
|
- logger.log: "=== Test 9: Static const char* ==="
|
||||||
|
- lambda: |-
|
||||||
|
auto *component = id(backoff_retry_sensor);
|
||||||
|
|
||||||
|
static const char* STATIC_NAME = "static_retry_test";
|
||||||
|
App.scheduler.set_retry(component, STATIC_NAME, 20, 1,
|
||||||
|
[](uint8_t retry_countdown) {
|
||||||
|
id(static_char_retry_counter)++;
|
||||||
|
ESP_LOGI("test", "Static const char retry %d", id(static_char_retry_counter));
|
||||||
|
return RetryResult::DONE;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel with same static const char*
|
||||||
|
App.scheduler.set_timeout(component, "static_cancel", 10, []() {
|
||||||
|
static const char* STATIC_NAME = "static_retry_test";
|
||||||
|
bool result = App.scheduler.cancel_retry(id(backoff_retry_sensor), STATIC_NAME);
|
||||||
|
ESP_LOGI("test", "Static cancel result: %s", result ? "true" : "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
# Test 10: Mix string and const char* cancel
|
||||||
|
- logger.log: "=== Test 10: Mixed string/const char* ==="
|
||||||
|
- lambda: |-
|
||||||
|
auto *component = id(immediate_done_sensor);
|
||||||
|
|
||||||
|
// Set with std::string
|
||||||
|
std::string str_name = "mixed_retry";
|
||||||
|
App.scheduler.set_retry(component, str_name, 40, 3,
|
||||||
|
[](uint8_t retry_countdown) {
|
||||||
|
ESP_LOGI("test", "Mixed retry - should be cancelled");
|
||||||
|
return RetryResult::RETRY;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel with const char*
|
||||||
|
id(mixed_cancel_result) = App.scheduler.cancel_retry(component, "mixed_retry");
|
||||||
|
ESP_LOGI("test", "Mixed cancel result: %s", id(mixed_cancel_result) ? "true" : "false");
|
||||||
|
|
||||||
# Wait for all tests to complete before reporting
|
# Wait for all tests to complete before reporting
|
||||||
- delay: 500ms
|
- delay: 500ms
|
||||||
|
|
||||||
@ -242,4 +301,7 @@ script:
|
|||||||
ESP_LOGI("test", "Empty name retry counter: %d (expected 1-2)", id(empty_name_retry_counter));
|
ESP_LOGI("test", "Empty name retry counter: %d (expected 1-2)", id(empty_name_retry_counter));
|
||||||
ESP_LOGI("test", "Component retry counter: %d (expected 2)", id(script_retry_counter));
|
ESP_LOGI("test", "Component retry counter: %d (expected 2)", id(script_retry_counter));
|
||||||
ESP_LOGI("test", "Multiple same name counter: %d (expected 20+)", id(multiple_same_name_counter));
|
ESP_LOGI("test", "Multiple same name counter: %d (expected 20+)", id(multiple_same_name_counter));
|
||||||
|
ESP_LOGI("test", "Const char retry counter: %d (expected 1)", id(const_char_retry_counter));
|
||||||
|
ESP_LOGI("test", "Static char retry counter: %d (expected 1)", id(static_char_retry_counter));
|
||||||
|
ESP_LOGI("test", "Mixed cancel result: %s (expected true)", id(mixed_cancel_result) ? "true" : "false");
|
||||||
ESP_LOGI("test", "All retry tests completed");
|
ESP_LOGI("test", "All retry tests completed");
|
||||||
|
@ -23,6 +23,9 @@ async def test_scheduler_retry_test(
|
|||||||
empty_name_retry_done = asyncio.Event()
|
empty_name_retry_done = asyncio.Event()
|
||||||
component_retry_done = asyncio.Event()
|
component_retry_done = asyncio.Event()
|
||||||
multiple_name_done = asyncio.Event()
|
multiple_name_done = asyncio.Event()
|
||||||
|
const_char_done = asyncio.Event()
|
||||||
|
static_char_done = asyncio.Event()
|
||||||
|
mixed_cancel_done = asyncio.Event()
|
||||||
test_complete = asyncio.Event()
|
test_complete = asyncio.Event()
|
||||||
|
|
||||||
# Track retry counts
|
# Track retry counts
|
||||||
@ -33,16 +36,20 @@ async def test_scheduler_retry_test(
|
|||||||
empty_name_retry_count = 0
|
empty_name_retry_count = 0
|
||||||
component_retry_count = 0
|
component_retry_count = 0
|
||||||
multiple_name_count = 0
|
multiple_name_count = 0
|
||||||
|
const_char_retry_count = 0
|
||||||
|
static_char_retry_count = 0
|
||||||
|
|
||||||
# Track specific test results
|
# Track specific test results
|
||||||
cancel_result = None
|
cancel_result = None
|
||||||
empty_cancel_result = None
|
empty_cancel_result = None
|
||||||
|
mixed_cancel_result = None
|
||||||
backoff_intervals = []
|
backoff_intervals = []
|
||||||
|
|
||||||
def on_log_line(line: str) -> None:
|
def on_log_line(line: str) -> None:
|
||||||
nonlocal simple_retry_count, backoff_retry_count, immediate_done_count
|
nonlocal simple_retry_count, backoff_retry_count, immediate_done_count
|
||||||
nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count
|
nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count
|
||||||
nonlocal multiple_name_count, cancel_result, empty_cancel_result
|
nonlocal multiple_name_count, const_char_retry_count, static_char_retry_count
|
||||||
|
nonlocal cancel_result, empty_cancel_result, mixed_cancel_result
|
||||||
|
|
||||||
# Strip ANSI color codes
|
# Strip ANSI color codes
|
||||||
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
|
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
|
||||||
@ -106,6 +113,27 @@ async def test_scheduler_retry_test(
|
|||||||
if multiple_name_count >= 20:
|
if multiple_name_count >= 20:
|
||||||
multiple_name_done.set()
|
multiple_name_done.set()
|
||||||
|
|
||||||
|
# Const char retry test
|
||||||
|
elif "Const char retry" in clean_line:
|
||||||
|
if match := re.search(r"Const char retry (\d+)", clean_line):
|
||||||
|
const_char_retry_count = int(match.group(1))
|
||||||
|
const_char_done.set()
|
||||||
|
|
||||||
|
# Static const char retry test
|
||||||
|
elif "Static const char retry" in clean_line:
|
||||||
|
if match := re.search(r"Static const char retry (\d+)", clean_line):
|
||||||
|
static_char_retry_count = int(match.group(1))
|
||||||
|
static_char_done.set()
|
||||||
|
|
||||||
|
elif "Static cancel result:" in clean_line:
|
||||||
|
# This is part of test 9, but we don't track it separately
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Mixed cancel test
|
||||||
|
elif "Mixed cancel result:" in clean_line:
|
||||||
|
mixed_cancel_result = "true" in clean_line
|
||||||
|
mixed_cancel_done.set()
|
||||||
|
|
||||||
# Test completion
|
# Test completion
|
||||||
elif "All retry tests completed" in clean_line:
|
elif "All retry tests completed" in clean_line:
|
||||||
test_complete.set()
|
test_complete.set()
|
||||||
@ -227,6 +255,40 @@ async def test_scheduler_retry_test(
|
|||||||
f"Expected multiple name count >= 20 (second retry only), got {multiple_name_count}"
|
f"Expected multiple name count >= 20 (second retry only), got {multiple_name_count}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Wait for const char retry test
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(const_char_done.wait(), timeout=1.0)
|
||||||
|
except TimeoutError:
|
||||||
|
pytest.fail(
|
||||||
|
f"Const char retry test did not complete. Count: {const_char_retry_count}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert const_char_retry_count == 1, (
|
||||||
|
f"Expected 1 const char retry call, got {const_char_retry_count}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for static char retry test
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(static_char_done.wait(), timeout=1.0)
|
||||||
|
except TimeoutError:
|
||||||
|
pytest.fail(
|
||||||
|
f"Static char retry test did not complete. Count: {static_char_retry_count}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert static_char_retry_count == 1, (
|
||||||
|
f"Expected 1 static char retry call, got {static_char_retry_count}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for mixed cancel test
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(mixed_cancel_done.wait(), timeout=1.0)
|
||||||
|
except TimeoutError:
|
||||||
|
pytest.fail("Mixed cancel test did not complete")
|
||||||
|
|
||||||
|
assert mixed_cancel_result is True, (
|
||||||
|
"Mixed string/const char cancel should have succeeded"
|
||||||
|
)
|
||||||
|
|
||||||
# Wait for test completion
|
# Wait for test completion
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(test_complete.wait(), timeout=1.0)
|
await asyncio.wait_for(test_complete.wait(), timeout=1.0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user