Potential issue in TIdSocketHandle.Readable (Indy 9)

Giganews Newsgroups
Subject: Potential issue in TIdSocketHandle.Readable (Indy 9)
Posted by:  Ron (ron@nospam.simdata.com.au)
Date: Mon, 26 Apr 2004

Hi,

Just wanted to warn about a potential problem when using Indy on low memory
system.  It bit me so I thought I'd share the knowledge.  I have a ftp
client program built using C++ Builder.  On a few systems with low memory,
the program crashes and I eventually discovered that it was due to a stack
overflow in a recursive Indy routine.  I managed to fix it by (1) increasing
the program's minimum stack size and (2) flattening the Indy procedure so
that it doesn't use recursion.  I guess that either would have done, but
because I couldn't reproduce the problem, I used both.

TIdSocketHandle.Readable calls itself n-times where n =  ReadTimeout /
AntiFreeze.IdleTimeout.  If you have a large ReadTimeout and a small
AntiFreeze.IdleTimeout, n can be quite large and take up a fair bit of stack
space.

My code is below in case anyone is interested.

Cheers,

Ron

// RWB
function TIdSocketHandle.ReadableDo(AMSec: Integer = IdTimeoutDefault):
boolean;
var
  ReadList: TList;
begin
  if not FHandleAllocated then begin
    raise EIdConnClosedGracefully.Create(RSConnectionClosedGracefully);
  end;
  ReadList := TList.Create; try
    ReadList.Add(Pointer(Handle));
    Result := GStack.WSSelect(ReadList, nil, nil, AMSec) = 1;
    TIdAntiFreezeBase.DoProcess(result = false);
  finally ReadList.free; end;
end;
function TIdSocketHandle.Readable(AMSec: Integer = IdTimeoutDefault):
boolean;
begin
  if TIdAntiFreezeBase.ShouldUse then begin
    if AMSec = IdTimeoutInfinite then begin
      repeat
        Result := ReadableDo(GAntiFreeze.IdleTimeOut);
        TIdAntiFreezeBase.DoProcess(result = false);
      until Result; // or EIdConnClosedGracefully in ReadableDo
      Exit;
    end else if AMSec > GAntiFreeze.IdleTimeOut then begin
      repeat
        Result := ReadableDo(GAntiFreeze.IdleTimeOut);
        TIdAntiFreezeBase.DoProcess(result = false);
        if Result then Exit;
        AMSec := AMSec - GAntiFreeze.IdleTimeOut;
      until AMSec <= 0;  // or EIdConnClosedGracefully in ReadableDo
      Exit;
    end;
  end;
  Result := ReadableDo(AMSec);
end;

{ RWB: original code
function TIdSocketHandle.Readable(AMSec: Integer = IdTimeoutDefault):
boolean;
var
  ReadList: TList;
begin
  if not FHandleAllocated then begin
    raise EIdConnClosedGracefully.Create(RSConnectionClosedGracefully);
  end;

  if TIdAntiFreezeBase.ShouldUse then begin
    if AMSec = IdTimeoutInfinite then begin
      repeat
        Result := Readable(GAntiFreeze.IdleTimeOut);
      until Result;
      Exit;
    end else if AMSec > GAntiFreeze.IdleTimeOut then begin
      Result := Readable(AMSec - GAntiFreeze.IdleTimeOut);
      if Result then begin
        Exit;
      end else begin
        AMSec := GAntiFreeze.IdleTimeOut;
      end;
    end;
  end;
  ReadList := TList.Create; try
    ReadList.Add(Pointer(Handle));
    Result := GStack.WSSelect(ReadList, nil, nil, AMSec) = 1;
    TIdAntiFreezeBase.DoProcess(result = false);
  finally ReadList.free; end;
end;
}

Replies