{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.1    1/15/05 2:23:00 PM  RLebeau
  Comment added to SetScheduler()

  Rev 1.0    12/2/2004 3:26:32 PM  JPMugaas
  Moved most of TIdTCPServer here so we can use TIdTCPServer as an end point
  which requires an OnExecute event.

  Rev 1.68    11/29/04 11:50:26 PM  RLebeau
  Updated ContextDisconected() to call DoDisconnect()

  Rev 1.67    11/27/04 3:28:36 AM  RLebeau
  Updated to automatically set up the client IOHandler before calling
  DoConnect(), and to tear the IOHandler down before calling OnDisconnect().

  Rev 1.66    10/8/2004 10:11:02 PM  BGooijen
  uncommented intercept code

  Rev 1.65    2004.08.13 10:55:38  czhower
  Removed IFDEF

  Rev 1.64    08.08.2004 10:43:10  OMonien
  temporary Thread.priority fix for Kylix

  Rev 1.63    6/11/2004 12:41:52 PM  JPMugaas
  Reuse Address now reenabled.

    Rev 1.62    6/1/2004 1:22:28 PM  DSiders
  Added TODO for TerminateWaitTimeout.

  Rev 1.61    28/04/2004 15:54:40  HHariri
  Changed thread priority for scheduler

  Rev 1.60    2004.04.22 11:44:48 PM  czhower
  Boosted thread priority of listener thread.

  Rev 1.59    2004.03.06 10:40:34 PM  czhower
  Changed IOHandler management to fix bug in server shutdowns.

  Rev 1.58    2004.03.01 5:12:40 PM  czhower
  -Bug fix for shutdown of servers when connections still existed (AV)
  -Implicit HELP support in CMDserver
  -Several command handler bugs
  -Additional command handler functionality.

  Rev 1.57    2004.02.03 4:16:56 PM  czhower
  For unit name changes.

  Rev 1.56    2004.01.20 10:03:36 PM  czhower
  InitComponent

  Rev 1.55    1/3/2004 11:49:30 PM  BGooijen
  the server creates a default binding for IPv6 now too, if IPv6 is supported

  Rev 1.54    2003.12.28 8:04:54 PM  czhower
  Shutdown fix for .net.

  Rev 1.53    2003.11.29 6:03:46 PM  czhower
  Active = True now works when set at design time.

  Rev 1.52    2003.10.21 12:19:02 AM  czhower
  TIdTask support and fiber bug fixes.

  Rev 1.51    2003.10.18 9:33:30 PM  czhower
  Boatload of bug fixes to command handlers.

  Rev 1.50    2003.10.18 8:04:28 PM  czhower
  Fixed bug with setting active at design time.

    Rev 1.49    10/15/2003 11:10:00 PM  DSiders
  Added localization comments.
  Added resource srting for exception raised in TIdTCPServer.SetScheduler.

  Rev 1.48    2003.10.15 4:34:38 PM  czhower
  Bug fix for shutdown.

  Rev 1.47    2003.10.14 11:18:12 PM  czhower
  Fix for AV on shutdown and other bugs

  Rev 1.46    2003.10.11 5:51:38 PM  czhower
  -VCL fixes for servers
  -Chain suport for servers (Super core)
  -Scheduler upgrades
  -Full yarn support

  Rev 1.45    10/5/2003 9:55:26 PM  BGooijen
  TIdTCPServer works on D7 and DotNet now

  Rev 1.44    10/5/2003 03:07:48 AM  JPMugaas
  Should compile.

  Rev 1.43    2003.10.01 9:11:28 PM  czhower
  .Net

  Rev 1.42    2003.09.30 1:23:08 PM  czhower
  Stack split for DotNet

  Rev 1.41    2003.09.19 10:11:22 PM  czhower
  Next stage of fiber support in servers.

  Rev 1.40    2003.09.19 11:54:34 AM  czhower
  -Completed more features necessary for servers
  -Fixed some bugs

  Rev 1.39    2003.09.18 4:43:18 PM  czhower
  -Removed IdBaseThread
  -Threads now have default names

    Rev 1.37    7/6/2003 8:04:10 PM  BGooijen
  Renamed IdScheduler* to IdSchedulerOf*

  Rev 1.36    2003.06.30 9:41:06 PM  czhower
  Fix for AV during server shut down.

    Rev 1.35    6/25/2003 3:57:58 PM  BGooijen
  Disconnecting the context is now inside try...except

    Rev 1.34    6/8/2003 2:13:02 PM  BGooijen
  Made ContextClass public

    Rev 1.33    6/5/2003 12:43:26 PM  BGooijen
  changed short circuit fix code

  Rev 1.32    2003.06.04 10:14:08 AM  czhower
  Removed short circuit dependency and fixed some older irrelevant code.

    Rev 1.31    6/3/2003 11:49:38 PM  BGooijen
  removed AV in TIdTCPServer.DoExecute (hopefully)

  Rev 1.30    5/26/2003 04:29:58 PM  JPMugaas
  Removed GenerateReply and ParseReply.  Those are now obsolete duplicate
  functions in the new design.

  Rev 1.29    2003.05.26 10:35:26 PM  czhower
  Fixed spelling typo.

  Rev 1.28    5/26/2003 12:20:00 PM  JPMugaas

  Rev 1.27    2003.05.26 11:38:22 AM  czhower

  Rev 1.26    5/25/2003 03:38:04 AM  JPMugaas

  Rev 1.25    5/25/2003 03:26:38 AM  JPMugaas

    Rev 1.24    5/20/2003 12:43:52 AM  BGooijen
  changeable reply types

    Rev 1.23    5/13/2003 2:56:40 PM  BGooijen
  changed GetGreating to SendGreeting

    Rev 1.21    4/4/2003 8:09:46 PM  BGooijen
  moved some consts tidcmdtcpserver, changed DoExecute to return
  .connection.connected

    Rev 1.20    3/25/2003 9:04:06 PM  BGooijen
  Scheduler in IOHandler is now updated when the scheduler is removed

    Rev 1.19    3/23/2003 11:33:34 PM  BGooijen
  Updates the scheduler in the iohandler when scheduler/iohandler is changed

    Rev 1.18    3/22/2003 11:44:08 PM  BGooijen
  ServerIntercept now logs connects/disconnects

    Rev 1.17    3/22/2003 1:46:02 PM  BGooijen
  Better handling of exceptions in TIdListenerThread.Run (could cause mem leaks
  first (in non-paged-memory))

    Rev 1.16    3/21/2003 5:55:54 PM  BGooijen
  Added code for serverIntercept

  Rev 1.15    3/21/2003 11:44:00 AM  JPMugaas
  Updated with a OnBeforeConnect event for the TIdMappedPort components.

    Rev 1.14    3/20/2003 12:18:32 PM  BGooijen
  Moved ReplyExceptionCode from TIdTCPServer to TIdCmdTCPServer

    Rev 1.13    3/13/2003 10:18:26 AM  BGooijen
  Server side fibers, bug fixes

  Rev 1.12    2003.02.18 5:52:16 PM  czhower
  Fix for warnings and logic error.

    Rev 1.11    1/23/2003 8:33:16 PM  BGooijen

    Rev 1.10    1/23/2003 11:05:48 AM  BGooijen

    Rev 1.9    1/20/2003 12:50:44 PM  BGooijen
  Added a Contexts propperty, which contains all contexts for that server
  Moved the commandhandlers to TIdCmdTCPServer

  Rev 1.8    1-18-2003 0:00:30  BGooijen
  Removed TIdContext.OnCreate
  Added ContextCreated

  Rev 1.7    1-17-2003 23:44:32  BGooijen
  added support code for TIdContext.OnCreate

  Rev 1.6    1-17-2003 22:22:10  BGooijen
  new design

  Rev 1.5    1-10-2003 23:59:22  BGooijen
  Connection is now freed in destructor of TIdContext

  Rev 1.4    1-10-2003 19:46:22  BGooijen
  The context was not freed, now it is

  Rev 1.3    1-9-2003 11:52:28  BGooijen
  changed construction of TIdContext to Create(AServer: TIdTCPServer)
  added TIdContext property .Server

  Rev 1.2    1-3-2003 19:05:56  BGooijen
  added FContextClass:TIdContextClass to TIdTcpServer
  added Data:TObject to TIdContext

  Rev 1.1    1-1-2003 16:42:10  BGooijen
  Changed TIdThread to TIdYarn
  Added TIdContext

  Rev 1.0    11/13/2002 09:00:42 AM  JPMugaas

2002-01-01 - Andrew P.Rybin
 - bug fix (MaxConnections, SetActive(FALSE)), TerminateListenerThreads, DoExecute

2002-04-17 - Andrew P.Rybin
 - bug fix: if exception raised in OnConnect, Threads.Remove and ThreadMgr.ReleaseThread are not called
}

