Конференция "Сети" » Out of memory при работе приложения
 
  • Maloj2007 © (12.10.10 14:59) [0]
    Доброго всем времени суток. Возникла следующая проблема, пишу приложение с использованием WinAPI функций. Задача приложения следующая: Принимать входящие соединения от клиентов, парсить трафик и перенаправлять соединения на нужный мне порт. Получается что-то вроде порт-маппеда. Количество одновременно работающих соединений 100-1000. После долгих тестов (более суток работы) при попытке соединения приложение начало выдавать ошибку "Out of memory". В самом приложение используются массивы вида array [0..7FFF] of char для чтения данных и передачи. В каждом потоке получается 4 таких массива. Если соединение разрывается, поток соответственно уничтожается. функции GetMem, FreeMem и т.п. не использую. Кто может подсказать в чем может быть проблема?
  • Медвежонок Пятачок © (12.10.10 15:20) [1]
    проблема в отсутствии лога по которому было бы видно, что Если соединение разрывается, поток соответственно уничтожается.
  • Maloj2007 © (12.10.10 15:33) [2]
    Логи собственно присутствуют. Поток действительно уничтожается. Появилась мысль, что проблема может быть в следующем: указатели на все потоки хранятся в структуре типа TList. Возможно ли, что проблема в ней? Т.к. к серверу за сутки подключений было более 50000, процедуру Pack я не вызывал. При создании потока он автоматически добавляется в структуру, затем удаляется. По логам видно что идентификатор потока только увеличивается, идентификатор берется:

    var
    Id:Integer;
    tunels:Tlist;
    ...
    Id:=integer(tunels.items[I]);


    Если  я правильно понимаю, то в памяти постоянно выделяется место под указатель, но при удалении итема, место не высвобождается (а если и высвобождается, то не используется повторно).
  • Сергей М. © (12.10.10 15:47) [3]
    50000 * 4 ~ 200 кб

    Мелочь.
    Ищи не там где светлее, а где лежит.
  • Медвежонок Пятачок © (12.10.10 15:59) [4]
    Логи собственно присутствуют.

    Ну тогда ты как минимум знаешь на какой операции получается аутовмемори.
  • Maloj2007 © (12.10.10 16:32) [5]

    > Ну тогда ты как минимум знаешь на какой операции получается
    > аутовмемори

    Да, сообщение вылетает при попытке создания нового тунеля.

     TTunel = class (TObject)
       initserversocket,              //Инициализированный Серверный сокет
       serversocket,              //Серверный сокет
       clientsocket : integer;    //клиентский
       curSockEngine : TObject;   //Сокетный движек создавший этот объект (для возврата назад по обьектам)
       ConnectOrErrorEvent : Cardinal;   //эвент возникающий при соединении клиентского потока
       hServerThread, hClientThread  : integer;  //хендлы потоков
       idServerThread, idClientThread : LongWord;  //threadid потоков
       LastTime:TDateTime; //для разъединения по таймауту
     public
       ErrorMessage:String;
       NeedDeInit: boolean;
       TunelWork:boolean;
       MustBeDestroyed : boolean;
       procedure SendAction(action : byte);
       procedure NewAction(action: byte; Caller: TObject);
       procedure EncryptAndSend(Packet:Tpacket; ToServer:Boolean);
     published
       constructor Create(SockEngine : TObject);
       procedure   Run;
       destructor Destroy; override;
     end;

  • Сергей М. © (12.10.10 16:40) [6]

    > Maloj2007 ©   (12.10.10 16:32) [5]


    Здесь тоже светло, но лежит не здесь.
  • Maloj2007 © (12.10.10 16:49) [7]

    > Сергей М. ©   (12.10.10 16:40) [6]

    По твоим подозрениям где может лежать? Почитал форумы, многие пишут что такая проблема возникает при частом выделении и освобождении памяти.
  • Сергей М. © (12.10.10 17:04) [8]

    > Maloj2007 ©   (12.10.10 16:49) [7]


    Ну так в приведенном тобой коде нет ни намека ни на выделение ни на освобождение)
  • Maloj2007 © (12.10.10 17:14) [9]
    Кидаю основные функции маппета, т.е. функции которые исполняю.тся потоками.


    type
     TCharArray = array[0..$7FFF] of AnsiChar;
     TCharArrayEx = array[0..$FFFE] of AnsiChar; //2х
     PPacket = ^TPacket;  
     TPacket = packed record case Integer of
       0: (Size: Word;
           Data: array[0..$7FFE] of Byte);
       1: (PacketAsByteArray: array[0..$7FFF] of Byte);
       2: (PacketAsCharArray: TCharArray);
       3: (pckSize: Word;
           pckId: Byte;
           pckData: array[0..$7FFE] of Byte);
     end;

     TTunel = class (TObject)
       initserversocket,              //Инициализированный Серверный сокет
       serversocket,              //Серверный сокет
       clientsocket : integer;    //клиентский
       curSockEngine : TObject;   //Сокетный движек создавший этот объект (для возврата назад по обьектам)
       ConnectOrErrorEvent : Cardinal;   //эвент возникающий при соединении клиентского потока
       hServerThread, hClientThread  : integer;  //хендлы потоков
       idServerThread, idClientThread : LongWord;  //threadid потоков
       EncDec:TEncDec;
       LastTime:TDateTime;
     public
       ErrorMessage:String;
       NeedDeInit: boolean;
       TunelWork:boolean;
       MustBeDestroyed : boolean;
       ServerKey:Integer;
       UserKey:Integer;
       IsBot:Boolean;
       procedure SendAction(action : byte);
       procedure NewAction(action: byte; Caller: TObject);
       procedure EncryptAndSend(Packet:Tpacket; ToServer:Boolean);
     published
       constructor Create(SockEngine : TObject);
       procedure   Run;
       destructor Destroy; override;
     end;

     TSocketEngine = class (TObject)
       tunels : TList;
     private
       WSA: TWSAData;
       hServerListenThread  : integer;
       idServerListenThread: Cardinal;
       ServerListenSock: Integer;
       function  WaitClient(var hSocket, NewSocket: TSocket): Boolean;
       function  WaitForData(Socket: TSocket; Timeout: Longint): Boolean;
       procedure DeInitSocket(VAR hSocket : integer; const ExitCode: Integer);
       function  InitSocket(var hSocket: TSocket; Port: Word; IP: String): Boolean;
       function  GetSocketData(Socket: TSocket; var Data; const Size: Word): Boolean;
       function  ConnectToServer(var hSocket: TSocket; Port: Word; IP: Integer): Boolean;
     public
       ErrorMessage:String;
       //Установать перед Init
       ServerPort : Word;
       //можно менять в момент работы
       RedirrectIP : Integer;
       RedirrectPort : Word;
       //установить флаг если надо уничтожить
       isServerTerminating : boolean;
       procedure SendAction(action : byte);
       procedure NewAction(action: byte; Caller: TObject);
     published
       procedure DestroyDeadTunels;
       constructor Create; //создание и предустановка
       Procedure StartServer; //запуск, вызывать после креейта и установки всех проперти
       destructor Destroy; override; //по цепочке разрушит все имеющиеся экземпляры TTunel
     end;

  • Maloj2007 © (12.10.10 17:17) [10]
    {=============================Thread=============================}
    procedure ServerListen(CurrentEngine: TSocketEngine);
    var
     NewSocket: TSocket;
     NewTunel : Ttunel;
    begin
     try
       with CurrentEngine do
       begin
         if not InitSocket(ServerListenSock, ServerPort, '0.0.0.0') then
         begin
           SendAction(TSockEngineActionListenErrror);
           exit;
         end;
         SendAction(TSockEngineActionListen);
         while WaitClient(ServerListenSock, NewSocket) do
         begin
           SendAction(TSockEngineActionNewConnect);
           //новое соединение на серверный сокет. создаем тунель.
           NewTunel := TTunel.Create(CurrentEngine);
           NewTunel.serversocket := NewSocket; //айди серверного сокета = наш индефикатор
           NewTunel.initserversocket := NewSocket; //айди серверного сокета = наш индефикатор
           NewTunel.Run; //и запускаем его
         end;
     end;
     except
       Addtolog('Error ServerListen'+IntToStr(GetLastError));
     end;
    end;

    procedure ServerBody(thisTunel:Ttunel);
    var
     StackAccumulator : TCharArrayEx;
     PreAccumulator : TCharArray;
     AccumulatorLen : Cardinal;
     BytesInStack : Longint;
     curPacket,NewPacket : TPacket;
     RecvBytes : Int64;
     PreSize, LastResult : Word;
     EventTimeout : boolean;
     IP: Integer;
     IPb:array[0..3] of Byte absolute ip;
     buff:String;
    begin
     try
       with TSocketEngine(thisTunel.curSockEngine) do
       begin
         thisTunel.ConnectOrErrorEvent := CreateEvent(nil, true,false,PChar('ConnectOrErrorEvent'+IntToStr(thisTunel.hServerThread)));
         thisTunel.hClientThread :=BeginThread(nil, 0, @ClientBody, thisTunel, 0, thisTunel.idClientThread);
         thisTunel.SendAction(TTunelActionConnectClient);
         EventTimeout := (WaitForSingleObject(thisTunel.ConnectOrErrorEvent, 30000) <> 0);
         if (EventTimeout) or (not thisTunel.TunelWork) then
         begin
           CloseHandle(thisTunel.ConnectOrErrorEvent);
           thisTunel.MustBeDestroyed := true;
           thisTunel.TunelWork := false;
           DeinitSocket(thisTunel.serversocket,WSAGetLastError);
           TerminateThread(thisTunel.hServerThread,0);
         end;
         CloseHandle(thisTunel.ConnectOrErrorEvent);
         ip := RedirrectIP;
         AccumulatorLen := 0;
         LastResult := 1;
         FillChar(PreAccumulator[0],$7fff,0);
         While (thisTunel.serversocket <> -1) do
         try
           ioctlsocket(thisTunel.serversocket, FIONREAD, BytesInStack);
           if BytesInStack = 0 then
             BytesInStack := 1;
           RecvBytes := recv(thisTunel.serversocket, PreAccumulator[0], BytesInStack, 0);//Читаем 1 байт или весь буффер сразу
           if RecvBytes <= 0 then
             break
           else
             PreSize := RecvBytes;
           LastResult := PreSize;
           if lastresult = 1 then
           begin
             ioctlsocket(thisTunel.serversocket, FIONREAD, BytesInStack);
             if BytesInStack > $7FFE then
               BytesInStack := $7FFE;
             if BytesInStack > 0 then
             begin
               RecvBytes := recv(thisTunel.serversocket, PreAccumulator[presize], BytesInStack, 0);
               if RecvBytes <= 0 then
                 break
               else
                 LastResult := LastResult + RecvBytes;
             end;
           end;
           if LastResult > 0 then
           begin
             Move(PreAccumulator[0], StackAccumulator[AccumulatorLen], LastResult);
             FillChar(PreAccumulator[0],$7fff,0);
             Inc(AccumulatorLen, LastResult);
         except
           break;
         end;
         try
           thisTunel.sendAction(TTunelActionDisconnectClient);
           DeinitSocket(thisTunel.serversocket,WSAGetLastError);
           if thisTunel.clientsocket <> -1 then
             DeinitSocket(thisTunel.clientsocket,WSAGetLastError);
           thisTunel.TunelWork := false;
           thisTunel.MustBeDestroyed := true;
         except
         end;
       end;
     except
     end;
    end;


  • Maloj2007 © (12.10.10 17:17) [11]
    Procedure ClientBody(thisTunel:Ttunel);
    var
     PreAccumulator : TCharArray;
     StackAccumulator : TCharArrayEx;
     AccumulatorLen : Cardinal;
     curPacket : TPacket;
     BytesInStack : Longint;
     PreSize, LastResult : Word;
     IP: Integer;
     IPb:array[0..3] of Byte absolute ip;
     recvbytes : int64;
    begin
     try
       with TSocketEngine(thisTunel.curSockEngine) do
       begin
         if not InitSocket(thisTunel.clientsocket,0,'0.0.0.0') then
         begin
           EndThread(0);
         end;
         ip := RedirrectIP;

         if not ConnectToServer(thisTunel.clientsocket, RedirrectPort, RedirrectIP) then
         begin
           SetEvent(thisTunel.ConnectOrErrorEvent); //разрешаем сдвинутся с места в сервербоди
           EndThread(0);
         end;
         thisTunel.SendAction(TTunelActionConnectServer);
         thisTunel.TunelWork := true;
         SetEvent(thisTunel.ConnectOrErrorEvent); //разрешаем сдвинутся с места в сервербоди
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
         AccumulatorLen := 0;
         LastResult := 1;
         While (thisTunel.clientsocket <> -1) do
         begin //Читаем пока не отвалимся
           try
             //Сколько еще в буфере ?!
             ioctlsocket(thisTunel.clientsocket, FIONREAD, BytesInStack);
             if BytesInStack = 0 then
               BytesInStack := 1;
             RecvBytes := recv(thisTunel.clientsocket, PreAccumulator[0], BytesInStack, 0);//Читаем 1 байт или весь буффер сразу
             if RecvBytes <= 0 then
               break
             else
               PreSize := RecvBytes;
             LastResult := PreSize;
             if lastresult = 1 then //Мы ждали данных. поэтому там 1 байт. дочитываем.
             begin
               ioctlsocket(thisTunel.clientsocket, FIONREAD, BytesInStack);
               if BytesInStack > $7FFE then
                 BytesInStack := $7FFE; //В прочитаном буффере - не более чем то что можем скушать за раз.
               if BytesInStack > 0 then //Дочитываем
               begin
                 RecvBytes := recv(thisTunel.clientsocket, PreAccumulator[presize], BytesInStack, 0);
                 if RecvBytes <= 0 then
                   break
                 else
                   LastResult := LastResult + RecvBytes;
               end;
             end;
             if LastResult > 0 then
             begin
               Move(PreAccumulator[0], StackAccumulator[AccumulatorLen], LastResult);
               FillChar(PreAccumulator[0],$7fff,0);
               inc(AccumulatorLen, LastResult);
           except
             AddToLog('2'+IntToStr(GetLastError));
           end;
         end;//While LastResult <> SOCKET_ERROR do
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
         //сюда попадаем когда отвалился сервер
         //уведомляем
         thisTunel.SendAction(TTunelActionDisconnectServer);
         //пишем в лог (добиваем сокет)
         DeinitSocket(thisTunel.clientsocket,WSAGetLastError);
         //закрываем серверный сокет
         if thisTunel.clientsocket <> -1 then
           DeinitSocket(thisTunel.serversocket,WSAGetLastError);
         thisTunel.TunelWork := false;
         //ставим этому обьекту статус камикадзе если надо.
         thisTunel.MustBeDestroyed := true;
       end;
     except
       Addtolog('Error ClientBody'+IntToStr(GetLastError));
     end;
    end;
    {=============================Thread=============================}

  • Сергей М. © (12.10.10 17:40) [12]
    > EndThread

    Этот вызов запросто м.б. источником утечек.
    Пользоваться им нужно с умом, а не абы где.


    > одновременно работающих соединений 100-1000


    И какждое соединение у тебя обслуживается тредом с мегабайтным стеком каждый.

    1 мб * 1000 ~ 1гб

    Не мудрено что в ВАП процесса в какой-то момент времени при создании  очередного треда не нашлось свободного региона размером в 1 мб
  • Maloj2007 © (12.10.10 18:15) [13]

    > Сергей М. ©   (12.10.10 17:40) [12]

    Если я правильно понял тебя, ты предлогаешь уменьшить буфер?

    ЗЫ: Система работает на Win Server x64 16 Гб ОП
  • Rouse_ © (12.10.10 19:41) [14]
    Возьми профайлер и прогони им - он явно покажет где у тебя закавыка...
  • sniknik © (12.10.10 20:51) [15]
    > ЗЫ: Система работает на Win Server x64 16 Гб ОП
    32 разрядному приложению это как бы до лампочки... не зря же 32 разрядная XP "видит" не больше 3 гиг. да и то через одно место, а нормально только 2.
  • Сергей М. © (12.10.10 21:33) [16]

    > Maloj2007 ©   (12.10.10 18:15) [13]
    > предлогаешь уменьшить буфер?


    Ни про какой буфер я не говорил.
    Я лишь заострил твое внимание на том что каждый твой тред требует выделения ему в ВАП твоего 32-разрядного (!) процесса региона размером 1 мб.
    А тредов у тебя, как ты заявил, может одновременно существовать до 1000.
    А ВАП - оно не резиновое.
    + дефрагментация.

    Так что мотай на ус.

    И про EndThread призадумайся.
    Exit() придумана не для Пушкина.
  • Maloj2007 © (13.10.10 00:00) [17]

    > Сергей М. ©   (12.10.10 21:33) [16]

    Спасибо, буду думать =)
  • RGV © (13.10.10 09:38) [18]
    IO completion port ?

    Порт завершения ввода-вывода (IO completion port, IOCP). Реализованный в ядре ОС и доступный через системные вызовы объект «очередь» с операциями «поместить структуру в хвост очереди» и «взять следующую структуру с головы очереди» — последний вызов приостанавливает исполнение потока в случае, если очередь пуста, и до тех пор, пока другой поток не осуществит вызов «поместить». Главнейшей особенностью IOCP является то, что структуры в него могут помещаться не только явным системным вызовом из режима пользователя, но и неявно внутри ядра ОС как результат завершения асинхронной операции ввода-вывода на одной из дескрипторов файлов. Для достижения такого эффекта необходимо использовать системный вызов «связать дескриптор файла с IOCP». В этом случае помещенная в очередь структура содержит в себе код ошибки операции ввода-вывода, а также, для случая успеха этой операции — число реально введенных или выведенных байт. Реализация порта завершения также ограничивает число потоков, исполняющихся на одном процессоре/ядре после получения структуры из очереди. Объект специфичен для MS Windows, и позволяет обработку входящих запросов соединения и порций данных в серверном программном обеспечении в архитектуре, где число потоков может быть меньше числа клиентов (нет требования создавать отдельный поток с расходами ресурсов на него для каждого нового клиента).

    wiki.
 
Конференция "Сети" » Out of memory при работе приложения
Есть новые Нет новых   [134436   +26][b:0][p:0.007]