Queue Android activity updates while holding the worker lifetime lock

This commit is contained in:
evgeny committed 2026-10-27 20:07:51 +01:00
1 parent 1ed110d47c
commit 4bece684da
3 files changed
+56

No files matched your search

@@ -684,6 +684,38 @@ void instance_lite_restart(const char* new_config_text) {
request_restart(new_config_text, 0);
}
static void set_active_trampoline(void* arg) {
struct UTUN_INSTANCE* inst = __atomic_load_n(&g_inst, __ATOMIC_ACQUIRE);
if (!inst || __atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) {
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "Android activity command cancelled: worker is stopping"); return;
}
int active = (int)(intptr_t)arg;
if (utun_set_client_activity(inst, active) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Android activity transition failed requested=%d", active); return;
}
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "Android activity applied requested=%d effective=%u", active, (unsigned)inst->client_activity);
}
int instance_lite_set_active(int active) {
if (g_is_worker) { IL_LOGE("activity request refused: frontend thread required"); return -1; }
pthread_mutex_lock(&g_stop_mutex);
struct UASYNC* ua = __atomic_load_n(&g_ua, __ATOMIC_ACQUIRE);
if (!ua || !__atomic_load_n(&g_running, __ATOMIC_ACQUIRE) || __atomic_load_n(&g_stop, __ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&g_stop_mutex);
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "Android activity request rejected: core is not ready"); return -1;
}
struct posted_task* task = u_malloc(sizeof(*task));
if (!task) {
pthread_mutex_unlock(&g_stop_mutex);
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "Android activity request allocation failed"); return -1;
}
task->callback = set_active_trampoline;
task->arg = (void*)(intptr_t)!!active;
uasync_post_reserved(ua, task);
pthread_mutex_unlock(&g_stop_mutex);
return 0;
}
/* ── Лёгкое обновление сокетов (auto_sockets=android) ── */
static void update_sockets_trampoline(void* arg) {
@@ -66,6 +66,11 @@ int instance_lite_generate_keys(struct utun_config* config);
* Последний ещё не взятый worker запрос restart/regenerate заменяет предыдущий. */
int instance_lite_regenerate_keys(const char* config_text);
/* Frontend: поставить ACTIVE (active!=0) / STANDBY (0) без заимствования сырого uasync.
* 0 — задача зарезервирована и поставлена живому worker; -1 — ядро не готово/останавливается, ошибка памяти или неверный поток.
* При stop ожидающая команда может отмениться. Результат применения/stream hold логируется в потоке ядра. */
int instance_lite_set_active(int active);
/* Копирует INI, будит poll-loop и пересоздаёт instance и uasync внутри worker.
* До остановки проверяет конфиг и сохраняет отсутствующие ключи; отказ оставляет прежнее ядро.
* При инициализации ставит запрос в очередь; при завершившемся worker делает join/start.
@@ -22,6 +22,7 @@ static pthread_cond_t changed = PTHREAD_COND_INITIALIZER;
static pthread_t ui, worker;
static unsigned attached, detached, destroyed, loops_destroyed, started, errors, checked;
static unsigned identity_failures;
static unsigned activity_checked;
static uint64_t first_node, current_node;
static struct UTUN_INSTANCE* last_detached;
static int stream_failure, voice_failure;
@@ -180,6 +181,7 @@ static void inspect(void* arg) {
(void)arg;
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
observe(inst && inst->call_audio_ops && inst->call_audio_ops->on_media, "ready core without audio provider");
observe(instance_lite_set_active(1) == -1, "activity posting accepted from native callback");
observe(!strcmp(cfg_get_string("lifecycle", "missing"), "owned"), "config provider unavailable before callback stop");
observe(utun_bridge_destroy() == -1 && instance_lite_is_running(), "bridge destroyed resources from core callback");
observe(!strcmp(cfg_get_string("lifecycle", "missing"), "owned"), "failed stop cleared config callbacks");
@@ -195,6 +197,14 @@ static void inspect(void* arg) {
pthread_mutex_lock(&observation_lock); ++checked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void inspect_activity(void* arg) {
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
int expected = (int)(intptr_t)arg;
observe(inst && inst->client_activity_requested == expected && inst->client_activity == expected,
"activity command did not run before subsequent worker callback");
pthread_mutex_lock(&observation_lock); ++activity_checked; pthread_cond_broadcast(&changed); pthread_mutex_unlock(&observation_lock);
}
static void stop_core(void* arg) {
(void)arg;
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
@@ -255,6 +265,7 @@ int main(int argc, char** argv) {
instance_lite_restart(config);
}
CHECK_ANDROID(wait_for(&paused, 1));
CHECK_ANDROID(instance_lite_set_active(1) == -1);
pthread_t stopper;
CHECK_ANDROID(!pthread_create(&stopper, NULL, stop_worker, NULL));
CHECK_ANDROID(wait_for(&stop_entered, 1));
@@ -266,6 +277,7 @@ int main(int argc, char** argv) {
unsigned stopped_early = stop_done;
pthread_mutex_unlock(&observation_lock);
CHECK_ANDROID(stop_requested && !stopped_early);
CHECK_ANDROID(instance_lite_set_active(1) == -1);
if (slow) {
clock_gettime(CLOCK_REALTIME, &deadline); deadline.tv_sec += 2; deadline.tv_nsec += 200000000;
if (deadline.tv_nsec >= 1000000000) { ++deadline.tv_sec; deadline.tv_nsec -= 1000000000; }
@@ -281,6 +293,7 @@ int main(int argc, char** argv) {
CHECK_ANDROID(!pthread_join(stopper, NULL));
CHECK_ANDROID(stop_done == 1 && joins == 1 && !self_detaches && started == (unsigned)cancel_restart);
CHECK_ANDROID(!instance_lite_is_running() && !__real_instance_lite_get_instance() && !instance_lite_get_uasync());
CHECK_ANDROID(instance_lite_set_active(1) == -1);
CHECK_ANDROID(!errors && u_get_allocated_count() == allocated);
/* Тот же singleton должен запуститься снова только после полного join. */
pause_loop = pause_instance = pause_attach = 0;
@@ -297,6 +310,12 @@ int main(int argc, char** argv) {
CHECK_ANDROID(!started && attached == (unsigned)voice_failure && detached == 1 && destroyed == 1 && loops_destroyed == 1);
} else {
CHECK_ANDROID(wait_for(&started, 1) && instance_lite_is_running());
CHECK_ANDROID(!instance_lite_set_active(1));
uasync_post(instance_lite_get_uasync(), inspect_activity, (void*)(intptr_t)1);
CHECK_ANDROID(wait_for(&activity_checked, 1));
CHECK_ANDROID(!instance_lite_set_active(0));
uasync_post(instance_lite_get_uasync(), inspect_activity, NULL);
CHECK_ANDROID(wait_for(&activity_checked, 2));
CHECK_ANDROID(!utun_bridge_call_audio_start(42));
uasync_post(instance_lite_get_uasync(), inspect, NULL);
CHECK_ANDROID(wait_for(&checked, 1));