mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(tci): Stop прокачивает Synchronize и на accept/тик-потоках, а не только на клиентских
Шаг 4 остановки сервера намеренно ждёт клиентские потоки с прокачкой CheckSynchronize — Stop зовут из потока контроллера, и клиентский поток может как раз висеть на FController.Invoke, то есть на TThread.Synchronize к этому самому потоку. Шаги 2 и 3 при этом делали глухой WaitFor, хотя тик-поток ходит той же дорогой: ReapClients → Disconnected → HandleDisconnect → StopTxOf → Invoke. Проверка CanInvoke у адаптера от этого не спасает: она читает Stopping ДО входа в Synchronize, а FStopping поднимается в начале Stop — значит клиент, отвалившийся ровно в момент снятия галки «Enable TCI server» (или закрытия приложения), успевает проскочить в это окно. Дальше тик-поток стоит в Synchronize, главный — в FTickThread.WaitFor, и таймаута ни у того, ни у другого нет: приложение висит намертво. Общий JoinPumped: ждём Finished, прокачивая очередь (Sleep, если зовут не из главного потока), и только потом WaitFor + FreeAndNil. Finished в FPC взводится ПОСЛЕ DoTerminate, поэтому финальный WaitFor уже не может застать чужой Synchronize и не блокирует. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -868,6 +868,22 @@ begin
|
|||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure JoinPumped(var T: TThread);
|
||||||
|
// Ожидание выхода потока с прокачкой очереди Synchronize — то же, что делает
|
||||||
|
// шаг 4 в Stop, но для accept- и тик-потока. Глухой WaitFor тут — взаимный
|
||||||
|
// клин: тик-поток из ReapClients зовёт Disconnected → адаптер → Invoke, а
|
||||||
|
// Invoke это TThread.Synchronize к потоку контроллера, то есть ровно к тому,
|
||||||
|
// кто сейчас ждёт в WaitFor. Проверка CanInvoke у адаптера не спасает: она
|
||||||
|
// читает Stopping ДО входа в Synchronize, и клиент, отвалившийся в момент
|
||||||
|
// остановки сервера, успевает проскочить в это окно.
|
||||||
|
begin
|
||||||
|
if T = nil then Exit;
|
||||||
|
while not T.Finished do
|
||||||
|
if GetCurrentThreadId = MainThreadID then CheckSynchronize(5) else Sleep(5);
|
||||||
|
T.WaitFor; // Finished взводится уже после DoTerminate — не блокирует
|
||||||
|
FreeAndNil(T);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TTCIServer.KillAll;
|
procedure TTCIServer.KillAll;
|
||||||
var i: Integer;
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
@@ -897,11 +913,11 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Шаг 2: дожидаемся accept-потока — после него новых клиентов не появится.
|
// Шаг 2: дожидаемся accept-потока — после него новых клиентов не появится.
|
||||||
if FAcceptThread <> nil then begin FAcceptThread.WaitFor; FreeAndNil(FAcceptThread); end;
|
JoinPumped(FAcceptThread);
|
||||||
|
|
||||||
// Шаг 3: будим клиентские потоки, висящие в recv, и останавливаем тик.
|
// Шаг 3: будим клиентские потоки, висящие в recv, и останавливаем тик.
|
||||||
KillAll;
|
KillAll;
|
||||||
if FTickThread <> nil then begin FTickThread.WaitFor; FreeAndNil(FTickThread); end;
|
JoinPumped(FTickThread);
|
||||||
|
|
||||||
// Шаг 4: ждём выхода клиентских потоков — БЕЗ таймаута. Прокачивая очередь
|
// Шаг 4: ждём выхода клиентских потоков — БЕЗ таймаута. Прокачивая очередь
|
||||||
// Synchronize: Stop зовёт поток контроллера (UI), а клиентский поток может
|
// Synchronize: Stop зовёт поток контроллера (UI), а клиентский поток может
|
||||||
|
|||||||
Reference in New Issue
Block a user