Subject: * Hide transient errors of the internal iterations of [socket -async] from the script level. * More tests for corner cases.

From: max <[email-masked]>
Date: Tue, 11 Mar 2014 17:34:54 +0000

---
 tests/socket.test  | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 unix/tclUnixSock.c |  75 ++++++++++++-------------
 2 files changed, 189 insertions(+), 46 deletions(-)

diff --git a/tests/socket.test b/tests/socket.test
index a21bb8d..6b072c2 100644
--- a/tests/socket.test
+++ b/tests/socket.test
@@ -1759,7 +1759,7 @@ test socket-14.0.0 {[socket -async] when server only listens on IPv4} \
         unset x
     } -result ok
 test socket-14.0.1 {[socket -async] when server only listens on IPv6} \
-    -constraints [list socket supported_any localhost_v4] \
+    -constraints [list socket supported_any localhost_v6] \
     -setup {
         proc accept {s a p} {
             global x
@@ -1962,13 +1962,13 @@ test socket-14.7.0 {pending [socket -async] and blocking [gets], server is IPv4}
         set port [gets $fd]
     } -body {
         set sock [socket -async localhost $port]
-        gets $sock
+        list [fconfigure $sock -error] [gets $sock] [fconfigure $sock -error]
     } -cleanup {
         # make sure the server exits
         catch {socket 127.0.0.1 $port}
         close $sock
         close $fd
-    } -result {ok}
+    } -result {{} ok {}}
 test socket-14.7.1 {pending [socket -async] and blocking [gets], server is IPv6} \
     -constraints {socket supported_inet supported_inet6} \
     -setup {
@@ -1983,13 +1983,22 @@ test socket-14.7.1 {pending [socket -async] and blocking [gets], server is IPv6}
         set port [gets $fd]
     } -body {
         set sock [socket -async localhost $port]
-        gets $sock
+        list [fconfigure $sock -error] [gets $sock] [fconfigure $sock -error]
     } -cleanup {
         # make sure the server exits
         catch {socket ::1 $port}
         close $sock
         close $fd
-    } -result {ok}
+    } -result {{} ok {}}
+test socket-14.7.2 {pending [socket -async] and blocking [gets], no listener} \
+    -constraints {socket supported_inet supported_inet6} \
+    -body {
+        set sock [socket -async localhost [randport]]
+        catch {gets $sock} x
+        list $x [fconfigure $sock -error]
+    } -cleanup {
+        close $sock
+    } -match glob -result {{error reading "sock*": socket is not connected} {connection refused}}
 test socket-14.8.0 {pending [socket -async] and nonblocking [gets], server is IPv4} \
     -constraints {socket supported_inet supported_inet6} \
     -setup {
@@ -2007,6 +2016,7 @@ test socket-14.8.0 {pending [socket -async] and nonblocking [gets], server is IP
         fconfigure $sock -blocking 0
         for {set i 0} {$i < 50} {incr i } {
             if {[catch {gets $sock} x] || $x ne "" || ![fblocked $sock]} break
+            update
             after 200
         }
         set x
@@ -2033,6 +2043,7 @@ test socket-14.8.1 {pending [socket -async] and nonblocking [gets], server is IP
         fconfigure $sock -blocking 0
         for {set i 0} {$i < 50} {incr i } {
             if {[catch {gets $sock} x] || $x ne "" || ![fblocked $sock]} break
+            update
             after 200
         }
         set x
@@ -2042,6 +2053,145 @@ test socket-14.8.1 {pending [socket -async] and nonblocking [gets], server is IP
         close $sock
         close $fd
     } -result {ok}
+test socket-14.8.2 {pending [socket -async] and nonblocking [gets], no listener} \
+    -constraints {socket supported_inet supported_inet6} \
+    -body {
+        set sock [socket -async localhost [randport]]
+        fconfigure $sock -blocking 0
+        for {set i 0} {$i < 50} {incr i } {
+            if {[catch {gets $sock} x] || $x ne "" || ![fblocked $sock]} break
+            update
+            after 200
+        }
+        fconfigure $sock -error
+    } -cleanup {
+        close $sock
+    } -match glob -result {connection refused}
+test socket-14.9.0 {pending [socket -async] and blocking [puts], server is IPv4} \
+    -constraints {socket supported_inet supported_inet6} \
+    -setup {
+        makeFile {
+            set server [socket -server accept -myaddr 127.0.0.1 0]
+            proc accept {s h p} {set ::x $s}
+            puts [lindex [fconfigure $server -sockname] 2]
+            flush stdout
+            vwait x
+            puts [gets $x]
+        } script
+        set fd [open |[list [interpreter] script] RDWR]
+        set port [gets $fd]
+    } -body {
+        set sock [socket -async localhost $port]
+        puts $sock ok
+        flush $sock
+        list [fconfigure $sock -error] [gets $fd]
+    } -cleanup {
+        # make sure the server exits
+        catch {socket 127.0.0.1 $port}
+        close $sock
+        close $fd
+    } -result {{} ok}
+test socket-14.9.1 {pending [socket -async] and blocking [puts], server is IPv6} \
+    -constraints {socket supported_inet supported_inet6} \
+    -setup {
+        makeFile {
+            set server [socket -server accept -myaddr ::1 0]
+            proc accept {s h p} {set ::x $s}
+            puts [lindex [fconfigure $server -sockname] 2]
+            flush stdout
+            vwait x
+            puts [gets $x]
+        } script
+        set fd [open |[list [interpreter] script] RDWR]
+        set port [gets $fd]
+    } -body {
+        set sock [socket -async localhost $port]
+        puts $sock ok
+        flush $sock
+        list [fconfigure $sock -error] [gets $fd]
+    } -cleanup {
+        # make sure the server exits
+        catch {socket ::1 $port}
+        close $sock
+        close $fd
+    } -result {{} ok}
+test socket-14.10.0 {pending [socket -async] and blocking [puts], server is IPv4} \
+    -constraints {socket supported_inet supported_inet6} \
+    -setup {
+        makeFile {
+            set server [socket -server accept -myaddr 127.0.0.1 0]
+            proc accept {s h p} {set ::x $s}
+            puts [lindex [fconfigure $server -sockname] 2]
+            flush stdout
+            vwait x
+            puts [gets $x]
+        } script
+        set fd [open |[list [interpreter] script] RDWR]
+        set port [gets $fd]
+    } -body {
+        set sock [socket -async localhost $port]
+        fconfigure $sock -blocking 0
+        puts $sock ok
+        flush $sock
+        fileevent $fd readable {set x 1}
+        vwait x
+        list [fconfigure $sock -error] [gets $fd]
+    } -cleanup {
+        # make sure the server exits
+        catch {socket 127.0.0.1 $port}
+        close $sock
+        close $fd
+    } -result {{} ok}
+test socket-14.10.1 {pending [socket -async] and blocking [puts], server is IPv6} \
+    -constraints {socket supported_inet supported_inet6} \
+    -setup {
+        makeFile {
+            set server [socket -server accept -myaddr ::1 0]
+            proc accept {s h p} {set ::x $s}
+            puts [lindex [fconfigure $server -sockname] 2]
+            flush stdout
+            vwait x
+            puts [gets $x]
+        } script
+        set fd [open |[list [interpreter] script] RDWR]
+        set port [gets $fd]
+    } -body {
+        set sock [socket -async localhost $port]
+        fconfigure $sock -blocking 0
+        puts $sock ok
+        flush $sock
+        fileevent $fd readable {set x 1}
+        vwait x
+        list [fconfigure $sock -error] [gets $fd]
+    } -cleanup {
+        # make sure the server exits
+        catch {socket ::1 $port}
+        close $sock
+        close $fd
+    } -result {{} ok}
+test socket-14.11.0 {pending [socket -async] and blocking [puts], no listener, no flush} \
+    -constraints {socket supported_inet supported_inet6} \
+    -body {
+        set sock [socket -async localhost [randport]]
+        fconfigure $sock -blocking 0
+        puts $sock ok
+        fileevent $sock writable {set x 1}
+        vwait x
+        close $sock
+    } -cleanup {
+    } -result {broken pipe} -returnCodes 1
+test socket-14.11.1 {pending [socket -async] and blocking [puts], no listener, flush} \
+    -constraints {socket supported_inet supported_inet6} \
+    -body {
+        set sock [socket -async localhost [randport]]
+        fconfigure $sock -blocking 0
+        puts $sock ok
+        flush $sock
+        fileevent $sock writable {set x 1}
+        vwait x
+        close $sock
+    } -cleanup {
+    } -result {broken pipe} -returnCodes 1
 
 ::tcltest::cleanupTests
 flush stdout
diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c
index 6e84ed5..8336bdb 100644
--- a/unix/tclUnixSock.c
+++ b/unix/tclUnixSock.c
@@ -73,7 +73,7 @@ struct TcpState {
     struct addrinfo *myaddr;	/* Iterator over myaddrlist. */
     int filehandlers;           /* Caches FileHandlers that get set up while
                                  * an async socket is not yet connected. */
-    int status;                 /* Cache status of async socket. */
+    int error;                  /* Cache SO_ERROR of async socket. */
     int cachedBlocking;         /* Cache blocking mode of async socket. */
 };
 
@@ -417,7 +417,7 @@ WaitForConnect(
 	errno = 0;
 	state = TclUnixWaitForFile(statePtr->fds.fd,
 		TCL_WRITABLE | TCL_EXCEPTION, timeOut);
-        if (state != 0) {
+        if (timeOut == -1 && state != 0) {
             CreateClientSocket(NULL, statePtr);
         }
         if (statePtr->flags & TCP_ASYNC_CONNECT) {
@@ -522,6 +522,7 @@ TcpOutputProc(
 	return -1;
     }
     written = send(statePtr->fds.fd, buf, (size_t) toWrite, 0);
+
     if (written > -1) {
 	return written;
     }
@@ -752,24 +753,22 @@ TcpGetOptionProc(
     if ((len > 1) && (optionName[1] == 'e') &&
 	    (strncmp(optionName, "-error", len) == 0)) {
 	socklen_t optlen = sizeof(int);
-	int err, ret;
 
-        if (statePtr->status == 0) {
-            ret = getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR,
-                            (char *) &err, &optlen);
-            if (statePtr->flags & TCP_ASYNC_CONNECT) {
-                statePtr->status = err;
-            }
-            if (ret < 0) {
-                err = errno;
-            }
+        if (statePtr->flags & TCP_ASYNC_CONNECT) {
+            /* Suppress errors as long as we are not done */
+            errno = 0;
+        } else if (statePtr->error != 0) {
+            errno = statePtr->error;
+            statePtr->error = 0;
         } else {
-            err = statePtr->status;
-            statePtr->status = 0;
+            int err;
+            getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR,
+                    (char *) &err, &optlen);
+            errno = err;
+        }
+        if (errno != 0) {
+	    Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(errno), -1);
         }
-	if (err != 0) {
-	    Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(err), -1);
-	}
 	return TCL_OK;
     }
 
@@ -982,7 +981,7 @@ CreateClientSocket(
 {
     socklen_t optlen;
     int async_callback = (state->addr != NULL);
-    int status;
+    int ret = -1, error;
     int async = state->flags & TCP_ASYNC_CONNECT;
 
     if (async_callback) {
@@ -991,8 +990,6 @@ CreateClientSocket(
 
     for (state->addr = state->addrlist; state->addr != NULL;
             state->addr = state->addr->ai_next) {
-	status = -1;
-
         for (state->myaddr = state->myaddrlist; state->myaddr != NULL;
                 state->myaddr = state->myaddr->ai_next) {
             int reuseaddr;
@@ -1014,6 +1011,7 @@ CreateClientSocket(
             if (state->fds.fd >= 0) {
 		close(state->fds.fd);
 		state->fds.fd = -1;
+                errno = 0;
 	    }
 
 	    state->fds.fd = socket(state->addr->ai_family, SOCK_STREAM, 0);
@@ -1035,19 +1033,18 @@ CreateClientSocket(
 	    TclSockMinimumBuffers(INT2PTR(state->fds.fd), SOCKET_BUFSIZE);
     
 	    if (async) {
-		status = TclUnixSetBlockingMode(state->fds.fd,
-                        TCL_MODE_NONBLOCKING);
-		if (status < 0) {
+                ret = TclUnixSetBlockingMode(state->fds.fd,TCL_MODE_NONBLOCKING);
+                if (ret < 0) {
                     continue;
-		}
-	    }
+                }
+            }
 
             reuseaddr = 1;
             (void) setsockopt(state->fds.fd, SOL_SOCKET, SO_REUSEADDR,
                     (char *) &reuseaddr, sizeof(reuseaddr));
-            status = bind(state->fds.fd, state->myaddr->ai_addr,
+            ret = bind(state->fds.fd, state->myaddr->ai_addr,
                     state->myaddr->ai_addrlen);
-            if (status < 0) {
+            if (ret < 0) {
                 continue;
             }
 
@@ -1058,9 +1055,10 @@ CreateClientSocket(
 	     * in being informed when the connect completes.
 	     */
 	    
-	    status = connect(state->fds.fd, state->addr->ai_addr,
-                    state->addr->ai_addrlen);
-	    if (status < 0 && errno == EINPROGRESS) {
+	    ret = connect(state->fds.fd, state->addr->ai_addr,
+                        state->addr->ai_addrlen);
+            error = errno;
+	    if (ret < 0 && errno == EINPROGRESS) {
                 Tcl_CreateFileHandler(state->fds.fd,
                         TCL_WRITABLE|TCL_EXCEPTION, TcpAsyncCallback, state);
                 return TCL_OK;
@@ -1077,23 +1075,18 @@ CreateClientSocket(
 
                 optlen = sizeof(int);
 
-                if (state->status == 0) {
-                    getsockopt(state->fds.fd, SOL_SOCKET, SO_ERROR,
-                            (char *) &status, &optlen);
-                    state->status = status;
-                } else {
-                    status = state->status;
-                    state->status = 0;
-                }
+                getsockopt(state->fds.fd, SOL_SOCKET, SO_ERROR,
+                        (char *) &error, &optlen);
+                errno = error;
             }
-	    if (status == 0) {
+	    if (ret == 0 || errno == 0) {
 		goto out;
 	    }
 	}
     }
 
 out:
-
+    state->error = errno;
     CLEAR_BITS(state->flags, TCP_ASYNC_CONNECT);
     if (async_callback) {
         /*
@@ -1113,7 +1106,7 @@ out:
          */
 
         Tcl_NotifyChannel(state->channel, TCL_WRITABLE);
-    } else if (status != 0) {
+    } else if (ret != 0) {
         /*
          * Failure for either a synchronous connection, or an async one that
          * failed before it could enter background mode, e.g. because an
-- 
cgit v0.12