Jump to content
Dmitry Onoshko

System.Net.Socket: TSocket.BeginReceive timeout

Recommended Posts

I’ve finally decided to give a try to the relatively new System.Net.Socket unit and its TSocket class. After successful connection of a client TCP socket (also monitored with Wireshark) and performing EndConnect I immediately do

Socket.BeginReceive(MyCallback);

expecting to get called back when some data arrives. But if the server doesn't transmit anything, ESocketError 'Timeout receiving data' gets raised a lot of times by this piece of code:

procedure TSocket.WaitToReceiveData;
begin
  if WaitForData(ReceiveTimeout) = TWaitResult.wrTimeout then
    raise ESocketError.CreateRes(@sSocketReceiveTimeout);
end;

which is called from here:

function TSocket.BeginReceive(const AsyncCallback: TAsyncCallback; Count: Integer; Flags: TSocketFlags): IAsyncResult;
begin
  Result := TSocketReceiveAsyncResult.Create(Self,
    function: TBytes
    begin
      if Count < 0 then WaitToReceiveData;
      Receive(Result, Count, Flags);
    end, AsyncCallback).Invoke;
end;

The timeout applied is 0, so the asynchronous Receive basically turns into not-so-tight polling spin loop. It obviously is quite inconvenient when debugging the program (tons of useless ESocketErrors), but what really worries me is the waste of processor time that happens.

 

Is the TSocket class really usable these days? Is there some good way to get notified about data arrival without blocking, using arbitrarily chosen timeouts and busy waiting? (It’s Delphi 11 CE.)

 

And, if Indy, ICS and other third-party libraries are not allowed, does this leave me with ScktComp and hand-written WinSock-based implementation?

Share this post


Link to post
3 hours ago, Dmitry Onoshko said:

Is the TSocket class really usable these days?

Not to sound gloomy, but I have yet to see Borland/CodeGear/Embarcadero produce a good solid cross-platform socket library.  There is always problems in their implementations.  Back in the day, CLX's sockets were notoriously buggy.  The Windows-only ScktComp unit is pretty solid (if you ignore its bad handling of Unicode text and TStream - the rest of it is good, though).

3 hours ago, Dmitry Onoshko said:

Is there some good way to get notified about data arrival without blocking, using arbitrarily chosen timeouts and busy waiting? (It’s Delphi 11 CE.)

The traditional approach is to use either 1) synchronous socket I/O, 2) non-blocking socket I/O with select() or (e)poll(), or 3) OS-specific notifications.

 

WinSock on Windows provides notifications via window messages (which ScktComp handles).  Most other platforms don't offer notifications, so that leaves blocking or polling.

 

In this case, TSocket.BeginReceive() is using a combination of #1 and #2 - it uses a TTask thread to perform a blocking read, but it also uses select() to wait for new bytes to arrive before then reading them.

 

The problem is that ReceiveTimeout is 0 by default, and comments in the implementation say both 0 and -1 are supposed to be infinite, but 0 is not actually treated as infinite. It is used for polling, but polling timeouts are being treated as failures like any other.

4 hours ago, Dmitry Onoshko said:

The timeout applied is 0, so the asynchronous Receive basically turns into not-so-tight polling spin loop.

If any timeout error occurs then the read operator is over, it doesn't keep polling.  Unless you are handling the failure and calling BeginReceive() again?

4 hours ago, Dmitry Onoshko said:

It obviously is quite inconvenient when debugging the program (tons of useless ESocketErrors)

You could just tell the debugger to ignore the errors.

3 hours ago, Dmitry Onoshko said:

And, if Indy, ICS and other third-party libraries are not allowed...

Why would they not be allowed, in what?

3 hours ago, Dmitry Onoshko said:

... does this leave me with ScktComp and hand-written WinSock-based implementation?

Possibly.  I've never had much trouble with ScktComp (and things it does have problems with are easily worked around).

Share this post


Link to post
Posted (edited)
30 minutes ago, Remy Lebeau said:

If any timeout error occurs then the read operator is over, it doesn't keep polling.  Unless you are handling the failure and calling BeginReceive() again?

I’m pretty sure I pushed “Continue” in the debugger exception message and almost immediately stopped at a breakpoint I’ve put somewhere before the WaitForData call. I was also quite surprised that it got like out of nowhere. I guess, I could try to reproduce it in a separate test project (in my code BeginReceive was only called in connect callback and after successful receive callback, the latter obviously didn’t have a chance to run). Just maybe somewhat later, in case it’s worth doing that. Or maybe it really was my mistake, I couldn’t find the source of multiple attempts in a reasonable amount of time.

30 minutes ago, Remy Lebeau said:

You could just tell the debugger to ignore the errors.

And later, when the errors are for a valid reason, that would be a pain to debug either with ESocketError ignored by the debugger or with tons of irrelevant ESocketErrors. The former is no better than the latter.

30 minutes ago, Remy Lebeau said:

Why would they not be allowed, in what?

Third-party dependencies have their cons long-term-wise. In fact, I’ve already written a set of base classes to wrap socket libraries without interfering the rest of the program. So, moving from one to another might be a bit easier. Still, any library not shipped with Delphi CE for free is not welcome by default. And Indy uses blocking model which makes me worry about serving like a few thousands of clients (expected amount of persistent connections).

 

I’m actually afraid of ScktComp.TServerSocket as well, but it at least looks promising in spite of not being available by default in modern versions of Delphi.

Edited by Dmitry Onoshko

Share this post


Link to post
6 hours ago, Dmitry Onoshko said:

And later, when the errors are for a valid reason, that would be a pain to debug either with ESocketError ignored by the debugger or with tons of irrelevant ESocketErrors. The former is no better than the latter.

If you don't want to ignore all socket exceptions, you can use breakpoints to tell the debugger to ignore the exceptions only for particular sections of code. And even use breakpoints to toggle when other breakpoints are enabled/disabled. Breakpoints have quite a few useful features beyond just stopping execution when they are hit.

Quote

I’m actually afraid of ScktComp.TServerSocket as well, but it at least looks promising in spite of not being available by default in modern versions of Delphi.

It is still available, it is just not installed by default. See https://docwiki.embarcadero.com/RADStudio/en/Installing_Socket_Components

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×