fix(tci): счётчик клиентских потоков переживал неудачный старт — Stop висел вечно

AcceptLoop делал InterLockedIncrement(FThreadCount) и следом создавал поток
безо всякой защиты. Не родился поток (память, лимит потоков в системе) —
исключение уходило из AcceptLoop и TTCIAcceptThread.Execute, accept-поток умирал
при FRunning = True, а счётчик оставался ≥ 1 навсегда. Ждут его на шаге 4
TTCIServer.Stop БЕЗ таймаута (и намеренно: следом освобождаются и клиенты, и сам
сервер), так что программа зависала и на выходе, и на любой смене настроек TCI.

Инкремент остался до старта — переносить его после Start нельзя, там гонка с
ThreadDone уходящего потока. Спавн обёрнут в try/except: счётчик возвращается
назад, сокет гасится Kill, клиент метится FClosed и достаётся тик-потоку
(ReapClients), как при обычном отключении. Объект потока уносит себя сам —
упавший конструктор сразу, поднявшийся по FreeOnTerminate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:06:53 +03:00
co-authored by Claude Opus 5
parent 6e4ba1c1c5
commit 3a5dced17c
+16 -2
View File
@@ -1009,9 +1009,23 @@ begin
Continue; Continue;
end; end;
// Счётчик — ДО старта: иначе Stop успел бы проскочить шаг 4 между Start и
// первой строкой потока. ★А не родился поток (память, лимит потоков) —
// сразу возвращаем счётчик назад: ждут его на шаге 4 без таймаута, и
// лишняя единица подвешивала бы и выход из программы, и любую смену
// настроек TCI. Объект потока уносит себя сам: упавший конструктор — сразу,
// а поднявшийся — по FreeOnTerminate.
InterLockedIncrement(FThreadCount); InterLockedIncrement(FThreadCount);
T := TTCIClientThread.Create(Self, Client); try
T.Start; T := TTCIClientThread.Create(Self, Client);
T.Start;
except
InterLockedDecrement(FThreadCount);
// Обслуживать клиента больше некому: гасим сокет и метим на освобождение
// — заберёт тик-поток (ReapClients), как и обычное отключение.
Client.Kill;
Client.FClosed := True;
end;
end; end;
end; end;