Make retry scheduler efficient (#3225)

This commit is contained in:
Otto Winter 2022-05-10 21:54:00 +02:00 committed by GitHub
parent e541ae400c
commit 235a97ea10
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 49 deletions

View file

@ -61,7 +61,7 @@ extern const uint32_t STATUS_LED_OK;
extern const uint32_t STATUS_LED_WARNING; extern const uint32_t STATUS_LED_WARNING;
extern const uint32_t STATUS_LED_ERROR; extern const uint32_t STATUS_LED_ERROR;
enum RetryResult { DONE, RETRY }; enum class RetryResult { DONE, RETRY };
class Component { class Component {
public: public:

View file

@ -14,7 +14,7 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
// #define ESPHOME_DEBUG_SCHEDULER // #define ESPHOME_DEBUG_SCHEDULER
void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout, void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout,
std::function<void()> &&func) { std::function<void()> func) {
const uint32_t now = this->millis_(); const uint32_t now = this->millis_();
if (!name.empty()) if (!name.empty())
@ -32,7 +32,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
item->timeout = timeout; item->timeout = timeout;
item->last_execution = now; item->last_execution = now;
item->last_execution_major = this->millis_major_; item->last_execution_major = this->millis_major_;
item->void_callback = std::move(func); item->callback = std::move(func);
item->remove = false; item->remove = false;
this->push_(std::move(item)); this->push_(std::move(item));
} }
@ -40,7 +40,7 @@ bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name
return this->cancel_item_(component, name, SchedulerItem::TIMEOUT); return this->cancel_item_(component, name, SchedulerItem::TIMEOUT);
} }
void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval, void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval,
std::function<void()> &&func) { std::function<void()> func) {
const uint32_t now = this->millis_(); const uint32_t now = this->millis_();
if (!name.empty()) if (!name.empty())
@ -65,7 +65,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
item->last_execution_major = this->millis_major_; item->last_execution_major = this->millis_major_;
if (item->last_execution > now) if (item->last_execution > now)
item->last_execution_major--; item->last_execution_major--;
item->void_callback = std::move(func); item->callback = std::move(func);
item->remove = false; item->remove = false;
this->push_(std::move(item)); this->push_(std::move(item));
} }
@ -73,37 +73,48 @@ bool HOT Scheduler::cancel_interval(Component *component, const std::string &nam
return this->cancel_item_(component, name, SchedulerItem::INTERVAL); return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
} }
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, struct RetryArgs {
uint8_t max_attempts, std::function<RetryResult()> &&func, std::function<RetryResult()> func;
float backoff_increase_factor) { uint8_t retry_countdown;
const uint32_t now = this->millis_(); uint32_t current_interval;
Component *component;
std::string name;
float backoff_increase_factor;
Scheduler *scheduler;
};
static void retry_handler(const std::shared_ptr<RetryArgs> &args) {
RetryResult retry_result = args->func();
if (retry_result == RetryResult::DONE || --args->retry_countdown <= 0)
return;
args->current_interval *= args->backoff_increase_factor;
args->scheduler->set_timeout(args->component, args->name, args->current_interval, [args]() { retry_handler(args); });
}
void HOT Scheduler::set_retry(Component *component, const std::string &name, uint32_t initial_wait_time,
uint8_t max_attempts, std::function<RetryResult()> func, float backoff_increase_factor) {
if (!name.empty()) if (!name.empty())
this->cancel_retry(component, name); this->cancel_retry(component, name);
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=%u,max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(),
initial_wait_time, max_attempts, backoff_increase_factor); initial_wait_time, max_attempts, backoff_increase_factor);
auto item = make_unique<SchedulerItem>(); auto args = std::make_shared<RetryArgs>();
item->component = component; args->func = std::move(func);
item->name = name; args->retry_countdown = max_attempts;
item->type = SchedulerItem::RETRY; args->current_interval = initial_wait_time;
item->interval = initial_wait_time; args->component = component;
item->retry_countdown = max_attempts; args->name = "retry$" + name;
item->backoff_multiplier = backoff_increase_factor; args->backoff_increase_factor = backoff_increase_factor;
item->last_execution = now - initial_wait_time; args->scheduler = this;
item->last_execution_major = this->millis_major_;
if (item->last_execution > now) this->set_timeout(component, args->name, initial_wait_time, [args]() { retry_handler(args); });
item->last_execution_major--;
item->retry_callback = std::move(func);
item->remove = false;
this->push_(std::move(item));
} }
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_item_(component, name, SchedulerItem::RETRY); return this->cancel_timeout(component, "retry$" + name);
} }
optional<uint32_t> HOT Scheduler::next_schedule_in() { optional<uint32_t> HOT Scheduler::next_schedule_in() {
@ -162,7 +173,6 @@ void HOT Scheduler::call() {
} }
while (!this->empty_()) { while (!this->empty_()) {
RetryResult retry_result = RETRY;
// use scoping to indicate visibility of `item` variable // use scoping to indicate visibility of `item` variable
{ {
// Don't copy-by value yet // Don't copy-by value yet
@ -191,11 +201,7 @@ void HOT Scheduler::call() {
// - timeouts/intervals get cancelled // - timeouts/intervals get cancelled
{ {
WarnIfComponentBlockingGuard guard{item->component}; WarnIfComponentBlockingGuard guard{item->component};
if (item->type == SchedulerItem::RETRY) { item->callback();
retry_result = item->retry_callback();
} else {
item->void_callback();
}
} }
} }
@ -213,16 +219,13 @@ void HOT Scheduler::call() {
continue; continue;
} }
if (item->type == SchedulerItem::INTERVAL || if (item->type == SchedulerItem::INTERVAL) {
(item->type == SchedulerItem::RETRY && (--item->retry_countdown > 0 && retry_result != RetryResult::DONE))) {
if (item->interval != 0) { if (item->interval != 0) {
const uint32_t before = item->last_execution; const uint32_t before = item->last_execution;
const uint32_t amount = (now - item->last_execution) / item->interval; const uint32_t amount = (now - item->last_execution) / item->interval;
item->last_execution += amount * item->interval; item->last_execution += amount * item->interval;
if (item->last_execution < before) if (item->last_execution < before)
item->last_execution_major++; item->last_execution_major++;
if (item->type == SchedulerItem::RETRY)
item->interval *= item->backoff_multiplier;
} }
this->push_(std::move(item)); this->push_(std::move(item));
} }

View file

@ -10,13 +10,13 @@ class Component;
class Scheduler { class Scheduler {
public: public:
void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> &&func); void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func);
bool cancel_timeout(Component *component, const std::string &name); bool cancel_timeout(Component *component, const std::string &name);
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> &&func); void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
bool cancel_interval(Component *component, const std::string &name); bool cancel_interval(Component *component, const std::string &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()> &&func, float backoff_increase_factor = 1.0f); std::function<RetryResult()> 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);
optional<uint32_t> next_schedule_in(); optional<uint32_t> next_schedule_in();
@ -29,20 +29,13 @@ class Scheduler {
struct SchedulerItem { struct SchedulerItem {
Component *component; Component *component;
std::string name; std::string name;
enum Type { TIMEOUT, INTERVAL, RETRY } type; enum Type { TIMEOUT, INTERVAL } type;
union { union {
uint32_t interval; uint32_t interval;
uint32_t timeout; uint32_t timeout;
}; };
uint32_t last_execution; uint32_t last_execution;
// Ideally this should be a union or std::variant std::function<void()> callback;
// but unions don't work with object like std::function
// union CallBack_{
std::function<void()> void_callback;
std::function<RetryResult()> retry_callback;
// };
uint8_t retry_countdown{3};
float backoff_multiplier{1.0f};
bool remove; bool remove;
uint8_t last_execution_major; uint8_t last_execution_major;
@ -60,8 +53,6 @@ class Scheduler {
switch (this->type) { switch (this->type) {
case SchedulerItem::INTERVAL: case SchedulerItem::INTERVAL:
return "interval"; return "interval";
case SchedulerItem::RETRY:
return "retry";
case SchedulerItem::TIMEOUT: case SchedulerItem::TIMEOUT:
return "timeout"; return "timeout";
default: default: