fix(tci): смена настроек TCI освобождала клиентов, не сняв тапы DSP

ApplySettings гасил сервер в порядке Stop → StopAllStreams → SetTaps(False), а
TTCIServer.Stop на шаге 5 освобождает всех клиентов. Тапы аудио и IQ в этот
момент ещё стояли, и DSP-поток, войдя в OnAudioTap между возвратом из Stop и
захватом FStreamLock в StopAllStreams, шёл по FStreams в FeedAudio → EmitFull →
FClient.SendBin, то есть брал FOutLock уже освобождённого объекта. Достаточно
было сменить порт или снять галку «Enable» в Settings → CAT, пока у клиента жив
AUDIO_START или IQ_START.

Порядок приведён к тому, что и так был в Destroy: ПЕРВЫМИ снимаем тапы (их
снятие ждёт выхода DSP-потока из вызова), потом останавливаем сервер, потом
гасим потоки — их объекты ссылаются на клиентов, поэтому они последние.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:05:29 +03:00
co-authored by Claude Opus 5
parent b62369ec6b
commit 4b715c8b03
+9 -4
View File
@@ -470,11 +470,16 @@ begin
Exit(False);
OldPort := FServer.Port;
FServer.Stop;
// Сервер остановлен — клиентов больше нет, значит и потоки чужие: снимаем
// тапы, иначе DSP-поток продолжал бы носить аудио в никуда.
StopAllStreams;
// ★ПЕРВЫМИ снимаем тапы — тот же порядок, что и в Destroy, и по той же
// причине: их зовёт DSP-поток. Раньше здесь сначала звали FServer.Stop, а он
// на шаге 5 освобождает клиентов; тапы в этот момент ещё стояли, и DSP-поток,
// войдя в OnAudioTap между Stop и StopAllStreams, писал в уже освобождённого
// клиента (FStreams[i].FeedAudio -> EmitFull -> FClient.SendBin). Ловилось
// сменой порта или снятием галки «Enable» при живом AUDIO_START/IQ_START.
SetTaps(False);
FServer.Stop;
// Потоки — после остановки сервера: их объекты ссылаются на клиентов.
StopAllStreams;
if not T.Enabled then
begin
FCfg := T;