Tcl Source Code

Check-in [b56716f2ac]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:
  • unix/tclUnixSock.c (CreateClientSocket): Fix and simplify posting of the writable fileevent at the end of an asynchronous connection attempt. Improve comments for some of the trickery around [socket -async]. [Bug 3325339]
  • tests/socket.test: Adjust tests to the async code changes. Add more tests for corner cases of async sockets.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: b56716f2ac6b6dc0e769d25513f73e66020d40c1
User & Date: max 2011-06-28 11:32:15
Context
2011-06-28
14:42
replace socket-14.3 with a test that is more useful and less likely to randomly fail depending on th... check-in: 2eb58b2d67 user: max tags: trunk
11:32
  • unix/tclUnixSock.c (CreateClientSocket): Fix and simplify posting of the writable fileevent at ...
check-in: b56716f2ac user: max tags: trunk
2011-06-24
17:50
merge mark check-in: a5f1869eae user: dgp tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ChangeLog.











1
2
3
4
5
6
7










2011-06-22  Andreas Kupries  <[email protected]>

	* library/platform/pkgIndex.tcl: Updated to platform 1.0.10. Added
	* library/platform/platform.tcl: handling of the DEB_HOST_MULTIARCH
	* unix/Makefile.in: location change for libc.
	* win/Makefile.in:

>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2011-06-28  Reinhard Max  <[email protected]>

	* unix/tclUnixSock.c (CreateClientSocket): Fix and simplify
	posting of the writable fileevent at the end of an asynchronous
	connection attempt. Improve comments for some of the trickery
	around [socket -async]. [Bug 3325339]
	
	* tests/socket.test: Adjust tests to the async code changes. Add
	more tests for corner cases of async sockets.

2011-06-22  Andreas Kupries  <[email protected]>

	* library/platform/pkgIndex.tcl: Updated to platform 1.0.10. Added
	* library/platform/platform.tcl: handling of the DEB_HOST_MULTIARCH
	* unix/Makefile.in: location change for libc.
	* win/Makefile.in:

Changes to tests/socket.test.

1747
1748
1749
1750
1751
1752
1753

1754
1755
1756
1757
1758
1759
1760
1761
1762


1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784










1785









































1786
1787
1788
1789
1790
1791
1792
1793
            global x
            puts $s bye
            close $s
	    lappend x ok
        }
        set server [socket -server accept -myaddr 127.0.0.1 0]
        set port [lindex [fconfigure $server -sockname] 2]

    } -body {
        set client [socket -async localhost $port]
        fileevent $client writable {
            lappend x [expr {[fconfigure $client -error] eq ""}]
        }
        set after [after 1000 {set x timeout}]
        vwait x
        vwait x
        set x


    } -cleanup {
        after cancel $after
        close $server
        close $client
        unset x
    } -result {ok 1}
test socket-14.2 {[socket -async] fileevent connection refused} \
    -constraints [list socket supported_any] \
    -body {
        set client [socket -async localhost [randport]]
        fileevent $client writable {set x [fconfigure $client -error]}
        set after [after 1000 {set x timeout}]
        vwait x
        if {$x eq "timeout"} {
            append x ": [fconfigure $client -error]"
        }
        set x
    } -cleanup {
        after cancel $after
        close $client
        unset x
    } -result "connection refused"




















































::tcltest::cleanupTests
flush stdout
return

# Local Variables:
# mode: tcl
# fill-column: 78
# End:







>



|

|
|
|
<
>
>





|
















>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762

1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
            global x
            puts $s bye
            close $s
	    lappend x ok
        }
        set server [socket -server accept -myaddr 127.0.0.1 0]
        set port [lindex [fconfigure $server -sockname] 2]
        set x ""
    } -body {
        set client [socket -async localhost $port]
        fileevent $client writable {
            lappend x [fconfigure $client -error]
        }
        set after [after 1000 {lappend x timeout}]
        while {[llength $x] < 2 && "timeout" ni $x} {
            vwait x

        }
        lsort $x; # we only want to see both events, the order doesn't matter
    } -cleanup {
        after cancel $after
        close $server
        close $client
        unset x
    } -result {{} ok}
test socket-14.2 {[socket -async] fileevent connection refused} \
    -constraints [list socket supported_any] \
    -body {
        set client [socket -async localhost [randport]]
        fileevent $client writable {set x [fconfigure $client -error]}
        set after [after 1000 {set x timeout}]
        vwait x
        if {$x eq "timeout"} {
            append x ": [fconfigure $client -error]"
        }
        set x
    } -cleanup {
        after cancel $after
        close $client
        unset x
    } -result "connection refused"