unit IdCustomTCPServer;

{
  Original Author and Maintainer:
  - Chad Z. Hower a.k.a Kudzu
}

interface

{$I IdCompilerDefines.inc}
//here to flip FPC into Delphi mode

uses
  Classes,
  {$IFDEF HAS_UNIT_Generics_Collections}
  System.Generics.Collections,
  {$ENDIF}
  IdBaseComponent,
  IdComponent,IdContext, IdGlobal, IdException,
  IdIntercept, IdIOHandler, IdIOHandlerStack,
  IdReply, IdScheduler, IdSchedulerOfThread, IdServerIOHandler,
  IdServerIOHandlerStack, IdSocketHandle, IdStackConsts, IdTCPConnection,
  IdThread, IdYarn, SysUtils;

const
  IdListenQueueDefault = 15;

type
  TIdCustomTCPServer = class;

  // This is the thread that listens for incoming connections and spawns
  // new ones to handle each one
  TIdListenerThread = class(TIdThread)
  protected
    FBinding: TIdSocketHandle;
    FServer: TIdCustomTCPServer;
    FOnBeforeRun: TIdNotifyThreadEvent;
    //
    procedure AfterRun; override;
    procedure BeforeRun; override;
    procedure Run; override;
  public
    constructor Create(AServer: TIdCustomTCPServer; ABinding: TIdSocketHandle); reintroduce;
    //
    property Binding: TIdSocketHandle read FBinding;
    property Server: TIdCustomTCPServer read FServer;
    property OnBeforeRun: TIdNotifyThreadEvent read FOnBeforeRun write FOnBeforeRun;
  End;

  {$IFDEF HAS_GENERICS_TThreadList}
  TIdListenerThreadList = TThreadList<TIdListenerThread>;
  TIdListenerList = TList<TIdListenerThread>;
  {$ELSE}
  // TODO: flesh out to match TThreadList<TIdListenerThread> and TList<TIdListenerThread> for non-Generics compilers
  TIdListenerThreadList = TThreadList;
  TIdListenerList = TList;
  {$ENDIF}

  TIdListenExceptionEvent = procedure(AThread: TIdListenerThread; AException: Exception) of object;
  TIdServerThreadExceptionEvent = procedure(AContext: TIdContext; AException: Exception) of object;
  TIdServerThreadEvent = procedure(AContext: TIdContext) of object;

  TIdServerContext = class(TIdContext)
  protected
    FServer: TIdCustomTCPServer;
  public
    property Server: TIdCustomTCPServer read FServer;
  end;

  TIdServerContextClass = class of TIdServerContext;

  TIdCustomTCPServer = class(TIdComponent)
  protected
    FActive: Boolean;
    {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FScheduler: TIdScheduler;
    FBindings: TIdSocketHandles;
    FContextClass: TIdServerContextClass;
    FImplicitScheduler: Boolean;
    FImplicitIOHandler: Boolean;
    {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FIntercept: TIdServerIntercept;
    {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FIOHandler: TIdServerIOHandler;
    FListenerThreads: TIdListenerThreadList;
    FListenQueue: integer;
    FMaxConnections: Integer;
    FReuseSocket: TIdReuseSocket;
    FTerminateWaitTime: Integer;
    FContexts: TIdContextThreadList;
    FOnContextCreated: TIdServerThreadEvent;
    FOnConnect: TIdServerThreadEvent;
    FOnDisconnect: TIdServerThreadEvent;
    FOnException: TIdServerThreadExceptionEvent;
    FOnExecute: TIdServerThreadEvent;
    FOnListenException: TIdListenExceptionEvent;
    FOnBeforeBind: TIdSocketHandleEvent;
    FOnAfterBind: TNotifyEvent;
    FOnBeforeListenerRun: TIdNotifyThreadEvent;
    FUseNagle : Boolean;
    //
    procedure CheckActive;
    procedure CheckOkToBeActive;  virtual;
    procedure ContextCreated(AContext: TIdContext); virtual;
    procedure ContextConnected(AContext: TIdContext); virtual;
    procedure ContextDisconnected(AContext: TIdContext); virtual;
    function CreateConnection: TIdTCPConnection; virtual;
    procedure DoBeforeBind(AHandle: TIdSocketHandle); virtual;
    procedure DoAfterBind; virtual;
    procedure DoBeforeListenerRun(AThread: TIdThread); virtual;
    procedure DoConnect(AContext: TIdContext); virtual;
    procedure DoDisconnect(AContext: TIdContext); virtual;
    procedure DoException(AContext: TIdContext; AException: Exception); virtual;
    function DoExecute(AContext: TIdContext): Boolean; virtual;
    procedure DoListenException(AThread: TIdListenerThread; AException: Exception); virtual;
    procedure DoMaxConnectionsExceeded(AIOHandler: TIdIOHandler); virtual;
    procedure DoTerminateContext(AContext: TIdContext); virtual;
    function GetDefaultPort: TIdPort;
    procedure InitComponent; override;
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    // This is needed for POP3's APOP authentication.  For that,
    // you send a unique challenge to the client dynamically.
    procedure SendGreeting(AContext: TIdContext; AGreeting: TIdReply); virtual;
    procedure SetActive(AValue: Boolean); virtual;
    procedure SetBindings(const AValue: TIdSocketHandles); virtual;
    procedure SetDefaultPort(const AValue: TIdPort); virtual;
    procedure SetIntercept(const AValue: TIdServerIntercept); virtual;
    procedure SetIOHandler(const AValue: TIdServerIOHandler); virtual;
    procedure SetScheduler(const AValue: TIdScheduler); virtual;
    procedure Startup; virtual;
    procedure Shutdown; virtual;
    procedure TerminateAllThreads;
    // Occurs in the context of the peer thread
    property OnExecute: TIdServerThreadEvent read FOnExecute write FOnExecute;

  public
    destructor Destroy; override;
    //
    procedure StartListening;
    procedure StopListening;
    //
    property Contexts: TIdContextThreadList read FContexts;
    property ContextClass: TIdServerContextClass read FContextClass write FContextClass;
    property ImplicitIOHandler: Boolean read FImplicitIOHandler;
    property ImplicitScheduler: Boolean read FImplicitScheduler;
  published
    property Active: Boolean read FActive write SetActive default False;
    property Bindings: TIdSocketHandles read FBindings write SetBindings;
    property DefaultPort: TIdPort read GetDefaultPort write SetDefaultPort;
    property Intercept: TIdServerIntercept read FIntercept write SetIntercept;
    property IOHandler: TIdServerIOHandler read FIOHandler write SetIOHandler;
    property ListenQueue: integer read FListenQueue write FListenQueue default IdListenQueueDefault;
    property MaxConnections: Integer read FMaxConnections write FMaxConnections default 0;
    // right before/after binding sockets
    property OnBeforeBind: TIdSocketHandleEvent read FOnBeforeBind write FOnBeforeBind;
    property OnAfterBind: TNotifyEvent read FOnAfterBind write FOnAfterBind;
    property OnBeforeListenerRun: TIdNotifyThreadEvent read FOnBeforeListenerRun write FOnBeforeListenerRun;
    property OnContextCreated: TIdServerThreadEvent read FOnContextCreated write FOnContextCreated;
    // Occurs in the context of the peer thread
    property OnConnect: TIdServerThreadEvent read FOnConnect write FOnConnect;
    // Occurs in the context of the peer thread
    property OnDisconnect: TIdServerThreadEvent read FOnDisconnect write FOnDisconnect;
    // Occurs in the context of the peer thread
    property OnException: TIdServerThreadExceptionEvent read FOnException write FOnException;
    property OnListenException: TIdListenExceptionEvent read FOnListenException write FOnListenException;
    property ReuseSocket: TIdReuseSocket read FReuseSocket write FReuseSocket default rsOSDependent; // {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use TIdSocketHandle.ReuseSocket'{$ENDIF};{$ENDIF}
//UseNagle should be set to true in most cases.
//See: http://tangentsoft.net/wskfaq/intermediate.html#disable-nagle and
//   http://tangentsoft.net/wskfaq/articles/lame-list.html#item19
//The Nagle algorithm reduces the amount of needless traffic.  Disabling Nagle
//program’s throughput to degrade.
    property UseNagle: boolean read FUseNagle write FUseNagle default true;
    property TerminateWaitTime: Integer read FTerminateWaitTime write FTerminateWaitTime default 5000;
    property Scheduler: TIdScheduler read FScheduler write SetScheduler;
  end;

  EIdTCPServerError = class(EIdException);
  EIdNoExecuteSpecified = class(EIdTCPServerError);
  EIdTerminateThreadTimeout = class(EIdTCPServerError);

implementation

uses
  {$IFDEF VCL_2010_OR_ABOVE}
    {$IFDEF WINDOWS}
  Windows,
    {$ENDIF}
  {$ENDIF}
  IdGlobalCore,
  IdResourceStringsCore, IdReplyRFC,
  IdSchedulerOfThreadDefault, IdStack,
  IdThreadSafe;

{ TIdCustomTCPServer }

procedure TIdCustomTCPServer.CheckActive;
begin
  if Active and (not IsDesignTime) and (not IsLoading) then begin
    raise EIdTCPServerError.Create(RSCannotPerformTaskWhileServerIsActive);
  end;
end;

procedure TIdCustomTCPServer.ContextCreated(AContext: TIdContext);
begin
  if Assigned(FOnContextCreated) then begin
    FOnContextCreated(AContext);
  end;
end;

destructor TIdCustomTCPServer.Destroy;
begin
  Active := False;

  SetIOHandler(nil);

  // Destroy bindings first
  FreeAndNil(FBindings);
  //
  FreeAndNil(FContexts);
  FreeAndNil(FListenerThreads);
  //
  inherited Destroy;
end;

procedure TIdCustomTCPServer.DoBeforeBind(AHandle: TIdSocketHandle);
begin
  if Assigned(FOnBeforeBind) then begin
    FOnBeforeBind(AHandle);
  end;
end;

procedure TIdCustomTCPServer.DoAfterBind;
begin
  if Assigned(FOnAfterBind) then begin
    FOnAfterBind(Self);
  end;
end;

procedure TIdCustomTCPServer.SendGreeting(AContext: TIdContext; AGreeting: TIdReply);
begin
  AContext.Connection.IOHandler.Write(AGreeting.FormattedReply);
end;

procedure TIdCustomTCPServer.ContextConnected(AContext: TIdContext);
var
  // under ARC, convert weak references to strong references before working with them
  LServerIntercept: TIdServerIntercept;
  LConnIntercept: TIdConnectionIntercept;
begin
  LServerIntercept := Intercept;
  if Assigned(LServerIntercept) then begin
    LConnIntercept := LServerIntercept.Accept(AContext.Connection);
    AContext.Connection.IOHandler.Intercept := LConnIntercept;
    if Assigned(LConnIntercept) then begin
      LConnIntercept.Connect(AContext.Connection);
    end;
  end;
  DoConnect(AContext);
end;

procedure TIdCustomTCPServer.ContextDisconnected(AContext: TIdContext);
var
  // under ARC, convert weak references to strong references before working with them
  LIOHandler: TIdIOHandler;
  LIntercept: TIdConnectionIntercept;
begin
  DoDisconnect(AContext);
  LIOHandler := AContext.Connection.IOHandler;
  if Assigned(LIOHandler) then begin
    LIntercept := LIOHandler.Intercept;
    if Assigned(LIntercept) then begin
      LIntercept.Disconnect;
      FreeAndNil(LIntercept);
      LIOHandler.Intercept := nil;
    end;
  end;
end;

function TIdCustomTCPServer.CreateConnection: TIdTCPConnection;
begin
  Result := TIdTCPConnection.Create(nil);
end;

procedure TIdCustomTCPServer.DoConnect(AContext: TIdContext);
begin
  if Assigned(OnConnect) then begin
    OnConnect(AContext);
  end;
end;

procedure TIdCustomTCPServer.DoDisconnect(AContext: TIdContext);
begin
  if Assigned(OnDisconnect) then begin
    OnDisconnect(AContext);
  end;
end;

procedure TIdCustomTCPServer.DoException(AContext: TIdContext; AException: Exception);
begin
  if Assigned(OnException) then begin
    OnException(AContext, AException);
  end;
end;

function TIdCustomTCPServer.DoExecute(AContext: TIdContext): Boolean;
begin
  if Assigned(OnExecute) then begin
    OnExecute(AContext);
  end;
  Result := False;
  if AContext <> nil then begin
    if AContext.Connection <> nil then begin
      Result := AContext.Connection.Connected;
    end;
  end;
end;

procedure TIdCustomTCPServer.DoListenException(AThread: TIdListenerThread; AException: Exception);
begin
  if Assigned(FOnListenException) then begin
    FOnListenException(AThread, AException);
  end;
end;

function TIdCustomTCPServer.GetDefaultPort: TIdPort;
begin
  Result := FBindings.DefaultPort;
end;

procedure TIdCustomTCPServer.Loaded;
begin
  inherited Loaded;
  // Active = True must not be performed before all other props are loaded
  if Active then begin
    FActive := False;
    Active := True;
  end;
end;

// under ARC, all weak references to a freed object get nil'ed automatically
// so this is mostly redundant
procedure TIdCustomTCPServer.Notification(AComponent: TComponent; Operation: TOperation);
begin
  // Remove the reference to the linked components if they are deleted
  if (Operation = opRemove) then begin
    if (AComponent = FScheduler) then begin
      FScheduler := nil;
      FImplicitScheduler := False;
    end
    else if (AComponent = FIntercept) then begin
      FIntercept := nil;
    end
    else if (AComponent = FIOHandler) then begin
      FIOHandler := nil;
      FImplicitIOHandler := False;
    end;
  end;
  inherited Notification(AComponent, Operation);
end;

procedure TIdCustomTCPServer.SetActive(AValue: Boolean);
begin
  // At design time we just set the value and save it for run time.
  // During loading we ignore it till all other properties are set.
  // Loaded will recall it to toggle it
  if IsDesignTime or IsLoading then begin
    FActive := AValue;
  end
  else if FActive <> AValue then begin
    if AValue then begin
      CheckOkToBeActive;
      try
        Startup;
      except
        FActive := True;
        SetActive(False); // allow descendants to clean up
        raise;
      end;
      FActive := True;
    end else begin
      // Must set to False here. Shutdown() implementations call property setters that check this
      FActive := False;
      Shutdown;
    end;
  end;
end;

procedure TIdCustomTCPServer.SetBindings(const AValue: TIdSocketHandles);
begin
  FBindings.Assign(AValue);
end;

procedure TIdCustomTCPServer.SetDefaultPort(const AValue: TIdPort);
begin
  FBindings.DefaultPort := AValue;
end;

procedure TIdCustomTCPServer.SetIntercept(const AValue: TIdServerIntercept);
begin
  {$IFDEF USE_OBJECT_ARC}
  // under ARC, all weak references to a freed object get nil'ed automatically
  FIntercept := AValue;
  {$ELSE}
  if FIntercept <> AValue then
  begin
    // Remove self from the intercept's notification list
    if Assigned(FIntercept) then begin
      FIntercept.RemoveFreeNotification(Self);
    end;
    FIntercept := AValue;
    // Add self to the intercept's notification list
    if Assigned(FIntercept) then begin
      FIntercept.FreeNotification(Self);
    end;
  end;
  {$ENDIF}
end;

procedure TIdCustomTCPServer.SetScheduler(const AValue: TIdScheduler);
var
  // under ARC, convert weak references to strong references before working with them
  LScheduler: TIdScheduler;
  LIOHandler: TIdServerIOHandler;
begin
  LScheduler := FScheduler;

  if LScheduler <> AValue then
  begin
    // RLebeau - is this really needed?  What should happen if this
    // gets called by Notification() if the Scheduler is freed while
    // the server is still Active?
    if Active then begin
      raise EIdException.Create(RSTCPServerSchedulerAlreadyActive);
    end;

    // under ARC, all weak references to a freed object get nil'ed automatically

    // If implicit one already exists free it
    // Free the default Thread manager
    if FImplicitScheduler then begin
      // Under D8 notification gets called after .Free of FreeAndNil, but before
      // its set to nil with a side effect of IDisposable. To counteract this we
      // set it to nil first.
      // -Kudzu
      FScheduler := nil;
      FImplicitScheduler := False;
      IdDisposeAndNil(LScheduler);
    end;

    {$IFNDEF USE_OBJECT_ARC}
    // Ensure we will no longer be notified when the component is freed
    if LScheduler <> nil then begin
      LScheduler.RemoveFreeNotification(Self);
    end;
    {$ENDIF}

    FScheduler := AValue;

    {$IFNDEF USE_OBJECT_ARC}
    // Ensure we will be notified when the component is freed, even is it's on
    // another form
    if AValue <> nil then begin
      AValue.FreeNotification(Self);
    end;
    {$ENDIF}

    LIOHandler := FIOHandler;
    if LIOHandler <> nil then begin
      LIOHandler.SetScheduler(AValue);
    end;
  end;
end;

procedure TIdCustomTCPServer.SetIOHandler(const AValue: TIdServerIOHandler);
var
  // under ARC, convert a weak reference to a strong reference before working with it
  LIOHandler: TIdServerIOHandler;
begin
  LIOHandler := FIOHandler;

  if LIOHandler <> AValue then begin

    // RLebeau - is this needed?  SetScheduler() does it, so should SetIOHandler()
    // also do it? What should happen if this gets called by Notification() if the
    // IOHandler is freed while the server is still Active?
    {
    if Active then begin
      raise EIdException.Create(RSTCPServerIOHandlerAlreadyActive);
    end;
    }

    if FImplicitIOHandler then begin
      FIOHandler := nil;
      FImplicitIOHandler := False;
      IdDisposeAndNil(LIOHandler);
    end;

    {$IFNDEF USE_OBJECT_ARC}
    // Ensure we will no longer be notified when the component is freed
    if Assigned(LIOHandler) then begin
      LIOHandler.RemoveFreeNotification(Self);
    end;
    {$ENDIF}

    FIOHandler := AValue;

    if AValue <> nil then begin
      {$IFNDEF USE_OBJECT_ARC}
      // Ensure we will be notified when the component is freed, even is it's on
      // another form
      AValue.FreeNotification(Self);
      {$ENDIF}
      AValue.SetScheduler(FScheduler);
    end;
  end;
end;

procedure TIdCustomTCPServer.StartListening;
var
  LListenerThreads: TIdListenerList;
  LListenerThread: TIdListenerThread;
  I: Integer;
  LBinding: TIdSocketHandle;
begin
  LListenerThreads := FListenerThreads.LockList;
  try
    // Set up any sockets that are not already listening
    I := LListenerThreads.Count;
    try
      while I < Bindings.Count do begin
        LBinding := Bindings[I];
        LBinding.AllocateSocket;
        // do not overwrite if the default. This allows ReuseSocket to be set per binding
        if FReuseSocket <> rsOSDependent then begin
          LBinding.ReuseSocket := FReuseSocket;
        end;
        DoBeforeBind(LBinding);
        LBinding.Bind;
        LBinding.UseNagle := FUseNagle;
        Inc(I);
      end;
    except
      Dec(I); // the one that failed doesn't need to be closed
      while I >= 0 do begin
        Bindings[I].CloseSocket;
        Dec(I);
      end;
      raise;
    end;

    if I > LListenerThreads.Count then begin
      DoAfterBind;
    end;

    // Set up any threads that are not already running
    for I := LListenerThreads.Count to Bindings.Count - 1 do
    begin
      LBinding := Bindings[I];
      LBinding.Listen(FListenQueue);
      LListenerThread := TIdListenerThread.Create(Self, LBinding);
      try
        LListenerThread.Name := Name + ' Listener #' + IntToStr(I + 1); {do not localize}
        LListenerThread.OnBeforeRun := DoBeforeListenerRun;
        //Todo: Implement proper priority handling for Linux
        //http://www.midnightbeach.com/jon/pubs/2002/BorCon.London/Sidebar.3.html
        LListenerThread.Priority := tpListener;
        LListenerThreads.Add(LListenerThread);
      except
        LBinding.CloseSocket;
        FreeAndNil(LListenerThread);
        raise;
      end;
      LListenerThread.Start;
    end;
  finally
    FListenerThreads.UnlockList;
  end;
end;

//APR-011207: for safe-close Ex: SQL Server ShutDown 1) stop listen 2) wait until all clients go out
procedure TIdCustomTCPServer.StopListening;
var
  LListenerThreads: TIdListenerList;
  LListener: TIdListenerThread;
begin
  LListenerThreads := FListenerThreads.LockList;
  try
    while LListenerThreads.Count > 0 do begin
      LListener := {$IFDEF HAS_GENERICS_TThreadList}LListenerThreads[0]{$ELSE}TIdListenerThread(LListenerThreads[0]){$ENDIF};
      // Stop listening
      LListener.Terminate;
      LListener.Binding.CloseSocket;
      // Tear down Listener thread
      LListener.WaitFor;
      LListener.Free;
      LListenerThreads.Delete(0); // RLebeau 2/17/2006
    end;
  finally
    FListenerThreads.UnlockList;
  end;
end;

{$IFDEF STRING_IS_UNICODE}
//This is an ugly hack that's required because a ShortString does not seem
//to be acceptable to D2009's Assert function.
procedure AssertClassName(const ABool : Boolean; const AString : String); inline;
begin
  Assert(ABool, AString);
end;
{$ENDIF}

procedure TIdCustomTCPServer.TerminateAllThreads;
var
  i: Integer;
  LContext: TIdContext;
  LList: TIdContextList;

  // under ARC, convert a weak reference to a strong reference before working with it
  LScheduler: TIdScheduler;
begin
  // TODO:  reimplement support for TerminateWaitTimeout

  //BGO: find out why TerminateAllThreads is sometimes called multiple times
  //Kudzu: Its because of notifications. It calls shutdown when the Scheduler is
  // set to nil and then again on destroy.
  if Contexts <> nil then begin
    LList := Contexts.LockList;
    try
      for i := 0 to LList.Count - 1 do begin
        LContext := {$IFDEF HAS_GENERICS_TList}LList.Items[i]{$ELSE}TIdContext(LList.Items[i]){$ENDIF};
        Assert(LContext<>nil);
        {$IFDEF STRING_IS_UNICODE}
        AssertClassName(LContext.Connection<>nil, LContext.ClassName);
        {$ELSE}
        Assert(LContext.Connection<>nil, LContext.ClassName);
        {$ENDIF}
        // RLebeau: allow descendants to perform their own cleanups before
        // closing the connection.  FTP, for example, needs to abort an
        // active data transfer on a separate asociated connection
        DoTerminateContext(LContext);
      end;
    finally
      Contexts.UnLockList;
    end;
  end;

  // Scheduler may be nil during destroy which calls TerminateAllThreads
  // This happens with explicit schedulers

  LScheduler := FScheduler;
  if Assigned(LScheduler) then begin
    LScheduler.TerminateAllYarns;
  end;
end;

procedure TIdCustomTCPServer.DoBeforeListenerRun(AThread: TIdThread);
begin
  if Assigned(OnBeforeListenerRun) then begin
    OnBeforeListenerRun(AThread);
  end;
end;

procedure TIdCustomTCPServer.DoMaxConnectionsExceeded(AIOHandler: TIdIOHandler);
begin
//
end;

procedure TIdCustomTCPServer.DoTerminateContext(AContext: TIdContext);
begin
  // Dont call disconnect with true. Otherwise it frees the IOHandler and the thread
  // is still running which often causes AVs and other.
  AContext.Connection.Disconnect(False);
end;

procedure TIdCustomTCPServer.InitComponent;
begin
  inherited InitComponent;
  FBindings := TIdSocketHandles.Create(Self);
  FContexts := TIdContextThreadList.Create;
  FContextClass := TIdServerContext;
  //
  FTerminateWaitTime := 5000;
  FListenQueue := IdListenQueueDefault;
  FListenerThreads := TIdListenerThreadList.Create;
  //TODO: When reestablished, use a sleeping thread instead
//  fSessionTimer := TTimer.Create(self);
  FUseNagle := true; // default
end;

procedure TIdCustomTCPServer.Shutdown;
var
  // under ARC, convert the weak reference to a strong reference before working with it
  LIOHandler: TIdServerIOHandler;
begin
  // tear down listening threads
  StopListening;

  // Tear down ThreadMgr
  try
    TerminateAllThreads;
  finally
    {//bgo TODO: fix this: and Threads.IsCountLessThan(1)}
    // DONE -oAPR: BUG! Threads still live, Mgr dead ;-(
    if ImplicitScheduler then begin
      SetScheduler(nil);
    end;
  end;

  LIOHandler := IOHandler;
  if LIOHandler <> nil then begin
    LIOHandler.Shutdown;
  end;
end;

// Linux/Unix does not allow an IPv4 socket and an IPv6 socket
// to listen on the same port at the same time! Windows does not
// have that problem...
{$IFNDEF IdIPv6}
  {$DEFINE CanCreateTwoBindings}
  {$IFDEF LINUX} // should this be UNIX instead?
    {$UNDEF CanCreateTwoBindings}
  {$ENDIF}
  {$IFDEF ANDROID}
    {$UNDEF CanCreateTwoBindings}
  {$ENDIF}
{$ENDIF}

procedure TIdCustomTCPServer.Startup;
var
  LScheduler: TIdScheduler;
  LIOHandler: TIdServerIOHandler;
begin
  // Set up bindings
  if Bindings.Count = 0 then begin
    // TODO: on systems that support dual-stack sockets, create a single
    // Binding object that supports both IPv4 and IPv6 on the same socket...
    Bindings.Add; // IPv4 or IPv6 by default
    {$IFNDEF IdIPv6}
      {$IFDEF CanCreateTwoBindings}
    if GStack.SupportsIPv6 then begin
      // maybe add a property too, so the developer can switch it on/off
      Bindings.Add.IPVersion := Id_IPv6;
    end;
      {$ENDIF}
    {$ENDIF}
  end;

  // Setup IOHandler
  LIOHandler := FIOHandler;
  if not Assigned(LIOHandler) then begin
    LIOHandler := TIdServerIOHandlerStack.Create(Self);
    SetIOHandler(LIOHandler);
    FImplicitIOHandler := True;
  end;
  LIOHandler.Init;

  // Set up scheduler
  LScheduler := FScheduler;
  if not Assigned(FScheduler) then begin
    LScheduler := TIdSchedulerOfThreadDefault.Create(Self);
    SetScheduler(LScheduler);
    FImplicitScheduler := True;
    // Useful in debugging and for thread names
    LScheduler.Name := Name + 'Scheduler';   {do not localize}
  end;
  LScheduler.Init;

  StartListening;
end;

procedure TIdCustomTCPServer.CheckOkToBeActive;
begin
  //nothing here.  Override in a descendant for an end-point
end;

{ TIdListenerThread }

procedure TIdListenerThread.AfterRun;
begin
  inherited AfterRun;
  // Close just own binding. The rest will be closed from their coresponding
  // threads
  FBinding.CloseSocket;
end;

procedure TIdListenerThread.BeforeRun;
begin
  inherited BeforeRun;
  if Assigned(FOnBeforeRun) then begin
    FOnBeforeRun(Self);
  end;
end;

constructor TIdListenerThread.Create(AServer: TIdCustomTCPServer; ABinding: TIdSocketHandle);
begin
  inherited Create;
  FBinding := ABinding;
  FServer := AServer;
end;

type
  TIdServerContextAccess = class(TIdServerContext)
  end;

procedure TIdListenerThread.Run;
var
  LContext: TIdServerContext;
  LIOHandler: TIdIOHandler;
  LPeer: TIdTCPConnection;
  LYarn: TIdYarn;
begin
  Assert(Server<>nil);
  Assert(Server.IOHandler<>nil);

  LContext := nil;
  LPeer := nil;
  LYarn := nil;
  try
    // GetYarn can raise exceptions
    LYarn := Server.Scheduler.AcquireYarn;

    // TODO: under Windows at least, use SO_CONDITIONAL_ACCEPT to allow
    // the user to reject connections before they are accepted.  Somehow
    // expose an event here for the user to decide with...
    
    LIOHandler := Server.IOHandler.Accept(Binding, Self, LYarn);
    if LIOHandler = nil then begin
      // Listening has finished
      Stop;
      Abort;
    end else begin
      // We have accepted the connection and need to handle it
      LPeer := TIdTCPConnection.Create(nil);
      {$IFDEF USE_OBJECT_ARC}
      // under ARC, the TIdTCPConnection.IOHandler property is a weak reference.
      // TIdServerIOHandler.Accept() returns an IOHandler with no Owner assigned,
      // so lets make the TIdTCPConnection become the Owner in order to keep the
      // IOHandler alive whic this method exits.
      //
      // TODO: should we assign Ownership unconditionally on all platforms?
      //
      LPeer.InsertComponent(LIOHandler);
      {$ENDIF}
      LPeer.IOHandler := LIOHandler;
      LPeer.ManagedIOHandler := True;
    end;

    // LastRcvTimeStamp := Now;  // Added for session timeout support
    // ProcessingTimeout := False;

    // Check MaxConnections
    if (Server.MaxConnections > 0) and (not Server.Contexts.IsCountLessThan(Server.MaxConnections)) then begin
      FServer.DoMaxConnectionsExceeded(LIOHandler);
      LPeer.Disconnect;
      Abort;
    end;

    // Create and init context
    LContext := Server.FContextClass.Create(LPeer, LYarn, Server.Contexts);
    LContext.FServer := Server;
    // We set these instead of having the context call them directly
    // because they are protected methods. Also its good to keep
    // Context indepent of the server as well.
    LContext.OnBeforeRun := Server.ContextConnected;
    LContext.OnRun := Server.DoExecute;
    LContext.OnAfterRun := Server.ContextDisconnected;
    LContext.OnException := Server.DoException;
    //
    Server.ContextCreated(LContext);
    //
    // If all ok, lets start the yarn
    Server.Scheduler.StartYarn(LYarn, LContext);
  except
    on E: Exception do begin
      // RLebeau 1/11/07: TIdContext owns the Peer by default so
      // take away ownership here so the Peer is not freed twice
      if LContext <> nil then begin
        TIdServerContextAccess(LContext).FOwnsConnection := False;
      end;
      FreeAndNil(LContext);
      FreeAndNil(LPeer);
      // Must terminate - likely has not started yet
      if LYarn <> nil then begin
        Server.Scheduler.TerminateYarn(LYarn);
      end;
      // EAbort is used to kick out above and destroy yarns and other, but
      // we dont want to show the user
      if not (E is EAbort) then begin
        Server.DoListenException(Self, E);
      end;
    end;
  end;
end;

end.

