【问题标题】:IdHTTPServer hangs on Shutdown in LinuxIdHTTPServer 在 Linux 中挂起
【发布时间】: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 关闭TIdTCPServerTIdHTTPServer 之间没有区别。关闭期间挂起的最常见原因是服务器事件处理程序中的死锁,阻止了一个或多个线程正确终止。例如,通过与试图关闭服务器的同一线程同步,因为Active 设置器是一个阻塞操作。如果没有minimal reproducible example,就很难诊断您的问题,但是从日志中可以清楚地看出有一个线程没有正确终止,这就是TerminateAllYarns() 循环继续运行的原因。
  • 我非常不喜欢这个论坛。现在我不能再添加任何代码了。
  • 另请注意,这发生在Linux下。在 Windows 下,它运行良好,并且在过去 5 年中一直是两台基于 Internet 的服务器 24/7 运行。我刚刚再次测试重新编译为 Windows 服务,它工作正常。这是相同的代码。据报道,在 Linux 中,它每次都失败。

标签: linux delphi httpserver indy10


【解决方案1】:

这是一个已知问题,但目前没有共同的解决方案。但是,如果仅使用 SSL IOhandler,则会出现挂起。因此,我不建议使用 SSL IOHandler,因为它不支持 TLSv1.3 和高级密码算法。相反,请打开 nginx 反向代理服务器,其中 HTTPS 前端和 HTTP 后端连接到您的应用程序。

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center