Почему мой Delphi Indy idHTTpServer перестает отвечать при CLOSE_WAIT?

Окружающая среда

Я создал веб-сервер в Delphi с использованием компонента Indy TidHTTPServer. Я использую Delphi XE2, который поставляется с версией Indy 10.5.8. Сервер работает как настольное приложение с формой, которая отображает журнал соединений и их запросов. Он работает на Windows 7 Professional. Запросы для данных SQL из базы данных Firebird. Ответ JSON. Весь трафик HTTP.

Соревнование

Когда я тестировал его с небольшим количеством пользователей, все работало отлично. Теперь, когда я разослал его примерно 400 пользователям, возникли проблемы со связью. Сервер перестает отвечать на запросы, и единственный способ заставить его ответить снова - это перезагрузить компьютер, на котором он работает, и затем перезапустить его. Необходимость перезагрузки возникает чаще в периоды большого объема.

Симптомы

Используя Windows netstat, я заметил, что всякий раз, когда происходит TCP-соединение типа CLOSE_WAIT, сервер перестает отвечать на запросы, и я должен перезагрузиться снова

Процедура испытаний

Я был в состоянии смоделировать это зависание, даже когда на сервере нет трафика. Я создал веб-страницу, которая отправляет несколько запросов с задержкой между каждым запросом.

На веб-странице я могу указать количество запросов, сколько ждать между каждым запросом и сколько ждать до истечения времени ожидания. Даже в течение одной миллисекунды между запросами сервер, кажется, отвечает без проблем.

Результаты теста

Если я установлю период ожидания каждого запроса на очень маленькое число, например 1 мсек, я могу заставить свой сервер Delphi HTTP зависать. По истечении 1 мсек запросы к моему серверу каждый раз терпят неудачу, как я и ожидал. Тайм-аут настолько мал, что мой сервер не может ответить достаточно быстро.

Чего я не понимаю, так это того, что после принудительного тайм-аута на стороне клиента, даже относительно небольшого числа запросов (менее 50), мой веб-сервер Delphi больше не отвечает ни на какие запросы. Когда я запускаю netstat на серверном компьютере, существует несколько соединений сокетов CLOSE_WAIT. Даже через час и после закрытия моего сервера соединения сокета CLOSE_WAIT сохраняются.

Вопросы

Что здесь происходит? Почему мой Delphi Indy idHTTPServer перестает отвечать, когда есть (хотя бы одно) соединение через сокет CLOSE_WAIT? CLOSE_WAIT не исчезают, и сервер не начинает отвечать снова. Я должен перезагрузиться.

Что я не делаю?

Вот результаты команды netstat, показывающей CLOSE_WAIT:

C:\Windows\system32>netstat -abn | findstr 62000
TCP    0.0.0.0:62000          0.0.0.0:0             LISTENING
TCP    10.1.1.13:62000        9.49.1.3:57036        TIME_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57162        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57215        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57244        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57263        CLOSE_WAIT
TCP    10.1.1.13:62000        9.49.1.3:57279        ESTABLISHED
TCP    10.1.1.13:62000        104.236.216.73:59051  ESTABLISHED

Вот суть моего веб-сервера:

unit MyWebServer;

interface

Uses
...

Type
  TfrmWebServer = class(TForm)
    ...
    IdHTTPServer: TIdHTTPServer;
    ...
    procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    procedure IdHTTPServerDisconnect(AContext: TIdContext);
    procedure btnStartClick(Sender: TObject);
    ...  
    dbFirebird : TIBDatabase;
    txFireird  : TIBTransaction;
    ...
  private
    function CreateSomeResponseStringData: string;
  end;


implementation

procedure TfrmWebServer.btnStartClick(Sender: TObject);
  begin
    {set the IP's and proit to listen on}
    IdHTTPServer.Bindings.Clear;
    IdHTTPServer.Bindings.Add.IP   := GetSetting(OPTION_TCPIP_ADDRESS);
    IdHTTPServer.Bindings.Add.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
    {start the web server}
    IdHTTPServer.Active := TRUE;
    ...
    dbFirebird.Transactrion := txFirebird;
    ...
  end;

procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  var
    qryFirebird : TIBSql;

  function CreateSomeResponseStringData: string;
    begin
      qryFirebird := NIL;
      qryFirebird := TIBSql.Create(IdHTTPServer);
      qryFirebird.Database := dbFirebird;
      dbFirebird.Connected := FALSE;
      dbFirebird.Connected := TRUE;
      qryFirebird.Active := TRUE;
      Result := {...whatever string will be returned}
    end;

  function CreateAnErrorResponse: string;
    begin
      Result := {...whatever string will be returned}
    end;

  begin
    try        
      AResponseInfo.ContentText := CreateSomeResponseStringData;
      {Clean up: What do I do here to make sure that the connection that was served is:
         - properly closed so that I don't run out of resourses?
         - anything that needs to be cleaned up is freed so no memory leaks
         - TIME_WAIT, CLOSE_WAIT, any other kind of _WAITs are not accumulating?}
    except;
      AResponseInfo.ContentText := CreateAnErrorResponse;
    end;
    qryFirebird.Free;
  end;

procedure TfrmWebServer.IdHTTPServerDisconnect(AContext: TIdContext);
  begin
    {Maybe I do the "Clean Up" here? I tried Disconnect as shown but still lots of 
    TIME_WAIT tcp/ip connections accumulate. even after the app is closed}    
    AContext.Connection.Disconnect;
  end;

end.  

1 ответ

Решение

В этом коде есть как минимум две основные проблемы, которые могут вызвать сбой:

  1. База данных и объекты транзакций являются глобальными для всех потоков, созданных IdHTTPServer, Когда вы отключите базу данных, она будет отключена для всех потоков.

  2. Если при назначении содержимого текста этой строки произошла ошибка AResponseInfo.ContentText := CreateAnErrorResponse; не находится в блоке исключения.

Вот как я бы это исправить:

...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
  begin
    {set the IP's and proit to listen on}
    IdHTTPServer.Bindings.Clear;
    IdHTTPServer.Default.Port    := Str2Int(GetSetting(OPTION_TCPIP_PORT));
    IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
    {start the web server}
    IdHTTPServer.Active := TRUE;
    ...
  end;

procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  var
    {make these local to each thread}
    qryFirebird : TIBSql;
    dbFirebird  : TIBDatabase;
    txFirebird  : TIBTransaction;

  function CreateSomeResponseStringData: string;
    begin
      dbFirebird  := TIBDatbase.Create(IdHTTPServer);
      txFirebird  := TIBTransaction.Create(IdHTTPServer);
      qryFirebird := TIBSql.Create(IdHTTPServer);
      dbFirebird.Transaction := txFirebird;
      qryFirebird.Database := dbFirebird;
      ...Add params that do the log in to database
      dbFirebird.Connected := TRUE;
      qryFirebird.Active := TRUE;
      Result := {...whatever string will be returned}
    end;

  function CreateAnErrorResponse: string;
    begin
      Result := {...whatever string will be returned}
    end;

  begin
    try
      try        
        ...
        AResponseInfo.ContentText := CreateSomeResponseStringData;
        ...
      except;
        try
          AResponseInfo.ContentText := CreateAnErrorResponse;
        except
          {give up}
        end;
      end;
    finaly
      qryFirebird.Free;
      dbFirebird.Free;
      txFirebird.Free;
    end;
  end;

end.     
Другие вопросы по тегам