comp.lang.ada
 help / color / mirror / Atom feed
* Gnat Sockets - UDP timeout too short.
@ 2016-10-12 14:23 ahlan
  2016-11-04  8:48 ` ahlan.marriott
  0 siblings, 1 reply; 2+ messages in thread
From: ahlan @ 2016-10-12 14:23 UTC (permalink / raw)


Under Microsoft Windows 8.1 (and later) Gnat.Sockets.Receive_Socket returns too early whilst waiting for a UDP datagram.
In our test program (below) we create a UDP socket, set the timeout for one second, bind it to a port and then call receive on the socket.
We catch and resolve the Socket_Error exception and process the expected Connection_Timed_Out.
We note the time before issuing Receive_Socket and the time when we catch the exception and then compare the elapsed time with the receive timeout.
On Window systems prior to Win8.1 this seems to work as expected, the elapsed time is always greater than the receive timeout.
However under Win8.1 (and later) the call to Receive_Socket returns approximately half a second too early!

Curiously, if I write the same thing using the Win32.WinSock API then it works as expected. Which I find odd because I would have thought that Gnat.Sockets would simply be a series of wrappers around a few WinApi calls. But then what do I know?

The effect of this bug is that programs using UDP protocols timeout earlier than they should do - which often leads to curious behaviour.

We have tested this on a couple of PCs running a variety of flavours of Ms-Windows. So far it seems that XP & Win7 work as expected whereas Win8 and Win10 fail.

Has anyone any idea what might cause this problem and how we might go about fixing it?

Best wishes,
MfG
Ahlan

------------------------------
with Ada.Text_IO;
with Ada.Exceptions;
with Ada.Real_Time;
with Ada.Streams;
with GNAT.Sockets;

package body Test is

  package Io  renames Ada.Text_IO;
  package Net renames GNAT.Sockets;

  Receive_Timeout : constant Duration := 1.0;

  Receive_Timeout_Span : constant Ada.Real_Time.Time_Span := Ada.Real_Time.To_Time_Span (Receive_Timeout);

  procedure Work is
    The_Datagram : Ada.Streams.Stream_Element_Array (1..20);
    The_Last     : Ada.Streams.Stream_Element_Offset;
    The_Socket   : Net.Socket_Type;
    Start_Time   : Ada.Real_Time.Time;
    End_Time     : Ada.Real_Time.Time;
    use type Ada.Real_Time.Time;
  begin
    Net.Create_Socket (Socket => The_Socket,
                       Family => Net.Family_Inet,
                       Mode   => Net.Socket_Datagram);
    Net.Set_Socket_Option (Socket => The_Socket,
                           Option => (Net.Receive_Timeout, Timeout => Receive_Timeout));
    Net.Bind_Socket (The_Socket, (Family => Net.Family_Inet,
                                  Addr   => Net.Any_Inet_Addr,
                                  Port   => 11154));
    loop
      begin
        Start_Time := Ada.Real_Time.Clock;
        Net.Receive_Socket (Socket => The_Socket,
                            Item   => The_Datagram,
                            Last   => The_Last);
        Io.New_Line;
        Io.Put_Line ("Unexpected reply!");
        exit;
      exception
      when Occurrence: Net.Socket_Error =>
        End_Time := Ada.Real_Time.Clock;
        declare
          Error : constant Net.Error_Type := Net.Resolve_Exception (Occurrence);
          use type Net.Error_Type;
        begin
          if Error = Net.Connection_Timed_Out then
            if End_Time >= (Start_Time + Receive_Timeout_Span) then
              Io.Put (".");
            else
              Io.New_Line;
              declare
                use type Ada.Real_Time.Time_Span;
                Shortfall : constant Ada.Real_Time.Time_Span := Receive_Timeout_Span - (End_Time - Start_Time);
              begin
                Io.Put_Line ("Timeout too short by" & Ada.Real_Time.To_Duration (Shortfall)'img & "seconds");
              end;
            end if;
          else
            Io.Put_Line ("Socket_Error : Unexpected error=" & Error'img);
            exit;
          end if;
        end;
      when Event : others =>
        Io.Put_Line ("Unexpected exception: " & Ada.Exceptions.Exception_Name (Event));
      end;
    end loop;
  exception
  when Event : others =>
    Io.Put_Line ("Internal Error: " & Ada.Exceptions.Exception_Name (Event));
  end Work;


^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: Gnat Sockets - UDP timeout too short.
  2016-10-12 14:23 Gnat Sockets - UDP timeout too short ahlan
@ 2016-11-04  8:48 ` ahlan.marriott
  0 siblings, 0 replies; 2+ messages in thread
From: ahlan.marriott @ 2016-11-04  8:48 UTC (permalink / raw)


