diff options
author | max <max@tclers.tk> | 2011-06-28 11:32:15 (GMT) |
---|---|---|
committer | max <max@tclers.tk> | 2011-06-28 11:32:15 (GMT) |
commit | 1543959a9b88e49df5b1f1ba37872eeae9eabb1e (patch) | |
tree | 4e1697dfded4c6d7238c3c13f317b9a9c8a91ccb /unix/tclUnixSock.c | |
parent | c6201577c310d21572008b287406f4a520847d2a (diff) | |
download | tcl-1543959a9b88e49df5b1f1ba37872eeae9eabb1e.zip tcl-1543959a9b88e49df5b1f1ba37872eeae9eabb1e.tar.gz tcl-1543959a9b88e49df5b1f1ba37872eeae9eabb1e.tar.bz2 |
* 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.
Diffstat (limited to 'unix/tclUnixSock.c')
-rw-r--r-- | unix/tclUnixSock.c | 75 |
1 files changed, 56 insertions, 19 deletions
diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 5ace251..52b089c 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -858,6 +858,17 @@ TcpGetHandleProc( 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. */ @@ -883,6 +894,18 @@ TcpAsyncCallback( * 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. + * *---------------------------------------------------------------------- */ @@ -892,14 +915,14 @@ CreateClientSocket( TcpState *state) { socklen_t optlen; - int in_coro = (state->addr != NULL); + int async_callback = (state->addr != NULL); int status; int async = state->flags & TCP_ASYNC_CONNECT; - if (in_coro) { - goto coro_continue; + if (async_callback) { + goto reenter; } - + for (state->addr = state->addrlist; state->addr != NULL; state->addr = state->addr->ai_next) { @@ -976,12 +999,13 @@ CreateClientSocket( TcpAsyncCallback, state); return TCL_OK; - coro_continue: + reenter: 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] + * 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, @@ -996,22 +1020,35 @@ CreateClientSocket( out: - if (async) { + 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); - } - 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; + /* + * 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; } |