Patchwork [Ada] Connect_Socket with timeout does not report failure correctly

login
register
mail settings
Submitter Arnaud Charlet
Date Oct. 3, 2012, 8:04 a.m.
Message ID <20121003080437.GA23077@adacore.com>
Download mbox | patch
Permalink /patch/188724/
State New
Headers show

Comments

Arnaud Charlet - Oct. 3, 2012, 8:04 a.m.
This change fixes an oversight in the Connect_Socket variant that includes
a timeout check. The connection is initiated asynchronously, and a select(2)
call is performed to wait for it to complete, with a timeout. When that call
returns however, the connection is not always succsefully established. It
may also happen that an error occurred during the connection attempt
(typically, connection refused by the remote host), which needs to be reported
through the raising of an exception. This change adds the missing error
check.

The following test case should report:
Client is trying to connect to Port: 11552 
Expected exception raised

with Text_IO;use Text_IO;
with Gnat.Sockets; use Gnat.Sockets;
with Ada.Command_Line;
with Ada.Exceptions; use Ada.Exceptions;

procedure Timed_Conn_Error is
  Port      : constant Port_Type := 11552;
  --  There should be no listening server on this port

  Socket    : Socket_Type := No_Socket;
  Address   : Sock_Addr_Type;
  Host      : constant String := Host_Name;
  Status    : Selector_Status;
begin
  Address.Addr := Addresses (Get_Host_By_Name (Host), 1);
  Address.Port := Port;

  Create_Socket (Socket);
  Set_Socket_Option (Socket, Socket_Level, (Reuse_Address, True));
  Put_Line ("Client is trying to connect to Port:" & Port'Img & " ");
  Connect_Socket (Socket => Socket, Server => Address, Timeout => Forever, Status => Status);

  begin
    String'Output (Stream (Socket), "Hello");
  exception
    when E : others =>
      Put_Line ("Unexpected exception, should not reach this point");
      Put_Line ("Info: " & Exception_Information (E));
  end;

exception when E : others =>
   Put_Line ("Expected exception raised");
   Put_Line ("Info: " & Exception_Information (E));
end Timed_Conn_Error;

Tested on x86_64-pc-linux-gnu, committed on trunk

2012-10-03  Thomas Quinot  <quinot@adacore.com>

	* g-socket.adb (Connect_Socket, version with timeout): When the
	newly-connected socket is reported as available for writing, check
	whether it has a pending asynchronous error prior to returning.

Patch

Index: g-socket.adb
===================================================================
--- g-socket.adb	(revision 192025)
+++ g-socket.adb	(working copy)
@@ -123,7 +123,7 @@ 
    function Resolve_Error
      (Error_Value : Integer;
       From_Errno  : Boolean := True) return Error_Type;
-   --  Associate an enumeration value (error_type) to en error value (errno).
+   --  Associate an enumeration value (error_type) to an error value (errno).
    --  From_Errno prevents from mixing h_errno with errno.
 
    function To_Name   (N  : String) return Name_Type;
@@ -702,6 +702,13 @@ 
       Req : Request_Type;
       --  Used to set Socket to non-blocking I/O
 
+      Conn_Err : aliased Integer;
+      --  Error status of the socket after completion of select(2)
+
+      Res           : C.int;
+      Conn_Err_Size : aliased C.int := Conn_Err'Size / 8;
+      --  For getsockopt(2) call
+
    begin
       if Selector /= null and then not Is_Open (Selector.all) then
          raise Program_Error with "closed selector";
@@ -735,10 +742,32 @@ 
          Selector => Selector,
          Status   => Status);
 
+      --  Check error condition (the asynchronous connect may have terminated
+      --  with an error, e.g. ECONNREFUSED) if select(2) completed.
+
+      if Status = Completed then
+         Res := C_Getsockopt
+           (C.int (Socket), SOSC.SOL_SOCKET, SOSC.SO_ERROR,
+            Conn_Err'Address, Conn_Err_Size'Access);
+
+         if Res /= 0 then
+            Conn_Err := Socket_Errno;
+         end if;
+
+      else
+         Conn_Err := 0;
+      end if;
+
       --  Reset the socket to blocking I/O
 
       Req := (Name => Non_Blocking_IO, Enabled => False);
       Control_Socket (Socket, Request => Req);
+
+      --  Report error condition if any
+
+      if Conn_Err /= 0 then
+         Raise_Socket_Error (Conn_Err);
+      end if;
    end Connect_Socket;
 
    --------------------