On Wednesday, 12 October 2016 16:23:39 UTC+2, ah...@marriott.org  wrote:
> Under Microsoft Windows 8.1 (and later) Gnat.Sockets.Receive_Socket returns too early whilst waiting for a UDP datagram.
> In our test program (below) we create a UDP socket, set the timeout for one second, bind it to a port and then call receive on the socket.
> We catch and resolve the Socket_Error exception and process the expected Connection_Timed_Out.
> We note the time before issuing Receive_Socket and the time when we catch the exception and then compare the elapsed time with the receive timeout.
> On Window systems prior to Win8.1 this seems to work as expected, the elapsed time is always greater than the receive timeout.
> However under Win8.1 (and later) the call to Receive_Socket returns approximately half a second too early!
> 
> Curiously, if I write the same thing using the Win32.WinSock API then it works as expected. Which I find odd because I would have thought that Gnat.Sockets would simply be a series of wrappers around a few WinApi calls. But then what do I know?
> 
> The effect of this bug is that programs using UDP protocols timeout earlier than they should do - which often leads to curious behaviour.
> 
> We have tested this on a couple of PCs running a variety of flavours of Ms-Windows. So far it seems that XP & Win7 work as expected whereas Win8 and Win10 fail.
> 
> Has anyone any idea what might cause this problem and how we might go about fixing it?
> 
> Best wishes,
> MfG
> Ahlan
> 
> ------------------------------
> with Ada.Text_IO;
> with Ada.Exceptions;
> with Ada.Real_Time;
> with Ada.Streams;
> with GNAT.Sockets;
> 
> package body Test is
> 
>   package Io  renames Ada.Text_IO;
>   package Net renames GNAT.Sockets;
> 
>   Receive_Timeout : constant Duration := 1.0;
> 
>   Receive_Timeout_Span : constant Ada.Real_Time.Time_Span := Ada.Real_Time.To_Time_Span (Receive_Timeout);
> 
>   procedure Work is
>     The_Datagram : Ada.Streams.Stream_Element_Array (1..20);
>     The_Last     : Ada.Streams.Stream_Element_Offset;
>     The_Socket   : Net.Socket_Type;
>     Start_Time   : Ada.Real_Time.Time;
>     End_Time     : Ada.Real_Time.Time;
>     use type Ada.Real_Time.Time;
>   begin
>     Net.Create_Socket (Socket => The_Socket,
>                        Family => Net.Family_Inet,
>                        Mode   => Net.Socket_Datagram);
>     Net.Set_Socket_Option (Socket => The_Socket,
>                            Option => (Net.Receive_Timeout, Timeout => Receive_Timeout));
>     Net.Bind_Socket (The_Socket, (Family => Net.Family_Inet,
>                                   Addr   => Net.Any_Inet_Addr,
>                                   Port   => 11154));
>     loop
>       begin
>         Start_Time := Ada.Real_Time.Clock;
>         Net.Receive_Socket (Socket => The_Socket,
>                             Item   => The_Datagram,
>                             Last   => The_Last);
>         Io.New_Line;
>         Io.Put_Line ("Unexpected reply!");
>         exit;
>       exception
>       when Occurrence: Net.Socket_Error =>
>         End_Time := Ada.Real_Time.Clock;
>         declare
>           Error : constant Net.Error_Type := Net.Resolve_Exception (Occurrence);
>           use type Net.Error_Type;
>         begin
>           if Error = Net.Connection_Timed_Out then
>             if End_Time >= (Start_Time + Receive_Timeout_Span) then
>               Io.Put (".");
>             else
>               Io.New_Line;
>               declare
>                 use type Ada.Real_Time.Time_Span;
>                 Shortfall : constant Ada.Real_Time.Time_Span := Receive_Timeout_Span - (End_Time - Start_Time);
>               begin
>                 Io.Put_Line ("Timeout too short by" & Ada.Real_Time.To_Duration (Shortfall)'img & "seconds");
>               end;
>             end if;
>           else
>             Io.Put_Line ("Socket_Error : Unexpected error=" & Error'img);
>             exit;
>           end if;
>         end;
>       when Event : others =>
>         Io.Put_Line ("Unexpected exception: " & Ada.Exceptions.Exception_Name (Event));
>       end;
>     end loop;
>   exception
>   when Event : others =>
>     Io.Put_Line ("Internal Error: " & Ada.Exceptions.Exception_Name (Event));
>   end Work;
To answer my own question...
Gnat sockets uses the Winsock function Recv and sets the timeout DWORD SO_RCVTIMEO in Milliseconds.
However according to the Microsoft Developers Network in Feb 2014 (and others articles) there was an undocumented minimum limit of 500ms which seems to have been implemented by Microsoft simply adding 500ms to whatever non-zero value was placed in SO_RECVTIMEO.
The consensus workaround was simply to deduct 500ms from the desired timeout.
This is indeed what Gnat.Sockets seems to have implemented at line 2297 in g-socket.adb
if V4 > 500 then V4 := V4 - 500; elsif v4 > 0 then V4 := 1 endif;
At line 1249 in g-socket.adb the 500 is added again if the timeout is retrieved.

It seems to me that recent versions of Windows no longer adds 500ms to the Recv timeout.
This would explain why under Win8 and Win10 our receive of UDP datagrams timeout half a second too soon.

Gnat.Sockets needs to determine the version of windows and only apply the correction if necessary.

The fun of course is going to be finding our which versions of Windows need the correction and which don’t. ;-)
Win8.1 and Win10 don't, Win7 and WinXp do.
Can anyone add to this list?

Best wishes
MfG
Ahlan


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2016-11-04  8:48 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-10-12 14:23 Gnat Sockets - UDP timeout too short ahlan
2016-11-04  8:48 ` ahlan.marriott

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox