So, I found some interesting details, which all seem to point to a buggy implementation of TSocket.Accept when targeting Linux.
Instead of
LConnectionSocket := FServerSocket.Accept(500);
if Assigned(LConnectionSocket) then
begin
...
I wrote the following code:
LReadFds := TFDSet.Create(Self);
LWaitResult := FServerSocket.Select(ReadFds, nil, nil, 500);
if LWaitResult := TWaitResult.wrSignaled then
begin
LConnectionSocket := FServerSocket.Accept;
if Assigned(LConnectionSocket) then
begin
Log('Daemon - New connection - ' + LConnectionSocket.RemoteAddress + ':' + IntToStr(LConnectionSocket.RemotePort));
LConnectionThread := TTCpConnectionThread.Create(LConnectionSocket);
...
... and it works, both when compiled for Windows and Linux !
The difference being that TSocket.Accept is now called without a parameter (infinite wait, but returns immediately because a client connection is pending), this pointed to the problematic implementation of TSocket.Accept with a timeout:
if not (TSocketState.Listening in FState) then
raise ESocketError.CreateRes(@sSocketNotListening);
Result := nil;
if Timeout <> INFINITE then
begin
FD_ZERO(FDR);
_FD_SET(FSocket, FDR);
time.tv_sec := Timeout div 1000;
time.tv_usec := (Timeout mod 1000) * 1000;
Res := socketselect(1, @FDR, nil, nil, @time); // << this is where things go wrong when compiled for Linux
CheckSocketResult(Res, 'select');
if Res = 0 then
Exit;
end;
Len := SizeOf(Addr);
ClientHandle := acceptsocket(FSocket, Addr, Len);
CheckSocketResult(ClientHandle, 'accept');
Result := GetClientSocket(ClientHandle, Addr);
The problem is the call to socketselect(1, @FDR, nil, nil, @time). This code works fine in Windows because the first hardcoded parameter (nfds = 1) is ignored. However, in Linux, this parameter must be the set to the highest-numbered file descriptor in any of the three sets + 1. So for Linux, this implementation is telling socketselect() to only look at file descriptor 0, which is almost certainly not what the socket is set to.
This is however correctly implemented in TSocket.Select (I am not sure why TSocket.Accept does not simply call TSocket.Select, rather than re-implementing the same functionality at low level):
ReadFds := GetFds(CheckRead);
WriteFds := GetFds(CheckWrite);
ErrorFds := GetFds(CheckError);
if Microseconds >= 0 then
begin
time.tv_sec := Microseconds div 1000000;
time.tv_usec := Microseconds mod 1000000;
TimePtr := @time;
end else
TimePtr := nil;
Res := socketselect(Max(GetMaxFds(CheckRead), Max(GetMaxFds(CheckWrite), GetMaxFds(CheckError))) + 1, ReadFds, WriteFds, ErrorFds, PTimeVal(TimePtr));
CheckSocketResult(Res, 'select');
if Res = 0 then
Result := TWaitResult.wrTimeout
else
Result := TWaitResult.wrSignaled;
More information about the low level call to select() can be found here: https://stackoverflow.com/questions/2008059/socket-select-works-in-windows-and-times-out-in-linux
I guess the next step is to report this as a bug to Embarcadero...