Browse Source
standby.h/c: duty-cycle ACTIVE/SLEEP, standby_wait() будит в burst сразу, в SLEEP — до следующего burst; notify_network_activity для раннего пробуждения. Интеграция (по флагу UTUN_HAVE_STANDBY, десктоп без изменений): - conn_mgr: bg_ping/candidate_ping в SLEEP defer через standby_wait - route_connectivity: probe_node откладывается (PROBE_STATUS_DEFERRED) - topo_group_connect: пауза между попытками — активный режим 1с, фон standby_wait - jni_bridge/ConfigProvider/NetworkActivityMonitor: set_active + сетевая активность - chat_setting: standby_active_sec/sleep_sec/min_sleep_sec + юнит-тест test_standby (31 проверок)topo_upd
26 changed files with 921 additions and 14 deletions
@ -0,0 +1,48 @@
|
||||
package com.utun.chat.data |
||||
|
||||
import android.content.Context |
||||
import android.net.ConnectivityManager |
||||
import android.os.Build |
||||
import com.utun.chat.ChatApplication |
||||
|
||||
/** |
||||
* Слушатель внешней сетевой активности (ConnectivityManager.OnNetworkActiveListener). |
||||
* |
||||
* Пробуждает standby-ядро досрочно: когда в фазе SLEEP прошёл min_sleep, |
||||
* приход сетевой активности запускает следующий активный цикл раньше. |
||||
* |
||||
* В колбэке не делаем тяжёлую работу — только дёшевый JNI-вызов |
||||
* NativeLib.notifyNetworkActive(), который постит событие на uasync-поток. |
||||
*/ |
||||
object NetworkActivityMonitor { |
||||
private var started = false |
||||
|
||||
private val listener = ConnectivityManager.OnNetworkActiveListener { |
||||
NativeLib.notifyNetworkActive() |
||||
} |
||||
|
||||
fun start() { |
||||
if (started) return |
||||
/* addDefaultNetworkActiveListener доступен с API 28 (P) */ |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return |
||||
val ctx = ChatApplication.instance.applicationContext |
||||
val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return |
||||
cm.addDefaultNetworkActiveListener(listener) |
||||
started = true |
||||
LogManager.addLog("INFO", "NET", "network activity listener started") |
||||
/* Если сеть уже активна в момент запуска — сразу уведомляем */ |
||||
if (cm.isDefaultNetworkActive) { |
||||
NativeLib.notifyNetworkActive() |
||||
} |
||||
} |
||||
|
||||
fun stop() { |
||||
if (!started) return |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return |
||||
val ctx = ChatApplication.instance.applicationContext |
||||
val cm = ctx.getSystemService(ConnectivityManager::class.java) ?: return |
||||
cm.removeDefaultNetworkActiveListener(listener) |
||||
started = false |
||||
LogManager.addLog("INFO", "NET", "network activity listener stopped") |
||||
} |
||||
} |
||||
@ -0,0 +1,386 @@
|
||||
/*
|
||||
* 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. |
||||
* При выходе из standby (или досрочном пробуждении) все wait-таймеры |
||||
* отменяются, колбэки вызываются сразу. |
||||
*/ |
||||
#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; |
||||
}; |
||||
|
||||
static struct UASYNC* g_ua = NULL; |
||||
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 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("standby_active_sec", 2); |
||||
} |
||||
|
||||
static int standby_sleep_sec(void) { |
||||
return chat_setting_get_int("standby_sleep_sec", 60); |
||||
} |
||||
|
||||
static int standby_min_sleep_sec(void) { |
||||
int m = chat_setting_get_int("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 void standby_arm_phase(void) { |
||||
int dur_tb = (g_phase == PHASE_ACTIVE) ? standby_active_tb() : standby_sleep_tb(); |
||||
g_phase_start_tb = get_time_tb(); |
||||
g_phase_end_tb = g_phase_start_tb + (uint64_t)dur_tb; |
||||
g_min_sleep_end_tb = (g_phase == PHASE_SLEEP) |
||||
? g_phase_start_tb + (uint64_t)standby_min_sleep_tb() |
||||
: 0; |
||||
g_phase_timer = uasync_set_timeout(g_ua, dur_tb, NULL, standby_phase_timer_cb, "standby_phase"); |
||||
if (!g_phase_timer) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: failed to arm phase timer"); |
||||
} |
||||
} |
||||
|
||||
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 = (g_phase == PHASE_ACTIVE) ? PHASE_SLEEP : PHASE_ACTIVE; |
||||
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); |
||||
} 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)); |
||||
} |
||||
standby_arm_phase(); |
||||
} |
||||
|
||||
/* ── список ожидающих ── */ |
||||
|
||||
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; |
||||
while (list) { |
||||
struct standby_wait_entry* e = list; |
||||
list = e->next; |
||||
e->prev = e->next = NULL; |
||||
if (e->timer_id) { |
||||
uasync_cancel_timeout(g_ua, e->timer_id); |
||||
e->timer_id = NULL; |
||||
} |
||||
void (*cb)(void*) = e->cb; |
||||
void* arg = e->arg; |
||||
wait_entry_free(e); |
||||
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_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_enabled && !g_phase_timer && !g_wait_head && !g_wait_pool) { |
||||
g_ua = NULL; |
||||
return; |
||||
} |
||||
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_ua = 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); |
||||
} |
||||
|
||||
void standby_set_enabled(struct UTUN_INSTANCE* inst, int enabled) { |
||||
if (!g_ua) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "standby: set_enabled(%d) but not initialized", enabled); |
||||
return; |
||||
} |
||||
if (g_enabled == enabled) return; |
||||
|
||||
if (enabled) { |
||||
g_enabled = 1; |
||||
g_phase = PHASE_ACTIVE; |
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "standby: ENTER (app background, active=%dms sleep=%dms min_sleep=%dms)", |
||||
standby_active_tb() / 10, standby_sleep_tb() / 10, standby_min_sleep_tb() / 10); |
||||
standby_arm_phase(); |
||||
} 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 (app foreground, 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); |
||||
standby_wake_all_waiters(); |
||||
} |
||||
|
||||
if (inst) inst->standby_enabled = (uint8_t)enabled; |
||||
if (g_switch_cb) g_switch_cb(enabled, g_switch_arg); |
||||
} |
||||
|
||||
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); |
||||
} |
||||
|
||||
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; |
||||
|
||||
/* время до начала следующего активного интервала:
|
||||
* SLEEP — ждём до конца фазы; ACTIVE — уже бодрствуем, будим сразу. */ |
||||
uint64_t now = get_time_tb(); |
||||
uint64_t tb; |
||||
if (g_phase == PHASE_SLEEP) { |
||||
tb = (now < g_phase_end_tb) ? (g_phase_end_tb - now) : 0; |
||||
} else { |
||||
tb = 0; |
||||
} |
||||
|
||||
e->timer_id = uasync_set_timeout(g_ua, (int)tb, 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->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; |
||||
} |
||||
|
||||
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_timer) { |
||||
uasync_cancel_timeout(g_ua, g_phase_timer); |
||||
g_phase_timer = NULL; |
||||
} |
||||
g_phase = PHASE_ACTIVE; |
||||
standby_arm_phase(); |
||||
standby_wake_all_waiters(); |
||||
} |
||||
|
||||
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; |
||||
} |
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* standby.h — режим standby с duty-cycle (активный/неактивный интервалы) |
||||
* |
||||
* Для chatgui-android: когда приложение в фоне, вместо постоянной работы |
||||
* узел периодически просыпается: ACTIVE (X сек) → SLEEP (Y сек) → ACTIVE ... |
||||
* |
||||
* Потребители периодической работы используют standby_wait(): колбэк |
||||
* срабатывает на старте следующего активного интервала, а при выходе из |
||||
* standby (приложение вернулось в форграунд) — немедленно. |
||||
*/ |
||||
#ifndef STANDBY_H |
||||
#define STANDBY_H |
||||
|
||||
#include "u_async.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
struct UTUN_INSTANCE; |
||||
|
||||
/* Привязать модуль к uasync. Вызывается после каждого uasync_create(). */ |
||||
void standby_init(struct UASYNC* ua); |
||||
|
||||
/* Отмена таймеров и освобождение пула (без вызова колбэков). */ |
||||
void standby_deinit(void); |
||||
|
||||
/* Включить/выключить standby-режим. Логирует вход/выход (GENERAL info)
|
||||
* и вызывает switch-callback. Флаг дублируется в inst->standby_enabled. */ |
||||
void standby_set_enabled(struct UTUN_INSTANCE* inst, int enabled); |
||||
int standby_is_enabled(void); |
||||
|
||||
/* Колбэк, вызываемый при каждом переключении standby-режима. */ |
||||
void standby_set_switch_callback(void (*cb)(int enabled, void* arg), void* arg); |
||||
|
||||
/* Сколько TB спать сейчас (0 = активный интервал или standby выключен). */ |
||||
int standby_get_sleep_tb(void); |
||||
|
||||
/* Сколько TB осталось активного режима (0 = спим или standby выключен). */ |
||||
int standby_get_active_remaining_tb(void); |
||||
|
||||
/* Ждёт до начала следующего активного интервала (как uasync timeout).
|
||||
* При выходе из standby таймер отменяется и cb вызывается немедленно. |
||||
* Возвращает handle для standby_wait_cancel(). */ |
||||
void* standby_wait(void* arg, void (*cb)(void* arg)); |
||||
void standby_wait_cancel(void* handle); |
||||
|
||||
/* Уведомление о внешней сетевой активности (Android ConnectivityManager).
|
||||
* В фазе SLEEP после min_sleep пробуждает досрочно; иначе игнорируется. |
||||
* Вызывается на uasync-потоке. */ |
||||
void standby_notify_network_activity(void); |
||||
|
||||
/* Переопределить интервалы (мс), минуя chat_setting. Для тестов.
|
||||
* active_ms/sleep_ms <= 0 → вернуться к chat_setting. |
||||
* min_sleep_ms может быть 0 (реагировать на сетевую активность сразу). */ |
||||
void standby_set_intervals_ms(int active_ms, int sleep_ms, int min_sleep_ms); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* STANDBY_H */ |
||||
@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16) |
||||
|
||||
add_executable(test_standby |
||||
test_standby.c |
||||
) |
||||
|
||||
target_link_libraries(test_standby PRIVATE utun_lite pthread) |
||||
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* test_standby.c — юнит-тест модуля standby (chatgui-android) |
||||
* |
||||
* Покрывает все сценарии: init, ENTER/EXIT, смену фаз ACTIVE↔SLEEP, |
||||
* standby_wait во всех фазах, cancel, сетевую активность (игнор/раннее |
||||
* пробуждение), clamp min_sleep и re-entry в wake_all_waiters. |
||||
* |
||||
* Интервалы задаются через standby_set_intervals_ms() (быстрые: 100-300мс). |
||||
* Время продвигается через uasync_poll(ua, -1) (до ближайшего таймера) |
||||
* и uasync_poll(ua, X) (частичное ожидание). |
||||
*/ |
||||
#include "standby.h" |
||||
#include "debug_config.h" |
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
static int g_failures = 0; |
||||
static int g_checks = 0; |
||||
|
||||
#define CHECK(cond, ...) do { \ |
||||
g_checks++; \
|
||||
if (!(cond)) { \
|
||||
g_failures++; \
|
||||
printf(" FAIL: "); printf(__VA_ARGS__); printf("\n"); \
|
||||
} \
|
||||
} while (0) |
||||
|
||||
static struct UASYNC* g_ua = NULL; |
||||
|
||||
/* ── колбэки для standby_wait и switch ── */ |
||||
|
||||
static int g_wait_calls = 0; |
||||
static void wait_cb(void* arg) { |
||||
(void)arg; |
||||
g_wait_calls++; |
||||
} |
||||
|
||||
static int g_switch_calls = 0; |
||||
static int g_switch_last_enabled = -1; |
||||
static void switch_cb(int enabled, void* arg) { |
||||
(void)arg; |
||||
g_switch_calls++; |
||||
g_switch_last_enabled = enabled; |
||||
} |
||||
|
||||
/* re-entry waiter: в колбэке снова регистрирует себя (ровно один раз) */ |
||||
static int g_reentry_calls = 0; |
||||
static void reentry_cb(void* arg) { |
||||
(void)arg; |
||||
g_reentry_calls++; |
||||
if (g_reentry_calls == 1) standby_wait(NULL, reentry_cb); |
||||
} |
||||
|
||||
/* ── хелперы ── */ |
||||
|
||||
static void reset_standby(void) { |
||||
standby_deinit(); |
||||
standby_set_intervals_ms(200, 300, 100); /* active=200ms sleep=300ms min_sleep=100ms */ |
||||
standby_init(g_ua); |
||||
g_wait_calls = 0; |
||||
g_switch_calls = 0; |
||||
g_switch_last_enabled = -1; |
||||
g_reentry_calls = 0; |
||||
standby_set_switch_callback(switch_cb, NULL); |
||||
} |
||||
|
||||
/* продвинуть время до ближайшего таймера (фаза переключится) */ |
||||
static void run_to_next_phase(void) { |
||||
uasync_poll(g_ua, -1); |
||||
} |
||||
|
||||
int main(void) { |
||||
debug_config_init(); |
||||
debug_set_level(DEBUG_LEVEL_INFO); |
||||
|
||||
g_ua = uasync_create(); |
||||
if (!g_ua) { printf("uasync_create failed\n"); return 1; } |
||||
|
||||
printf("=== standby unit test ===\n"); |
||||
|
||||
/* ── 1. init ── */ |
||||
reset_standby(); |
||||
CHECK(standby_is_enabled() == 0, "init: should be disabled"); |
||||
CHECK(standby_get_sleep_tb() == 0, "init: sleep_tb should be 0"); |
||||
CHECK(standby_get_active_remaining_tb() == 0, "init: active_remaining should be 0"); |
||||
|
||||
/* ── 2. ENTER ── */ |
||||
standby_set_enabled(NULL, 1); |
||||
CHECK(standby_is_enabled() == 1, "ENTER: should be enabled"); |
||||
CHECK(standby_get_sleep_tb() == 0, "ENTER: active phase → sleep_tb 0"); |
||||
CHECK(standby_get_active_remaining_tb() > 0, "ENTER: active phase → active_remaining > 0"); |
||||
CHECK(g_switch_calls == 1 && g_switch_last_enabled == 1, "ENTER: switch_cb(1) once"); |
||||
|
||||
/* ── 3. ACTIVE → SLEEP ── */ |
||||
run_to_next_phase(); |
||||
CHECK(standby_get_sleep_tb() > 0, "ACTIVE→SLEEP: sleep_tb > 0"); |
||||
CHECK(standby_get_active_remaining_tb() == 0, "ACTIVE→SLEEP: active_remaining == 0"); |
||||
|
||||
/* ── 4. SLEEP → ACTIVE (по таймеру) ── */ |
||||
run_to_next_phase(); |
||||
CHECK(standby_get_sleep_tb() == 0, "SLEEP→ACTIVE: sleep_tb == 0"); |
||||
CHECK(standby_get_active_remaining_tb() > 0, "SLEEP→ACTIVE: active_remaining > 0"); |
||||
|
||||
/* ── 5. EXIT ── */ |
||||
standby_set_enabled(NULL, 0); |
||||
CHECK(standby_is_enabled() == 0, "EXIT: should be disabled"); |
||||
CHECK(standby_get_sleep_tb() == 0, "EXIT: sleep_tb == 0"); |
||||
CHECK(g_switch_calls == 2 && g_switch_last_enabled == 0, "EXIT: switch_cb(0)"); |
||||
|
||||
/* ── 6. standby_wait при выключенном → сразу ── */ |
||||
reset_standby(); |
||||
standby_wait(NULL, wait_cb); |
||||
uasync_poll(g_ua, 0); /* обработать call_soon */ |
||||
CHECK(g_wait_calls == 1, "wait(disabled): cb fired immediately"); |
||||
|
||||
/* ── 7. standby_wait в ACTIVE → будит сразу (burst, без паузы) ── */ |
||||
reset_standby(); |
||||
standby_set_enabled(NULL, 1); |
||||
standby_wait(NULL, wait_cb); /* ACTIVE → tb=0, immediate */ |
||||
uasync_poll(g_ua, 0); |
||||
CHECK(g_wait_calls == 1, "wait(ACTIVE): fired immediately (burst)"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 8. standby_wait в SLEEP → ждёт конца сна ── */ |
||||
reset_standby(); |
||||
standby_set_enabled(NULL, 1); |
||||
run_to_next_phase(); /* → SLEEP */ |
||||
CHECK(standby_get_sleep_tb() > 0, "wait(SLEEP): in sleep"); |
||||
standby_wait(NULL, wait_cb); |
||||
uasync_poll(g_ua, -1); /* конец сна */ |
||||
uasync_poll(g_ua, 0); |
||||
CHECK(g_wait_calls == 1, "wait(SLEEP): fired at sleep end"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 9. standby_wait_cancel ── */ |
||||
reset_standby(); |
||||
standby_set_enabled(NULL, 1); |
||||
run_to_next_phase(); /* → SLEEP */ |
||||
void* h = standby_wait(NULL, wait_cb); |
||||
standby_wait_cancel(h); |
||||
uasync_poll(g_ua, -1); /* конец сна */ |
||||
uasync_poll(g_ua, 0); |
||||
CHECK(g_wait_calls == 0, "wait_cancel: cb not called"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 10. notify при standby off → игнор ── */ |
||||
reset_standby(); |
||||
standby_notify_network_activity(); |
||||
CHECK(standby_is_enabled() == 0, "notify(off): still disabled"); |
||||
|
||||
/* ── 11. notify в ACTIVE → игнор ── */ |
||||
standby_set_enabled(NULL, 1); |
||||
CHECK(standby_get_active_remaining_tb() > 0, "notify(ACTIVE): in active"); |
||||
standby_notify_network_activity(); |
||||
CHECK(standby_get_active_remaining_tb() > 0, "notify(ACTIVE): still active"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 12. notify в SLEEP до min_sleep → игнор ── */ |
||||
reset_standby(); |
||||
standby_set_enabled(NULL, 1); |
||||
run_to_next_phase(); /* → SLEEP */ |
||||
CHECK(standby_get_sleep_tb() > 0, "notify(min_sleep): in sleep"); |
||||
standby_notify_network_activity(); /* сразу — ещё < min_sleep(100ms) */ |
||||
CHECK(standby_get_sleep_tb() > 0, "notify(<min_sleep): still sleeping"); |
||||
|
||||
/* ── 13. notify в SLEEP после min_sleep → раннее пробуждение ── */ |
||||
uasync_poll(g_ua, 2000); /* +200ms → прошло min_sleep(100ms) */ |
||||
standby_notify_network_activity(); |
||||
CHECK(standby_get_sleep_tb() == 0, "notify(>min_sleep): woke → sleep_tb 0"); |
||||
CHECK(standby_get_active_remaining_tb() > 0, "notify(>min_sleep): woke → active_remaining > 0"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 14. раннее пробуждение будит waiters ── */ |
||||
reset_standby(); |
||||
standby_set_enabled(NULL, 1); |
||||
run_to_next_phase(); /* → SLEEP */ |
||||
standby_wait(NULL, wait_cb); |
||||
uasync_poll(g_ua, 2000); /* +200ms → min_sleep прошёл */ |
||||
standby_notify_network_activity(); |
||||
uasync_poll(g_ua, 0); |
||||
CHECK(g_wait_calls == 1, "early wake: waiter fired immediately"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 15. clamp min_sleep ≥ sleep → notify всегда игнор ── */ |
||||
reset_standby(); |
||||
standby_set_intervals_ms(200, 300, 500); /* min_sleep(500) > sleep(300) → clamp */ |
||||
standby_init(g_ua); |
||||
standby_set_switch_callback(switch_cb, NULL); |
||||
standby_set_enabled(NULL, 1); |
||||
run_to_next_phase(); /* → SLEEP */ |
||||
uasync_poll(g_ua, 2000); /* +200ms */ |
||||
standby_notify_network_activity(); |
||||
CHECK(standby_get_sleep_tb() > 0, "clamp: min_sleep==sleep → notify ignored"); |
||||
standby_set_enabled(NULL, 0); |
||||
|
||||
/* ── 16. re-entry: waiter перерегистрируется, без бесконечного цикла ── */ |
||||
reset_standby(); |
||||
standby_set_enabled(NULL, 1); |
||||
run_to_next_phase(); /* → SLEEP */ |
||||
standby_wait(NULL, reentry_cb); |
||||
uasync_poll(g_ua, 2000); /* +200ms → min_sleep прошёл */ |
||||
standby_notify_network_activity(); /* раннее пробуждение → wake_all_waiters */ |
||||
CHECK(g_reentry_calls == 1, "re-entry: wake fired waiter once (no infinite loop)"); |
||||
CHECK(standby_is_enabled() == 1, "re-entry: still enabled after early wake"); |
||||
standby_set_enabled(NULL, 0); /* EXIT → будит перерегистрированного ещё раз */ |
||||
CHECK(g_reentry_calls == 2, "re-entry: exit fired re-registered waiter once more"); |
||||
|
||||
/* ── итог ── */ |
||||
standby_deinit(); |
||||
uasync_destroy(g_ua, 0); |
||||
|
||||
printf("=== standby unit test: %d checks, %d failures ===\n", g_checks, g_failures); |
||||
if (g_failures == 0) printf("PASS\n"); |
||||
else printf("FAIL\n"); |
||||
return g_failures ? 1 : 0; |
||||
} |
||||
Loading…
Reference in new issue