chore(repo): keep only current main snapshot
History squashed to one root commit to permanently drop old branch history and objects.
This commit is contained in:
310
components/control_plane/src/controller_lifecycle.c
Normal file
310
components/control_plane/src/controller_lifecycle.c
Normal file
@@ -0,0 +1,310 @@
|
||||
#include "control_plane.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "domain.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "controller_lifecycle";
|
||||
static SemaphoreHandle_t s_lock;
|
||||
static controller_lifecycle_status_t s_status = {
|
||||
.state = CONTROLLER_LIFECYCLE_STATE_STOPPED,
|
||||
.last_error = ESP_OK,
|
||||
.start_attempt = 0,
|
||||
.last_transition_ms = 0,
|
||||
.last_stage = "stopped",
|
||||
};
|
||||
|
||||
static void lifecycle_lock_init_if_needed(void) {
|
||||
if (s_lock == NULL) {
|
||||
s_lock = xSemaphoreCreateMutex();
|
||||
}
|
||||
}
|
||||
|
||||
static bool lifecycle_lock_take(uint32_t timeout_ms) {
|
||||
lifecycle_lock_init_if_needed();
|
||||
return s_lock != NULL && xSemaphoreTake(s_lock, pdMS_TO_TICKS(timeout_ms)) == pdTRUE;
|
||||
}
|
||||
|
||||
static void lifecycle_set_state_locked(controller_lifecycle_state_t state,
|
||||
esp_err_t last_error,
|
||||
const char *stage) {
|
||||
s_status.state = state;
|
||||
s_status.last_error = last_error;
|
||||
s_status.last_transition_ms = esp_timer_get_time() / 1000;
|
||||
strlcpy(s_status.last_stage, stage != NULL ? stage : "unknown", sizeof(s_status.last_stage));
|
||||
domain_diag_set_gauge(DOMAIN_DIAG_GAUGE_LIFECYCLE_STATE, (int32_t)state);
|
||||
}
|
||||
|
||||
const char *controller_lifecycle_state_str(controller_lifecycle_state_t state) {
|
||||
switch (state) {
|
||||
case CONTROLLER_LIFECYCLE_STATE_STOPPED:
|
||||
return "stopped";
|
||||
case CONTROLLER_LIFECYCLE_STATE_STARTING:
|
||||
return "starting";
|
||||
case CONTROLLER_LIFECYCLE_STATE_RUNNING:
|
||||
return "running";
|
||||
case CONTROLLER_LIFECYCLE_STATE_DEGRADED:
|
||||
return "degraded";
|
||||
case CONTROLLER_LIFECYCLE_STATE_STOPPING:
|
||||
return "stopping";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_start_system_runtime(void) {
|
||||
return system_runtime_start();
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_start_printer_protocol(void) {
|
||||
return printer_protocol_init();
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_start_voice(void) {
|
||||
return voice_interaction_init();
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_start_rest_server(void) {
|
||||
return rest_server_start();
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_stop_rest_server(void) {
|
||||
rest_server_stop();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_stop_voice(void) {
|
||||
esp_err_t rc = voice_interaction_stop(NULL, 0);
|
||||
if (rc == ESP_OK || rc == ESP_ERR_INVALID_STATE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_stop_printer_protocol(void) {
|
||||
esp_err_t rc = printer_protocol_stop(domain_policy_printer_stop_timeout_ms());
|
||||
if (rc == ESP_OK || rc == ESP_ERR_INVALID_STATE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static esp_err_t lifecycle_stop_system_runtime(void) {
|
||||
return system_runtime_shutdown();
|
||||
}
|
||||
|
||||
typedef esp_err_t (*lifecycle_step_fn_t)(void);
|
||||
|
||||
static esp_err_t lifecycle_run_step_with_retry(const char *stage, lifecycle_step_fn_t fn) {
|
||||
uint32_t retry_count = domain_policy_lifecycle_start_retry_count();
|
||||
uint32_t max_attempts = retry_count + 1;
|
||||
|
||||
for (uint32_t attempt = 0; attempt < max_attempts; ++attempt) {
|
||||
esp_err_t rc = fn();
|
||||
if (rc == ESP_OK) {
|
||||
if (attempt > 0) {
|
||||
ESP_LOGW(TAG, "stage %s recovered after retries, attempt=%u", stage, (unsigned)(attempt + 1));
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool can_retry = (attempt + 1 < max_attempts) && domain_policy_is_retryable_error(rc);
|
||||
ESP_LOGW(TAG,
|
||||
"stage %s failed, rc=0x%x, attempt=%u/%u, retry=%d",
|
||||
stage,
|
||||
(unsigned)rc,
|
||||
(unsigned)(attempt + 1),
|
||||
(unsigned)max_attempts,
|
||||
can_retry ? 1 : 0);
|
||||
|
||||
if (!can_retry) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_START_RETRY, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(domain_policy_lifecycle_retry_backoff_ms(attempt)));
|
||||
}
|
||||
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t controller_lifecycle_start(void) {
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_START_ATTEMPT, 1);
|
||||
|
||||
if (!lifecycle_lock_take(1000)) {
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_START_FAILED, 1);
|
||||
domain_diag_record_error("lifecycle_start", ESP_ERR_TIMEOUT, "lifecycle lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (s_status.state == CONTROLLER_LIFECYCLE_STATE_RUNNING) {
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
if (s_status.state == CONTROLLER_LIFECYCLE_STATE_STARTING ||
|
||||
s_status.state == CONTROLLER_LIFECYCLE_STATE_STOPPING) {
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
s_status.start_attempt++;
|
||||
lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_STARTING, ESP_OK, "bootstrap");
|
||||
xSemaphoreGive(s_lock);
|
||||
|
||||
bool system_started = false;
|
||||
bool printer_started = false;
|
||||
bool voice_started = false;
|
||||
bool rest_started = false;
|
||||
const char *failed_stage = "unknown";
|
||||
|
||||
failed_stage = "system_runtime";
|
||||
esp_err_t rc = lifecycle_run_step_with_retry("system_runtime", lifecycle_start_system_runtime);
|
||||
if (rc != ESP_OK) {
|
||||
goto start_failed;
|
||||
}
|
||||
system_started = true;
|
||||
|
||||
failed_stage = "printer_protocol";
|
||||
rc = lifecycle_run_step_with_retry("printer_protocol", lifecycle_start_printer_protocol);
|
||||
if (rc != ESP_OK) {
|
||||
goto start_failed;
|
||||
}
|
||||
printer_started = true;
|
||||
|
||||
failed_stage = "voice_interaction";
|
||||
rc = lifecycle_run_step_with_retry("voice_interaction", lifecycle_start_voice);
|
||||
if (rc != ESP_OK) {
|
||||
goto start_failed;
|
||||
}
|
||||
voice_started = true;
|
||||
|
||||
failed_stage = "rest_server";
|
||||
rc = lifecycle_run_step_with_retry("rest_server", lifecycle_start_rest_server);
|
||||
if (rc != ESP_OK) {
|
||||
goto start_failed;
|
||||
}
|
||||
rest_started = true;
|
||||
|
||||
image_generation_schedule_prewarm();
|
||||
|
||||
if (lifecycle_lock_take(1000)) {
|
||||
lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_RUNNING, ESP_OK, "running");
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_START_SUCCESS, 1);
|
||||
ESP_LOGI(TAG, "controller lifecycle started");
|
||||
return ESP_OK;
|
||||
|
||||
start_failed:
|
||||
if (rest_started) {
|
||||
(void)lifecycle_stop_rest_server();
|
||||
}
|
||||
if (voice_started) {
|
||||
(void)lifecycle_stop_voice();
|
||||
}
|
||||
if (printer_started) {
|
||||
(void)lifecycle_stop_printer_protocol();
|
||||
}
|
||||
if (system_started) {
|
||||
(void)lifecycle_stop_system_runtime();
|
||||
}
|
||||
|
||||
if (lifecycle_lock_take(1000)) {
|
||||
lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_DEGRADED, rc, failed_stage);
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_START_FAILED, 1);
|
||||
domain_diag_record_error("lifecycle_start", rc, failed_stage);
|
||||
ESP_LOGE(TAG,
|
||||
"controller lifecycle start failed at stage=%s, rc=0x%x",
|
||||
failed_stage,
|
||||
(unsigned)rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
esp_err_t controller_lifecycle_stop(void) {
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_STOP_ATTEMPT, 1);
|
||||
|
||||
if (!lifecycle_lock_take(1000)) {
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_STOP_FAILED, 1);
|
||||
domain_diag_record_error("lifecycle_stop", ESP_ERR_TIMEOUT, "lifecycle lock timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (s_status.state == CONTROLLER_LIFECYCLE_STATE_STOPPED) {
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
if (s_status.state == CONTROLLER_LIFECYCLE_STATE_STARTING ||
|
||||
s_status.state == CONTROLLER_LIFECYCLE_STATE_STOPPING) {
|
||||
xSemaphoreGive(s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_STOPPING, ESP_OK, "stopping");
|
||||
xSemaphoreGive(s_lock);
|
||||
|
||||
esp_err_t first_err = ESP_OK;
|
||||
|
||||
esp_err_t rc = lifecycle_stop_rest_server();
|
||||
if (rc != ESP_OK && first_err == ESP_OK) {
|
||||
first_err = rc;
|
||||
}
|
||||
|
||||
rc = lifecycle_stop_voice();
|
||||
if (rc != ESP_OK && first_err == ESP_OK) {
|
||||
first_err = rc;
|
||||
}
|
||||
|
||||
rc = lifecycle_stop_printer_protocol();
|
||||
if (rc != ESP_OK && first_err == ESP_OK) {
|
||||
first_err = rc;
|
||||
}
|
||||
|
||||
rc = lifecycle_stop_system_runtime();
|
||||
if (rc != ESP_OK && first_err == ESP_OK) {
|
||||
first_err = rc;
|
||||
}
|
||||
|
||||
if (lifecycle_lock_take(1000)) {
|
||||
if (first_err == ESP_OK) {
|
||||
lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_STOPPED, ESP_OK, "stopped");
|
||||
} else {
|
||||
lifecycle_set_state_locked(CONTROLLER_LIFECYCLE_STATE_DEGRADED, first_err, "stop_failed");
|
||||
}
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
|
||||
if (first_err == ESP_OK) {
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_STOP_SUCCESS, 1);
|
||||
ESP_LOGI(TAG, "controller lifecycle stopped");
|
||||
} else {
|
||||
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_LIFECYCLE_STOP_FAILED, 1);
|
||||
domain_diag_record_error("lifecycle_stop", first_err, "controller stop failed");
|
||||
ESP_LOGE(TAG, "controller lifecycle stop failed, rc=0x%x", (unsigned)first_err);
|
||||
}
|
||||
|
||||
return first_err;
|
||||
}
|
||||
|
||||
void controller_lifecycle_get_status(controller_lifecycle_status_t *out_status) {
|
||||
if (out_status == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(out_status, 0, sizeof(*out_status));
|
||||
|
||||
if (!lifecycle_lock_take(200)) {
|
||||
return;
|
||||
}
|
||||
|
||||
*out_status = s_status;
|
||||
xSemaphoreGive(s_lock);
|
||||
}
|
||||
Reference in New Issue
Block a user