test socket-14.3 {[socket -async] fileevent host unreachable} \
    -constraints [list socket supported_any] \
    -body {
        # address from rfc5737
        set client [socket -async 192.0.2.42 [randport]]
        fileevent $client writable {set x [fconfigure $client -error]}
        set after [after 5000 {set x timeout}]
        vwait x
        if {$x eq "timeout"} {
            append x ": [fconfigure $client -error]"
        }
        set x
    } -cleanup {
        after cancel $after
        close $client
        unset x
    } -result "host is unreachable"
test socket-14.4 {[socket -async] and both, readdable and writable fileevents} \
    -constraints [list socket supported_any] \
    -setup {
        proc accept {s a p} {
            puts $s bye
            close $s
        }
        set server [socket -server accept -myaddr 127.0.0.1 0]
        set port [lindex [fconfigure $server -sockname] 2]
        set x ""
    } -body {
        set client [socket -async localhost $port]
        fileevent $client writable {
            lappend x [fconfigure $client -error]
            fileevent $client writable {}
        }
        fileevent $client readable {lappend x [gets $client]}
        set after [after 1000 {lappend x timeout}]
        while {[llength $x] < 2 && "timeout" ni $x} {
            vwait x
        }
        lsort $x
    } -cleanup {
        after cancel $after
        close $client
        close $server
    } -result {{} bye}
