TCP Server는 Server에 연결되어 있는 다수의 클라이언트들에게 메세지를 보내야 하는 경우가 있습니다. 많은 분들이 아시겠지만, 이 경우 TCP Server에 연결되어 있는 socket으로 send를 loop을 통해서 전송을 할 때 문제가 발생할 수가 있습니다. 연결되어 있는 TCP Session 중에 한놈이라도 네트워크 문제가 발생하여 송수신이 제대로 되지 않는 경우 "send" API 자체가 block이 되어 서버측에서 loop의 수행이 재빨리 되지 않는 문제점이 발생하게 됩니다.


이를 위해서 TCP Server단에서 클라이언트들에게 보내어야 할 메세지를 각각 별도의 Send Thread를 이용해서 처리를 하게 됩니다. 즉 서버가 클라이언트에게 보내어야 할 메세지가 있을 때 바로 "send"를 호출하지 않고 Send 를 담당하는 스레드에게 메세지를 보내 주고 그 스레드에서 실제 전송을 하는 구조입니다.






그런데 관련된 소스를 보던 도중에 Context라는 놈을 사용하게 되는데, 여기에서 흥미로운 버그가 발견되었습니다. Send Thread에서는 Context라는 놈을 이용해서 전송을 하게 되는데, 과연 이 Context라는 놈이 언제 해제가 될까 하는 것입니다. 이를 위해서 허접 디버깅을 해 보았습니다. IdContext.pas라는 파일을 테스트 프로젝트에 추가를 시키고 생성자(TIdContext.Create)와 해제자(TIdContext.Destroy)에 break point를 걸고 테스트를 해 보았습니다.


클라이언트측에서 TCP 연결이 될 때 break point가 걸리는데 이는 IdCustomTCPServer.pas 파일의 다음 코드(919 line)에서 생성이 됩니다.


procedure TIdListenerThread.Run;

begin

  ...

  LIOHandler := Server.IOHandler.Accept(Binding, Self, LYarn);

  ...

  LContext := Server.FContextClass.Create(LPeer, LYarn, Server.Contexts);

  ...

end;


그런데 디버깅을 해 보면 TIdContext.Destory의 break point가 걸렸을 때의 Stack을 보면 Indy에서 호출해 주는 코드가 없습니다. 즉 TIdTCPServer에서는 클라이언트로부터 연결이 되었을 때 TIdContext 객체를 생성하는 코드만 Indy에서 존재를 하지 이 객체를 해제하는 코드는 없다는 것입니다. 그럼 과연 이 객체(TIdContext)는 언제 해제가 될까?






정답은 Classes.pas 파일에 있습니다. 다음 코드에서 해당 객체가 해제가 됩니다.


function ThreadProc(Thread: TThread): Integer;

var

  FreeThread: Boolean;

begin

{$IFDEF LINUX}

  if Thread.FSuspended then sem_wait(Thread.FCreateSuspendedSem);

{$ENDIF}

  try

    if not Thread.Terminated then

    try

      Thread.Execute;

    except

      Thread.FFatalException := AcquireExceptionObject;

    end;

  finally

    FreeThread := Thread.FFreeOnTerminate;

    Result := Thread.FReturnValue;

    Thread.DoTerminate;

    Thread.FFinished := True;

    SignalSyncEvent;

    if FreeThread then Thread.Free;

{$IFDEF MSWINDOWS}

    EndThread(Result);

{$ENDIF}

{$IFDEF LINUX}

    // Directly call pthread_exit since EndThread will detach the thread causing

    // the pthread_join in TThread.WaitFor to fail.  Also, make sure the EndThreadProc

    // is called just like EndThread would do. EndThreadProc should not return

    // and call pthread_exit itself.

    if Assigned(EndThreadProc) then

      EndThreadProc(Result);

    pthread_exit(Pointer(Result));

{$ENDIF}

  end;

end;


즉 Indy는 클라이언트로부터 TCP 연결이 될 때 TCPServer는 관련 스레드를 생성을 하고 그 스레드 내부에서 Context를 생성을 한 이후에 Execute 이벤트를 발생시킵니다. 그리고 TCP 연결이 끊겼을 때 해당 스레드가 종료가 되고 상기 ThreadProc의 Execute 이벤트 발생 이후에 해제가 됩니다.






중요한 것은 Indy에서 사용하는 Context 객체는 해당 스레드(Execute) 코드의 수행이 종료되고 나게 되면 상기 코드(ThreadProc)에 진입을 하여 대번에 해제가 됩니다. 그런데(Context가 해제되었음에도 불구하고) 프로그램 코드 내부에서 이 Context를 사용하려고 하면 Access Violation Error가 날 수 있습니다.


procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);

begin

  // AContext 객체는 본 스레드 코드의 실행이 완료되고 나면 Classes.pas의 ThreadProc에 의해서 대번에 해제가 된다.

  // 즉 Context 객체는 반드시 본 스레드 코드(Execute) 종료 시점 이후에는 사용되어 지지 않도록 주의를 한다.

end;






이거 찾아 내느라 한참을 고생을 했네요. Indy로 TCP Server 프로그래밍을 하실 때 주의하시기 바랍니다.