【发布时间】:2026-01-30 00:50:02
【问题描述】:
在 Linux 下,TIdHTTPServer 在关机时挂起 (IdHTTPServer1.Active := false),原因是 TIdScheduler.TerminateAllYarns() 中的循环未结束。
问题是永久性(在我的开发 PC 上),并且仅当至少有一个与 HTTP 服务器的连接时。
然而,奇怪的是,虽然问题出现在基本 Indy 系统的共享代码中,但它只发生在关闭 HTTP 服务器时,而不发生在关闭两个 TIdTCPServer 时s 之前,并且其中也有打开的连接。它们正常工作。
我通过代码添加了很多 Syslog 条目,syslog 看起来是这样的:
Aug 31 09:32:32 debian11 sartrackserver[31893]: DebugLogShutdown: HTTP Server stop
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomHTTPServer.Shutdown: SetSessionList(nil)
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomHTTPServer.SetSessionList: LSessionList <> AValue
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomHTTPServer.SetSessionList: FImplicitSessionList
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomHTTPServer.SetSessionList: IdDisposeAndNil
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomHTTPServer.SetSessionList: DONE
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomHTTPServer.Shutdown: inherited Shutdown
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomTCPServer.Shutdown
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdCustomTCPServer.StopListening
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdSocketHandle.CloseSocket: HandleAllocated
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdSocketHandle.CloseSocket calls Disconnect
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdSocketHandle.Disconnect calls GStack.Disconnect(Handle)
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdSocketHandle.CloseSocket: NOT HandleAllocated
Aug 31 09:32:32 debian11 sartrackserver[31893]: IdSocketHandle.Disconnect: GStack.Disconnect DONE
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdSocketHandle.CloseSocket: NOT HandleAllocated
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.StopListening Done
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.Shutdown TerminateAllThreads
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads: LockList
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads: List.Count=1
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads: Assert 1 okay
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads: AssertClassname 1 okay
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads: Calling DoTerminateContext
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.DoTerminateContext calls CloseSocket
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdSocketHandle.CloseSocket: HandleAllocated
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdSocketHandle.CloseSocket calls Disconnect
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdSocketHandle.Disconnect calls GStack.Disconnect(Handle)
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdSocketHandle.Disconnect: GStack.Disconnect DONE
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads: UnLockList
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdCustomTCPServer.TerminateAllThreads calls TerminateAllYarns
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns loop: Count=1
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns DONE
Aug 31 09:32:33 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns loop: Count=1
Aug 31 09:32:34 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns DONE
Aug 31 09:32:34 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns loop: Count=1
Aug 31 09:32:34 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns DONE
Aug 31 09:32:34 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns loop: Count=1
Aug 31 09:32:35 debian11 sartrackserver[31893]: IdScheduler.TerminateAllYarns DONE
etc.
TerminateYarns() 中的实际代码是这样的:
procedure TIdScheduler.TerminateAllYarns;
var
i: Integer;
LList: TIdYarnList;
begin
Assert(FActiveYarns<>nil);
while True do begin
// Must unlock each time to allow yarns that are terminating to remove themselves from the list
// Bart
LList := FActiveYarns.LockList;
try
syslog(LOG_NOTICE, 'IdScheduler.TerminateAllYarns loop: Count='+LList.Count.ToString);
if LList.Count = 0 then begin
Break;
end;
for i := LList.Count - 1 downto 0 do begin
TerminateYarn(
{$IFDEF HAS_GENERICS_TList}LList.Items[i]{$ELSE}TIdYarn(LList.Items[i]){$ENDIF} <<< Never deletes anything from the list
);
end;
finally
FActiveYarns.UnlockList;
end;
IndySleep(500); // Wait a bit before looping to prevent thrashing
syslog(LOG_NOTICE, 'IdScheduler.TerminateAllYarns DONE');
end;
end;
知道我可以尝试解决这个问题吗?
更新:这是我的代码:
class procedure TDebugLogEventHandlers.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
TmpList: TStringList;
ST: TSystemTime;
begin
// if StatsListInProgress then
if true then
begin
TmpList := TStringList.Create;
try
TmpList.Add('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"><html><head>');
TmpList.Add('<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type"><title>SARTrack Server Stats</title>');
TmpList.Add('<meta http-equiv="refresh" content="5">');
TmpList.Add('</head><body>');
TmpList.Add('Data Temporarily Unavailable');
TmpList.Add(MyDateToStrEx(ST)+' '+MyTimeToStr(ST)+' UTC');
TmpList.Add('</body></html>');
try
AResponseInfo.ContentText := TmpList.Text;
except
on E:Exception do
LocalLog('TDebugLogEventHandlers: '+E.Message,d_error);
end;
LocalLog('TDebugLogEventHandlers: StatsList is in use during request',d_error);
finally
TmpList.Free;
end;
end else
begin
{try
AResponseInfo.ContentText := StatsList.Text;
except
on E:Exception do
LocalLog('TDebugLogEventHandlers: '+E.Message,d_error);
end;}
end;
end;
procedure DebugLogInit;
begin
try
Log('DebugLogInit');
IdHTTPServer1 := TIdHTTPServer.Create(nil);
IdHTTPServer1.OnCommandGet := TDebugLogEventHandlers.IdHTTPServer1CommandGet;
IdHTTPServer1.OnException := TDebugLogEventHandlers.IdHTTPServer1Exception;
IdHTTPServer1.DefaultPort := 8053;
IdHTTPServer1.KeepAlive := true;
IdHTTPServer1.MaxConnections := 10;
except
on E:Exception do
Log('DebugLogInit: '+E.Message,d_error);
end;
end;
procedure DebugLogShutdown;
begin
Log('Shutting down DebugLog system...');
try
{TimerQueue.Release(WebRefreshTimer);
TimerQueue.Release(Port2ClearTimer);
Log('DebugLogShutdown: TCP Server stop...');
IdTCPServerDebug.Active := false;
Log('DebugLogShutdown: Freeing TCP Server...');
IdTCPServerDebug.Free;}
Log('DebugLogShutdown: HTTP Server stop');
IdHTTPServer1.Active := false;
Log('DebugLogShutdown: HTTP Server free');
IdHTTPServer1.Free;
{StatsList.Free;
IOFormShutDown;
CloseAllIniFiles;
Port2StationList.Free;}
except
on E:Exception do
Log('DebugLogForm.Shutdown: '+E.Message,d_error);
end;
end;
更新2
我更深入地研究了代码,这就是它出错的地方:
IdCustomTCPServer.TerminateAllThreads calls TerminateAllYarns
Sep 1 07:37:47 debian11 sartrackserver[33098]: IdScheduler.TerminateAllYarns loop: Count=1
Sep 1 07:37:47 debian11 sartrackserver[33098]: IdSchedulerOfThread.TerminateYarn
Sep 1 07:37:47 debian11 sartrackserver[33098]: IdSchedulerOfThread.TerminateYarn calls LThread.Stop
Sep 1 07:37:48 debian11 sartrackserver[33098]: IdScheduler.TerminateAllYarns DONE
Sep 1 07:37:48 debian11 sartrackserver[33098]: IdScheduler.TerminateAllYarns loop: Count=1
Sep 1 07:37:48 debian11 sartrackserver[33098]: IdSchedulerOfThread.TerminateYarn
Sep 1 07:37:48 debian11 sartrackserver[33098]: IdSchedulerOfThread.TerminateYarn calls LThread.Stop
Sep 1 07:37:48 debian11 sartrackserver[33098]: IdScheduler.TerminateAllYarns DONE
Sep 1 07:37:48 debian11 sartrackserver[33098]: IdScheduler.TerminateAllYarns loop: Count=1
代码如下所示:
procedure TIdSchedulerOfThread.TerminateYarn(AYarn: TIdYarn);
var
LYarn: TIdYarnOfThread;
LThread: TIdThreadWithTask;
begin
// Bart
syslog(LOG_NOTICE, 'IdSchedulerOfThread.TerminateYarn');
Assert(AYarn<>nil);
LYarn := TIdYarnOfThread(AYarn);
LThread := LYarn.Thread;
if (LThread <> nil) and (not LThread.Suspended) then begin
// Is still running and will free itself
syslog(LOG_NOTICE, 'IdSchedulerOfThread.TerminateYarn calls LThread.Stop');
LThread.Stop;
// Dont free the yarn. The thread frees it (IdThread.pas)
end else
begin
// If suspended, was created but never started
// ie waiting on connection accept
// RLebeau: free the yarn here as well. This allows TIdSchedulerOfThreadPool
// to put the suspended thread, if present, back in the pool.
syslog(LOG_NOTICE, 'IdSchedulerOfThread.TerminateYarn calls IdDisposeAndNil(LYarn)');
IdDisposeAndNil(LYarn);
end;
end;
更新 3
procedure TIdThread.Stop;
begin
syslog(LOG_NOTICE, 'IdThread.Stop: Try enter lock');
FLock.Enter;
syslog(LOG_NOTICE, 'IdThread.Stop: locked');
try
if not Stopped then
begin
case FStopMode of
smTerminate: begin syslog(LOG_NOTICE, 'IdThread.Stop calls Terminate'); Terminate; end
// smSuspend: ;{DO not suspend here. Suspend is immediate. See Execute for implementation};
else syslog(LOG_NOTICE, 'IdThread.Stop does NOT call Terminate');
end;
Include(FOptions, itoStopped);
end else syslog(LOG_NOTICE, 'IdThread.Stop: is Stopped. Not doing anything');
finally FLock.Leave; end;
end;
并终止:
procedure TIdThread.Terminate;
begin
//this assert can only raise if terminate is called on an already-destroyed thread
Assert(FLock<>nil);
FLock.Enter;
try
syslog(LOG_NOTICE, 'IdThread.Terminate calls Include');
Include(FOptions, itoStopped);
syslog(LOG_NOTICE, 'IdThread.Terminate calls inherited Terminate');
inherited Terminate;
finally FLock.Leave; end;
end;
停止和终止不会释放纱线。 所以它永远不会被释放。 如果我错了,请纠正我...
【问题讨论】:
-
将列表的第一项分配给局部变量,然后解锁列表,然后调用
TerminateYarn( firstItem );- 这确保锁定的列表不是问题的一部分(如:can't被从列表中删除,因为列表被锁定)。 -
@AmigoJack
TerminateYarn()只是简单地通知线程终止自身,然后列表被解锁。线程停止运行时会销毁自己,这将销毁关联的TIdYarn,从而将自己从列表中删除。 -
@BartKindt 关闭
TIdTCPServer和TIdHTTPServer之间没有区别。关闭期间挂起的最常见原因是服务器事件处理程序中的死锁,阻止了一个或多个线程正确终止。例如,通过与试图关闭服务器的同一线程同步,因为Active设置器是一个阻塞操作。如果没有minimal reproducible example,就很难诊断您的问题,但是从日志中可以清楚地看出有一个线程没有正确终止,这就是TerminateAllYarns()循环继续运行的原因。 -
我非常不喜欢这个论坛。现在我不能再添加任何代码了。
-
另请注意,这仅发生在Linux下。在 Windows 下,它运行良好,并且在过去 5 年中一直是两台基于 Internet 的服务器 24/7 运行。我刚刚再次测试重新编译为 Windows 服务,它工作正常。这是相同的代码。据报道,在 Linux 中,它每次都失败。
标签: linux delphi httpserver indy10