test socket-14.5 {[socket -async] which fails before any connect() can be made} \
    -constraints [list socket supported_any] \
    -body {
        # addresses from rfc5737
        socket -async -myaddr 192.0.2.42 198.51.100.42 [randport]
    } \
    -returnCodes 1 \
    -result {couldn't open socket: cannot assign requested address}
::tcltest::cleanupTests
flush stdout
return

# Local Variables:
# mode: tcl
# fill-column: 78
# End:

Changes to unix/tclUnixSock.c.

854
855
856
857
858
859
860











861
862
863
864
865
866
867
{
    TcpState *statePtr = (TcpState *) instanceData;

    *handlePtr = INT2PTR(statePtr->fds.fd);
    return TCL_OK;
}












static void
TcpAsyncCallback(
    ClientData clientData,	/* The socket state. */
    int mask)			/* Events of interest; an OR-ed combination of
				 * TCL_READABLE, TCL_WRITABLE and
				 * TCL_EXCEPTION. */
{







>
>
>
>
>
>
>
>
>
>
>







854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
{
    TcpState *statePtr = (TcpState *) instanceData;

    *handlePtr = INT2PTR(statePtr->fds.fd);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TcpAsyncCallback --
 *
 *	Called by the event handler that CreateClientSocket sets up
 *	internally for [socket -async] to get notified when the
 *	asyncronous connection attempt has succeeded or failed.
 *
 *----------------------------------------------------------------------
 */
static void
TcpAsyncCallback(
    ClientData clientData,	/* The socket state. */
    int mask)			/* Events of interest; an OR-ed combination of
				 * TCL_READABLE, TCL_WRITABLE and
				 * TCL_EXCEPTION. */
{
879
880
881
882
883
884
885












886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
 *      TCL_OK, if the socket was successfully connected or an asynchronous
 *      connection is in progress. If an error occurs, TCL_ERROR is returned
 *      and an error message is left in interp.
 *
 * Side effects:
 *	Opens a socket.
 *












 *----------------------------------------------------------------------
 */

static int
CreateClientSocket(
    Tcl_Interp *interp,		/* For error reporting; can be NULL. */
    TcpState *state)
{
    socklen_t optlen;
    int in_coro = (state->addr != NULL);
    int status;
    int async = state->flags & TCP_ASYNC_CONNECT;

    if (in_coro) {
        goto coro_continue;
    }
    
    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) {







>
>
>
>
>
>
>
>
>
>
>
>









|



|
|

|







890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
 *      TCL_OK, if the socket was successfully connected or an asynchronous
 *      connection is in progress. If an error occurs, TCL_ERROR is returned
 *      and an error message is left in interp.
 *
 * Side effects:
 *	Opens a socket.
 *
 * Remarks:
 *	A single host name may resolve to more than one IP address, e.g. for
 *	an IPv4/IPv6 dual stack host. For handling asyncronously connecting
 *	sockets in the background for such hosts, this function can act as a
 *	coroutine. On the first call, it sets up the control variables for the
 *	two nested loops over the local and remote addresses. Once the first
 *	connection attempt is in progress, it sets up itself as a writable
 *	event handler for that socket, and returns. When the callback occurs,
 *	control is transferred to the "reenter" label, right after the initial
 *	return and the loops resume as if they had never been interrupted.
 *	For syncronously connecting sockets, the loops work the usual way.
 *
 *----------------------------------------------------------------------
 */

static int
CreateClientSocket(
    Tcl_Interp *interp,		/* For error reporting; can be NULL. */
    TcpState *state)
{
    socklen_t optlen;
    int async_callback = (state->addr != NULL);
    int status;
    int async = state->flags & TCP_ASYNC_CONNECT;

    if (async_callback) {
        goto reenter;
    }

    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) {
972
973
974
975
976
977
978
979
980
981
982
983

984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999



1000
1001
1002
1003
1004


1005
1006




1007

1008





1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
                             state->addr->ai_addrlen);
	    if (status < 0 && errno == EINPROGRESS) {
                Tcl_CreateFileHandler(state->fds.fd,
                                      TCL_WRITABLE | TCL_EXCEPTION,
                                      TcpAsyncCallback, state);
                return TCL_OK;

            coro_continue:
                Tcl_DeleteFileHandler(state->fds.fd);
                /*
                 * Read the error state from the socket, to see if the async
                 * connection has succeeded or failed and store the status in

                 * the socket state for later retrieval by [fconfigure -error]
                 */
                optlen = sizeof(int);
                getsockopt(state->fds.fd, SOL_SOCKET, SO_ERROR,
                           (char *)&status, &optlen);
                state->status = status;
            }
	    if (status == 0) {
                goto out;
	    }
	}
    }

out:

    if (async) {



        CLEAR_BITS(state->flags, TCP_ASYNC_CONNECT);
        TcpWatchProc(state, state->filehandlers);
        TclUnixSetBlockingMode(state->fds.fd, TCL_MODE_BLOCKING);
    }



    if (status < 0) {
        if (in_coro) {




            Tcl_NotifyChannel(state->channel, TCL_WRITABLE);

        } else {





            if (interp != NULL) {
                Tcl_AppendResult(interp, "couldn't open socket: ",
                                 Tcl_PosixError(interp), NULL);
            }
            return TCL_ERROR;
        }
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *







|


|
|
>
|














|
>
>
>



|
|
>
>
|
<
>
>
>
>
|
>
|
>
>
>
>
>
|
|
|
|
|
<







995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034

1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051

1052
1053
1054
1055
1056
1057
1058
                             state->addr->ai_addrlen);
	    if (status < 0 && errno == EINPROGRESS) {
                Tcl_CreateFileHandler(state->fds.fd,
                                      TCL_WRITABLE | TCL_EXCEPTION,
                                      TcpAsyncCallback, state);
                return TCL_OK;

            reenter:
                Tcl_DeleteFileHandler(state->fds.fd);
                /*
                 * Read the error state from the socket to see if the async
                 * connection has succeeded or failed. As this clears the
                 * error condition, we cache the status in the socket state
                 * struct for later retrieval by [fconfigure -error].
                 */
                optlen = sizeof(int);
                getsockopt(state->fds.fd, SOL_SOCKET, SO_ERROR,
                           (char *)&status, &optlen);
                state->status = status;
            }
	    if (status == 0) {
                goto out;
	    }
	}
    }

out:

    if (async_callback) {
        /*
         * An asynchonous connection has finally succeeded or failed.
         */
        CLEAR_BITS(state->flags, TCP_ASYNC_CONNECT);
        TcpWatchProc(state, state->filehandlers);
        TclUnixSetBlockingMode(state->fds.fd, TCL_MODE_BLOCKING);

        /*
         * We need to forward the writable event that brought us here, bcasue
         * upon reading of getsockopt(SO_ERROR), at least some OSes clear the
         * writable state from the socket, and so a subsequent select() on

         * behalf of a script level [fileevent] would not fire. It doesn't
         * hurt that this is also called in the successful case and will save
         * the event mechanism one roundtrip through select().
         */
        Tcl_NotifyChannel(state->channel, TCL_WRITABLE);

    } else if (status != 0) {
        /*
         * Failure for either a synchronous connection, or an async one that
         * failed before it could enter background mode, e.g. because an
         * invalid -myaddr was given.
         */
        if (interp != NULL) {
            Tcl_AppendResult(interp, "couldn't open socket: ",
                             Tcl_PosixError(interp), NULL);
        }
        return TCL_ERROR;

    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *