chatgui: диагностика воспроизведения голосовых сообщений (Windows)

- sound_manager: ma_engine_init/playRawPcm логируют через DEBUG_* (qWarning/qDebug терялись в GUI-приложении без консоли); детальный результат старта PCM (rc/is_playing/cursor)
- main: ранняя инициализация лога в GUI-потоке до SoundManager::init, чтобы диагностика инициализации попала в файл
- messagedelegate: лог клика по Play/timeline (filePath/dlState/существует ли файл)
- voiceplayback: лог заголовка opus (sr/ch/frameSamples)

Попутные правки:
- u_async: фикс двойного close wakeup_pipe (eventfd/Windows)
- chat_core: NULL-guard в get_db/is_initialized
- android: sqlite открывается в utun_instance_init, cleanup stale-потока без утечки fd
- android SettingsScreen: компактная раскладка имени интерфейса
This commit is contained in:
evgeny committed 2026-09-21 18:59:04 +03:00
1 parent 19dc076528
commit 9793733cd5
8 files changed
+76 -51

No files matched your search

@@ -249,8 +249,6 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
val stateColor = when (s.state) { 2 -> Color(0xFF27AE60); 1 -> Color(0xFFF1C40F); else -> Color(0xFFE53935) }
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(s.ifName, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f))
Spacer(Modifier.width(4.dp))
Text("${s.bindIp}:${s.port}", color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.width(6.dp))
Surface(shape = MaterialTheme.shapes.extraSmall, color = stateColor,
@@ -265,6 +263,8 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
Text(s.ifName, style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.fillMaxWidth())
}
}
}
@@ -261,6 +261,8 @@ static void install_crash_handlers(void) {
static void* instance_thread(void* arg) {
char* config_text = (char*)arg;
int my_gen = g_generation;
struct UASYNC* my_ua = NULL; /* поток-локальная копия — для cleanup stale-потока */
struct UTUN_INSTANCE* my_inst = NULL;
struct utun_config* config = parse_config_from_buf(config_text, strlen(config_text), "android");
u_free(config_text);
@@ -269,6 +271,7 @@ static void* instance_thread(void* arg) {
install_crash_handlers();
g_ua = uasync_create();
my_ua = g_ua;
if (!g_ua) { IL_LOGE("uasync_create failed"); free_config(config); __atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE); pthread_detach(pthread_self()); return NULL; }
standby_init(g_ua);
@@ -280,6 +283,7 @@ static void* instance_thread(void* arg) {
ensure_keys(config);
g_inst = utun_instance_create_from_config(g_ua, config);
my_inst = g_inst;
if (!g_inst) {
IL_LOGE("utun_instance_create_from_config failed");
standby_deinit();
@@ -294,25 +298,8 @@ static void* instance_thread(void* arg) {
IL_LOGI("instance created, node_id=0x%016llx", (unsigned long long)g_inst->node_id);
/* Open shared SQLite DB */
if (g_db_path[0] && !g_inst->topo_sqlite_db) {
char db_file[512];
snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path);
int rc = sqlite3_open_v2(db_file, &g_inst->topo_sqlite_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc == SQLITE_OK && g_inst->topo_sqlite_db) {
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA wal_autocheckpoint=10000", NULL, NULL, NULL);
topo_node_sqlite_init(g_inst->topo_sqlite_db);
IL_LOGI("SQLite opened %s", db_file);
} else {
IL_LOGE("SQLite open failed rc=%d", rc);
if (g_inst->topo_sqlite_db) { sqlite3_close(g_inst->topo_sqlite_db); g_inst->topo_sqlite_db = NULL; }
}
}
/* Shared SQLite DB открывается внутри utun_instance_init() → topo_groups_init()
(topo_group.c). Двойное открытие здесь давало утечку соединения на каждый рестарт. */
/* Full init: db_sync, chat_core, chat_sync, init_connections */
if (utun_instance_init(g_inst) != 0) {
IL_LOGE("utun_instance_init failed");
@@ -369,13 +356,17 @@ static void* instance_thread(void* arg) {
g_inst ? g_inst->connections : NULL);
uasync_mark_stopped(g_ua);
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
utun_instance_destroy(g_inst);
struct UTUN_INSTANCE* old_inst = g_inst;
g_inst = NULL;
utun_instance_destroy(old_inst);
my_inst = NULL;
uasync_print_resources(g_ua, "AFTER_DESTROY");
u_report_unfreed_blocks();
standby_deinit();
uasync_destroy(g_ua, 0);
my_ua = NULL;
g_ua = uasync_create();
my_ua = g_ua;
if (!g_ua) { IL_LOGE("poll exit: uasync_create failed"); break; }
standby_init(g_ua);
@@ -389,20 +380,11 @@ static void* instance_thread(void* arg) {
if (!config) { IL_LOGE("poll exit: parse_config failed"); break; }
g_inst = utun_instance_create_from_config(g_ua, config);
my_inst = g_inst;
if (!g_inst) { IL_LOGE("poll exit: create_from_config failed"); break; }
chat_event_set_handler(g_inst, chat_event_forward);
char db_file[512];
snprintf(db_file, sizeof(db_file), "%s/chats.db", g_db_path);
int rc = sqlite3_open_v2(db_file, &g_inst->topo_sqlite_db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
if (rc == SQLITE_OK && g_inst->topo_sqlite_db) {
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
sqlite3_exec(g_inst->topo_sqlite_db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL);
topo_node_sqlite_init(g_inst->topo_sqlite_db);
} else { IL_LOGE("poll exit: SQLite open failed rc=%d", rc); break; }
/* Shared SQLite DB открывается внутри utun_instance_init() (topo_group.c). */
if (utun_instance_init(g_inst) != 0) { IL_LOGE("poll exit: utun_instance_init failed"); break; }
chat_core_sync_my_addresses(g_inst);
fire_local_sockets_event();
@@ -424,22 +406,29 @@ static void* instance_thread(void* arg) {
if (my_gen == g_generation) {
__atomic_store_n(&g_running, 0, __ATOMIC_RELEASE);
if (g_inst) {
chat_event_post(g_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0);
utun_instance_destroy(g_inst);
if (my_inst) {
chat_event_post(my_inst, CHAT_EVT_SERVICE_STOPPED, NULL, 0);
g_inst = NULL;
utun_instance_destroy(my_inst);
my_inst = NULL;
}
if (g_ua) {
if (my_ua) {
standby_deinit();
uasync_destroy(g_ua, 0);
uasync_destroy(my_ua, 0);
g_ua = NULL;
my_ua = NULL;
}
char* stale_cfg = (char*)__atomic_exchange_n(&g_restart_config, NULL, __ATOMIC_ACQUIRE);
u_free(stale_cfg);
u_report_unfreed_blocks();
IL_LOGI("cleanup complete (gen=%d)", my_gen);
} else {
IL_LOGI("cleanup skipped — thread gen=%d but current gen=%d (stale thread)", my_gen, g_generation);
/* stale-поток: глобальные g_inst/g_ua уже принадлежат новому потоку,
но собственные объекты этого потока надо закрыть, иначе течёт fd. */
IL_LOGI("cleanup (stale thread gen=%d vs %d): destroying own inst=%p ua=%p to avoid fd leak",
my_gen, g_generation, (void*)my_inst, (void*)my_ua);
if (my_inst) { utun_instance_destroy(my_inst); my_inst = NULL; }
if (my_ua) { uasync_destroy(my_ua, 0); my_ua = NULL; }
}
__atomic_store_n(&g_thread_running, 0, __ATOMIC_RELEASE);
pthread_mutex_lock(&g_stop_mutex);