You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

436 lines
16 KiB

/*
* standby.c — режим standby с duty-cycle (активный/неактивный интервалы)
*
* Синглтон (одна instance на процесс — как instance_lite.c).
* Таймеры только через uasync. Интервалы читаются лениво из chat_setting,
* поэтому их изменение через GUI применяется на границе следующей фазы.
*
* Duty-cycle:
* standby включён → фаза ACTIVE на standby_active_sec, затем SLEEP на
* standby_sleep_sec, и так по кругу, пока standby не выключат.
*
* Досрочное пробуждение:
* в фазе SLEEP первые standby_min_sleep_sec секунд спим безусловно;
* после них standby_notify_network_activity() (внешняя сетевая активность)
* пробуждает досрочно — следующий цикл как обычно.
*
* Список ожидающих (standby_wait) — двусвязный список, элементы из memory_pool.
* В SLEEP ожидающие будятся фазовым таймером после анонса ACTIVE пирам.
* При выходе из standby они будятся после восстановления режима линков.
*/
#include "standby.h"
#include "chat_setting.h"
#include "utun_instance.h"
#include "debug_config.h"
#include "memory_pool.h"
#define STANDBY_TB_PER_SEC 10000 /* timebase = 0.1ms → 1 сек = 10000 TB */
#define PHASE_ACTIVE 0
#define PHASE_SLEEP 1
struct standby_wait_entry {
struct standby_wait_entry* prev;
struct standby_wait_entry* next;
void* timer_id; /* handle uasync_set_timeout */
void (*cb)(void* arg);
void* arg;
int dispatching; /* отцеплен для пробуждения; cancel только снимает cb */
};
static struct UASYNC* g_ua = NULL;
static struct UTUN_INSTANCE* g_inst = NULL; /* для чтения chat_setting (per-instance) */
static int g_enabled = 0;
static int g_phase = PHASE_ACTIVE;
static uint64_t g_phase_start_tb = 0; /* начало текущей фазы */
static uint64_t g_phase_end_tb = 0; /* дедлайн текущей фазы */
static uint64_t g_min_sleep_end_tb = 0; /* до этой точки спим безусловно */
static void* g_phase_timer = NULL;
static void (*g_switch_cb)(int enabled, void* arg) = NULL;
static void* g_switch_arg = NULL;
static void (*g_phase_cb)(int sleeping, void* arg) = NULL;
static void* g_phase_cb_arg = NULL;
static struct memory_pool* g_wait_pool = NULL;
static struct standby_wait_entry* g_wait_head = NULL;
static struct standby_wait_entry* g_wait_tail = NULL;
/* счётчики для диагностики */
static uint32_t g_cnt_wakes_timer = 0;
static uint32_t g_cnt_wakes_early = 0;
static uint32_t g_cnt_sleeps = 0;
static uint32_t g_cnt_net_events = 0;
static uint32_t g_cnt_net_ignored = 0;
static int standby_active_sec(void) {
return chat_setting_get_int(g_inst, "standby_active_sec", 2);
}
static int standby_sleep_sec(void) {
return chat_setting_get_int(g_inst, "standby_sleep_sec", 60);
}
static int standby_min_sleep_sec(void) {
int m = chat_setting_get_int(g_inst, "standby_min_sleep_sec", 15);
if (m < 0) m = 0;
int s = standby_sleep_sec();
if (m > s) m = s;
return m;
}
/* override интервалов (мс) для тестов: 0 = использовать chat_setting */
static int g_override = 0;
static int g_active_ms = 0;
static int g_sleep_ms = 0;
static int g_min_sleep_ms = 0;
static int standby_active_tb(void) {
return g_override ? g_active_ms * 10 : standby_active_sec() * STANDBY_TB_PER_SEC;
}
static int standby_sleep_tb(void) {
return g_override ? g_sleep_ms * 10 : standby_sleep_sec() * STANDBY_TB_PER_SEC;
}
static int standby_min_sleep_tb(void) {
int m = g_override ? g_min_sleep_ms * 10 : standby_min_sleep_sec() * STANDBY_TB_PER_SEC;
if (m < 0) m = 0;
int s = standby_sleep_tb();
if (m > s) m = s;
return m;
}
/* ── фазовый таймер ── */
static void standby_phase_timer_cb(void* arg);
/* Сначала создаёт новый таймер; при ошибке сохраняет прежнюю фазу и её таймер. */
static int standby_arm_phase(int phase) {
int dur_tb = (phase == PHASE_ACTIVE) ? standby_active_tb() : standby_sleep_tb();
uint64_t now = get_time_tb();
void* timer = uasync_set_timeout(g_ua, dur_tb, NULL, standby_phase_timer_cb, "standby_phase");
if (!timer) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: failed to arm phase=%d duration=%dms", phase, dur_tb / 10);
return -1;
}
if (g_phase_timer) uasync_cancel_timeout(g_ua, g_phase_timer);
g_phase_timer = timer;
g_phase = phase;
g_phase_start_tb = now;
g_phase_end_tb = now + (uint64_t)dur_tb;
g_min_sleep_end_tb = phase == PHASE_SLEEP ? now + (uint64_t)standby_min_sleep_tb() : 0;
return 0;
}
static void standby_wake_all_waiters(void);
static void standby_phase_timer_cb(void* arg) {
(void)arg;
g_phase_timer = NULL;
if (!g_enabled) return;
uint64_t elapsed = get_time_tb() - g_phase_start_tb;
/* переармить фазу ДО фазового колбэка: колбэк читает g_phase_end_tb
(ka_active_remaining_01s), иначе он увидит старый (уже истёкший) дедлайн. */
if (standby_arm_phase(g_phase == PHASE_ACTIVE ? PHASE_SLEEP : PHASE_ACTIVE) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: phase timer lost; returning to normal mode");
if (g_inst) utun_set_client_activity(g_inst, 1); else standby_set_enabled(NULL, 0);
return;
}
if (g_phase == PHASE_SLEEP) {
g_cnt_sleeps++;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,
"standby: SLEEP (active done in %d.%ds, sleep=%dms min_sleep=%dms)",
(int)(elapsed / STANDBY_TB_PER_SEC), (int)((elapsed % STANDBY_TB_PER_SEC) / 1000),
standby_sleep_tb() / 10, standby_min_sleep_tb() / 10);
if (g_phase_cb) g_phase_cb(1, g_phase_cb_arg);
} else {
g_cnt_wakes_timer++;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: WAKE (timer), slept %d.%ds",
(int)(elapsed / STANDBY_TB_PER_SEC), (int)((elapsed % STANDBY_TB_PER_SEC) / 1000));
if (g_phase_cb) g_phase_cb(0, g_phase_cb_arg);
standby_wake_all_waiters();
}
}
/* ── список ожидающих ── */
static struct standby_wait_entry* wait_entry_alloc(void) {
if (!g_wait_pool) {
g_wait_pool = memory_pool_init(sizeof(struct standby_wait_entry), "standby_pool");
if (!g_wait_pool) return NULL;
}
return (struct standby_wait_entry*)memory_pool_alloc(g_wait_pool);
}
static void wait_entry_free(struct standby_wait_entry* e) {
memory_pool_free(g_wait_pool, e);
}
static void wait_list_remove(struct standby_wait_entry* e) {
if (e->prev) e->prev->next = e->next; else g_wait_head = e->next;
if (e->next) e->next->prev = e->prev; else g_wait_tail = e->prev;
e->prev = e->next = NULL;
}
static void wait_list_push(struct standby_wait_entry* e) {
e->prev = g_wait_tail;
e->next = NULL;
if (g_wait_tail) g_wait_tail->next = e; else g_wait_head = e;
g_wait_tail = e;
}
/* Отменяет все wait-таймеры и вызывает их колбэки немедленно.
* Сначала отцепляем весь список — новые регистрации в колбэках
* (standby_wait) попадут в свежий список и не будут задеты этим проходом. */
static void standby_wake_all_waiters(void) {
struct standby_wait_entry* list = g_wait_head;
g_wait_head = g_wait_tail = NULL;
for (struct standby_wait_entry* e = list; e; e = e->next) {
e->dispatching = 1;
if (e->timer_id) {
uasync_cancel_timeout(g_ua, e->timer_id);
e->timer_id = NULL;
}
}
while (list) {
struct standby_wait_entry* e = list;
list = e->next;
void (*cb)(void*) = e->cb;
void* arg = e->arg;
wait_entry_free(e);
if (cb) cb(arg);
}
}
static void standby_wait_timer_cb(void* arg) {
struct standby_wait_entry* e = (struct standby_wait_entry*)arg;
e->timer_id = NULL;
wait_list_remove(e);
void (*cb)(void*) = e->cb;
void* user_arg = e->arg;
wait_entry_free(e);
cb(user_arg);
}
/* ── публичный API ── */
void standby_init(struct UASYNC* ua) {
g_ua = ua;
g_inst = NULL;
g_enabled = 0;
g_phase = PHASE_ACTIVE;
g_phase_timer = NULL;
g_cnt_wakes_timer = g_cnt_wakes_early = 0;
g_cnt_sleeps = g_cnt_net_events = g_cnt_net_ignored = 0;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: init (active=%dms sleep=%dms min_sleep=%dms)",
standby_active_tb() / 10, standby_sleep_tb() / 10, standby_min_sleep_tb() / 10);
}
void standby_deinit(void) {
if (g_phase_timer && g_ua) {
uasync_cancel_timeout(g_ua, g_phase_timer);
g_phase_timer = NULL;
}
while (g_wait_head) {
struct standby_wait_entry* e = g_wait_head;
if (e->timer_id && g_ua) uasync_cancel_timeout(g_ua, e->timer_id);
wait_list_remove(e);
wait_entry_free(e);
}
if (g_wait_pool) {
memory_pool_destroy(g_wait_pool);
g_wait_pool = NULL;
}
g_enabled = 0;
g_phase = PHASE_ACTIVE;
g_phase_start_tb = g_phase_end_tb = g_min_sleep_end_tb = 0;
g_ua = NULL;
g_inst = NULL;
g_switch_cb = NULL;
g_switch_arg = NULL;
g_phase_cb = NULL;
g_phase_cb_arg = NULL;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: deinit (wakes_timer=%u wakes_early=%u sleeps=%u net=%u ignored=%u)",
g_cnt_wakes_timer, g_cnt_wakes_early, g_cnt_sleeps, g_cnt_net_events, g_cnt_net_ignored);
}
int standby_set_enabled(struct UTUN_INSTANCE* inst, int enabled) {
if (!g_ua || (inst && inst->ua != g_ua)) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: set_enabled(%d) ua=%p instance_ua=%p", enabled,
(void*)g_ua, (void*)(inst ? inst->ua : NULL));
return -1;
}
enabled = !!enabled;
if (inst) g_inst = inst;
if (g_enabled == enabled) return 0;
if (enabled) {
if (standby_arm_phase(PHASE_ACTIVE) < 0) return -1;
g_enabled = 1;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: ENTER (active=%dms sleep=%dms min_sleep=%dms)",
standby_active_tb() / 10, standby_sleep_tb() / 10, standby_min_sleep_tb() / 10);
} else {
g_enabled = 0;
if (g_phase_timer) {
uasync_cancel_timeout(g_ua, g_phase_timer);
g_phase_timer = NULL;
}
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: EXIT (wakes_timer=%u wakes_early=%u sleeps=%u net=%u ignored=%u)",
g_cnt_wakes_timer, g_cnt_wakes_early, g_cnt_sleeps, g_cnt_net_events, g_cnt_net_ignored);
}
if (g_switch_cb) g_switch_cb(enabled, g_switch_arg);
if (!enabled) standby_wake_all_waiters();
return 0;
}
int standby_is_enabled(void) {
return g_enabled;
}
void standby_set_switch_callback(void (*cb)(int enabled, void* arg), void* arg) {
g_switch_cb = cb;
g_switch_arg = arg;
}
int standby_get_sleep_tb(void) {
if (!g_enabled) return 0;
if (g_phase == PHASE_ACTIVE) return 0;
uint64_t now = get_time_tb();
if (now >= g_phase_end_tb) return 0;
return (int)(g_phase_end_tb - now);
}
int standby_get_active_remaining_tb(void) {
if (!g_enabled) return 0;
if (g_phase == PHASE_SLEEP) return 0;
uint64_t now = get_time_tb();
if (now >= g_phase_end_tb) return 0;
return (int)(g_phase_end_tb - now);
}
int standby_get_sleep_duration_tb(void) {
return standby_sleep_tb();
}
int standby_get_wake_deadline_tb(void) {
if (!g_enabled) return 0;
if (g_phase == PHASE_SLEEP) return standby_get_sleep_tb();
/* ACTIVE: остаток активного + полная длительность предстоящего сна */
return standby_get_active_remaining_tb() + standby_sleep_tb();
}
int standby_get_keepalive_ms(void) {
int ms = chat_setting_get_int(g_inst, "standby_keepalive_ms", 30000);
if (ms < 100) ms = 100;
return ms;
}
void standby_set_phase_callback(void (*cb)(int sleeping, void* arg), void* arg) {
g_phase_cb = cb;
g_phase_cb_arg = arg;
}
void* standby_wait(void* arg, void (*cb)(void* arg)) {
if (!cb) return NULL;
if (!g_ua) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: wait() but not initialized");
return NULL;
}
if (!g_enabled) {
/* standby выключен → будим сразу (отложенно, чтобы не было рекурсии) */
uasync_call_soon(g_ua, arg, (timeout_callback_t)cb);
return NULL;
}
struct standby_wait_entry* e = wait_entry_alloc();
if (!e) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: wait_entry_alloc failed");
cb(arg);
return NULL;
}
e->cb = cb;
e->arg = arg;
e->dispatching = 0;
/* SLEEP ждёт фазового события; ACTIVE вызывает cb отложенно, без рекурсии. */
e->timer_id = NULL;
if (g_phase == PHASE_ACTIVE) {
e->timer_id = uasync_set_timeout(g_ua, 0, e, standby_wait_timer_cb, "standby_wait");
if (!e->timer_id) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: uasync_set_timeout failed");
wait_entry_free(e);
cb(arg);
return NULL;
}
}
wait_list_push(e);
return e;
}
void standby_wait_cancel(void* handle) {
if (!handle) return;
struct standby_wait_entry* e = (struct standby_wait_entry*)handle;
if (e->dispatching) { e->cb = NULL; return; }
if (e->timer_id && g_ua) {
uasync_cancel_timeout(g_ua, e->timer_id);
e->timer_id = NULL;
}
wait_list_remove(e);
wait_entry_free(e);
}
/* ── внешняя сетевая активность ── */
void standby_notify_network_activity(void) {
g_cnt_net_events++;
if (!g_enabled) {
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "standby: network activity ignored (standby off)");
return;
}
if (g_phase != PHASE_SLEEP) {
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "standby: network activity ignored (phase=ACTIVE)");
return;
}
uint64_t now = get_time_tb();
uint64_t slept = now - g_phase_start_tb;
if (now < g_min_sleep_end_tb) {
g_cnt_net_ignored++;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,
"standby: network activity caught, min_sleep not elapsed (%d.%ds left)",
(int)((g_min_sleep_end_tb - now) / STANDBY_TB_PER_SEC),
(int)(((g_min_sleep_end_tb - now) % STANDBY_TB_PER_SEC) / 1000));
return;
}
if (standby_arm_phase(PHASE_ACTIVE) < 0) return;
g_cnt_wakes_early++;
DEBUG_INFO(DEBUG_CATEGORY_GENERAL,
"standby: WAKE early (network activity), slept %d.%ds min_sleep=%dms",
(int)(slept / STANDBY_TB_PER_SEC), (int)((slept % STANDBY_TB_PER_SEC) / 1000),
standby_min_sleep_tb() / 10);
if (g_phase_cb) g_phase_cb(0, g_phase_cb_arg);
standby_wake_all_waiters();
}
void standby_log_rx(const char* kind, const char* who) {
if (!g_enabled || g_phase != PHASE_SLEEP) return;
uint64_t slept = get_time_tb() - g_phase_start_tb;
DEBUG_INFO(DEBUG_CATEGORY_DEBUG,
"standby: rx in SLEEP: %s from %s, slept %d.%ds",
kind ? kind : "?", who ? who : "?",
(int)(slept / STANDBY_TB_PER_SEC), (int)((slept % STANDBY_TB_PER_SEC) / 1000));
}
void standby_set_intervals_ms(int active_ms, int sleep_ms, int min_sleep_ms) {
if (active_ms <= 0 || sleep_ms <= 0) {
g_override = 0;
return;
}
g_override = 1;
g_active_ms = active_ms;
g_sleep_ms = sleep_ms;
g_min_sleep_ms = min_sleep_ms < 0 ? 0 : min_sleep_ms;
}