Isolate call audio TX tasks by instance binding epoch

This commit is contained in:
evgeny committed 2026-10-26 14:57:24 +01:00
1 parent 266406261a
commit e11eca93bb
2 files changed
+28 -7

No files matched your search

+24 -5
View File
@@ -48,6 +48,7 @@ static int g_prepared_capture = 0;
static int g_aec_enabled = 1; /* по умолчанию: настройка aec_enabled */
static struct audio_compressor* g_compressor = NULL;
static uint64_t g_tx_generation;
static uint64_t g_instance_generation; /* Эпоха привязки; смена звонка её не меняет. */
static uint64_t g_capture_epoch;
static int64_t g_capture_end = INT64_MIN;
static int g_codec_delay;
@@ -69,6 +70,7 @@ struct call_audio_tx {
struct posted_task task;
struct UTUN_INSTANCE* inst;
uint64_t call_id, generation, created_tb;
uint64_t instance_generation;
int len;
struct call_capture_time timing;
uint8_t opus[256];
@@ -77,9 +79,20 @@ struct call_audio_tx {
static void call_audio_send(void* arg) {
struct call_audio_tx* tx = arg;
pthread_mutex_lock(&g_mtx);
int current = tx->generation == g_tx_generation;
int owner = tx->instance_generation == g_instance_generation && g_inst == tx->inst;
int current = owner && tx->generation == g_tx_generation;
if (current) call_audio_duration_add(&g_post_time, (get_time_tb() - tx->created_tb) * 100);
if (g_tx_pending) --g_tx_pending;
/* Старый звонок той же привязки всё ещё занимает свой слот очереди. */
if (owner) {
if (g_tx_pending) --g_tx_pending;
else DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: TX pending underflow call=%llu instance_epoch=%llu", CALL_AUDIO_ID,
(unsigned long long)tx->call_id, (unsigned long long)tx->instance_generation);
}
if (!current) DEBUG_TRACE(DEBUG_CATEGORY_CALL,
"%s: stale TX call=%llu owner=%d instance_epoch=%llu/%llu call_epoch=%llu/%llu pending=%u", CALL_AUDIO_ID,
(unsigned long long)tx->call_id, owner, (unsigned long long)tx->instance_generation,
(unsigned long long)g_instance_generation, (unsigned long long)tx->generation,
(unsigned long long)g_tx_generation, g_tx_pending);
int send = current && g_active && !g_ending && g_inst == tx->inst && g_call_id == tx->call_id;
if (get_time_tb() - tx->created_tb > 2000) {
send = 0;
@@ -104,13 +117,16 @@ static int call_audio_decode_cb(void* arg, const uint8_t* enc, int len, int16_t*
}
int call_audio_init(struct UTUN_INSTANCE* inst) {
if (!inst) return -1;
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: init missing instance", CALL_AUDIO_ID); return -1; }
pthread_mutex_lock(&g_mtx);
if (g_inst == inst) { pthread_mutex_unlock(&g_mtx); return 0; }
unsigned int previous_pending = g_tx_pending;
g_inst = inst; g_tx_pending = 0;
++g_instance_generation;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: initialized inst=%p instance_epoch=%llu previous_pending=%u", CALL_AUDIO_ID,
(void*)inst, (unsigned long long)g_instance_generation, previous_pending);
pthread_mutex_unlock(&g_mtx);
call_set_audio_ops(inst, &g_ops);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: initialized", CALL_AUDIO_ID);
return 0;
}
@@ -119,8 +135,10 @@ void call_audio_destroy(struct UTUN_INSTANCE* inst) {
call_set_audio_ops(inst, NULL);
pthread_mutex_lock(&g_mtx);
g_inst = NULL;
++g_instance_generation;
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: destroyed inst=%p instance_epoch=%llu pending=%u", CALL_AUDIO_ID,
(void*)inst, (unsigned long long)g_instance_generation, g_tx_pending);
pthread_mutex_unlock(&g_mtx);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "%s: destroyed", CALL_AUDIO_ID);
}
/* ── uasync-поток: приём медиа (кладём кодированный кадр в jitter-буфер) ── */
@@ -452,6 +470,7 @@ static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count,
}
task->inst = inst; task->call_id = call_id;
task->generation = g_tx_generation; task->created_tb = get_time_tb();
task->instance_generation = g_instance_generation;
task->timing = (struct call_capture_time){capture_pts_us, g_capture_epoch, (uint32_t)g_codec_delay};
task->len = len;
memcpy(task->opus, opus, (size_t)len);
+4 -2
View File
@@ -32,9 +32,11 @@ extern "C" {
#define CALL_AUDIO_FRAME_SAMPLES (CALL_AUDIO_SAMPLE_RATE * CALL_AUDIO_FRAME_MS / 1000) /* 960 */
/* Зарегистрировать общий для процесса аудио-движок в call.c; повтор с тем же inst идемпотентен.
* Известный дефект 26.10.2026: destroy/init на живом uasync со старыми TX-задачами нарушает
* учёт лимита8 новых задач. Подробности и план изоляции эпох привязки — doc/call_audit_findings.txt,раздел6. */
* init/destroy сериализованы lifecycle ядра; producers остановлены перед освобождением inst/ua.
* Лимит8 TX-задач относится к текущей привязке. Старые задачи не отправляют медиа и не меняют
* её счётчик/позднюю статистику; uasync освобождает их после callback либо при destroy. 0 = успех, -1 = нет inst. */
int call_audio_init(struct UTUN_INSTANCE* inst);
/* Снять регистрацию и инвалидировать TX-привязку; codec/jitter освобождаются отдельно через stop/release. */
void call_audio_destroy(struct UTUN_INSTANCE* inst);
/* GUI-поток: создать codec + jitter + tones. Старый API принимает raw PCM и использует core AEC.