summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoroehhar <harald.oehlmann@elmicron.de>2014-04-02 10:02:18 (GMT)
committeroehhar <harald.oehlmann@elmicron.de>2014-04-02 10:02:18 (GMT)
commit30a15d58d30ef07b7bcf90e876e495fdfb55a830 (patch)
tree8c46a04e41cbbd5954b1b0826cbf13a35885e9f6
parentc85fe58264794439fe6057b5d98428a033774bcc (diff)
parentb5277efde02115a99b120d3a90fb1471c6aee409 (diff)
downloadtcl-30a15d58d30ef07b7bcf90e876e495fdfb55a830.zip
tcl-30a15d58d30ef07b7bcf90e876e495fdfb55a830.tar.gz
tcl-30a15d58d30ef07b7bcf90e876e495fdfb55a830.tar.bz2
Fix bug [336441ed59]: Win socket stall on quick termination of async socket connect
-rw-r--r--tests/socket.test16
-rw-r--r--win/tclWinSock.c246
2 files changed, 184 insertions, 78 deletions
diff --git a/tests/socket.test b/tests/socket.test
index 0ae5abd..218cce4 100644
--- a/tests/socket.test
+++ b/tests/socket.test
@@ -910,6 +910,22 @@ test socket-8.1 {testing -async flag on sockets} {socket} {
set z
} bye
+test socket-8.2 {testing writable event when quick failure} {socket win} {
+ # Test for bug 336441ed59 where a quick background fail was ignored
+
+ # Test only for windows as socket -async 255.255.255.255 fails directly
+ # on unix
+
+ # The following connect should fail very quickly
+ set a1 [after 2000 {set x timeout}]
+ set s [socket -async 255.255.255.255 43434]
+ fileevent $s writable {set x writable}
+ vwait x
+ catch {close $s}
+ after cancel $a1
+ set x
+} writable
+
test socket-9.1 {testing spurious events} {socket} {
set len 0
set spurious 0
diff --git a/win/tclWinSock.c b/win/tclWinSock.c
index 9fa01c9..e7b086a 100644
--- a/win/tclWinSock.c
+++ b/win/tclWinSock.c
@@ -98,29 +98,31 @@ static ProcessGlobalValue hostName = {
/*
* The following structure is used to store the data associated with each
* socket.
+ * All members modified by the notifier thread are defined as volatile.
*/
typedef struct SocketInfo {
Tcl_Channel channel; /* Channel associated with this socket. */
SOCKET socket; /* Windows SOCKET handle. */
- int flags; /* Bit field comprised of the flags described
+ volatile int flags; /* Bit field comprised of the flags described
* below. */
int watchEvents; /* OR'ed combination of FD_READ, FD_WRITE,
* FD_CLOSE, FD_ACCEPT and FD_CONNECT that
* indicate which events are interesting. */
- int readyEvents; /* OR'ed combination of FD_READ, FD_WRITE,
+ volatile int readyEvents; /* OR'ed combination of FD_READ, FD_WRITE,
* FD_CLOSE, FD_ACCEPT and FD_CONNECT that
* indicate which events have occurred. */
int selectEvents; /* OR'ed combination of FD_READ, FD_WRITE,
* FD_CLOSE, FD_ACCEPT and FD_CONNECT that
* indicate which events are currently being
* selected. */
- int acceptEventCount; /* Count of the current number of FD_ACCEPTs
+ volatile int acceptEventCount;
+ /* Count of the current number of FD_ACCEPTs
* that have arrived and not yet processed. */
Tcl_TcpAcceptProc *acceptProc;
/* Proc to call on accept. */
ClientData acceptProcData; /* The data for the accept proc. */
- int lastError; /* Error code from last message. */
+ volatile int lastError; /* Error code from last message. */
struct SocketInfo *nextPtr; /* The next socket on the per-thread socket
* list. */
} SocketInfo;
@@ -167,6 +169,10 @@ typedef struct {
* socketThread has been initialized and has
* started. */
HANDLE socketListLock; /* Win32 Event to lock the socketList */
+ SocketInfo *pendingSocketInfo;
+ /* This socket is opened but not jet in the
+ * list. This value is also checked by
+ * the event structure. */
SocketInfo *socketList; /* Every open socket in this thread has an
* entry on this list. */
} ThreadSpecificData;
@@ -329,6 +335,7 @@ InitSockets(void)
if (tsdPtr == NULL) {
tsdPtr = TCL_TSD_INIT(&dataKey);
+ tsdPtr->pendingSocketInfo = NULL;
tsdPtr->socketList = NULL;
tsdPtr->hwnd = NULL;
tsdPtr->threadId = Tcl_GetCurrentThread();
@@ -815,7 +822,7 @@ TcpCloseProc(
SocketInfo *infoPtr = (SocketInfo *) instanceData;
/* TIP #218 */
int errorCode = 0;
- /* ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); */
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
* Check that WinSock is initialized; do not call it if not, to prevent
@@ -837,6 +844,23 @@ TcpCloseProc(
}
/*
+ * Clear an eventual tsd info list pointer.
+ * This may be called, if an async socket connect fails or is closed
+ * between connect and thread action callback.
+ */
+ if (tsdPtr->pendingSocketInfo != NULL
+ && tsdPtr->pendingSocketInfo == infoPtr) {
+
+ /* get infoPtr lock, because this concerns the notifier thread */
+ WaitForSingleObject(tsdPtr->socketListLock, INFINITE);
+
+ tsdPtr->pendingSocketInfo = NULL;
+
+ /* Free list lock */
+ SetEvent(tsdPtr->socketListLock);
+ }
+
+ /*
* TIP #218. Removed the code removing the structure from the global
* socket list. This is now done by the thread action callbacks, and only
* there. This happens before this code is called. We can free without
@@ -923,12 +947,10 @@ CreateSocket(
* asynchronously. */
{
u_long flag = 1; /* Indicates nonblocking mode. */
- int asyncConnect = 0; /* Will be 1 if async connect is in
- * progress. */
SOCKADDR_IN sockaddr; /* Socket address */
SOCKADDR_IN mysockaddr; /* Socket address for client */
SOCKET sock = INVALID_SOCKET;
- SocketInfo *infoPtr; /* The returned value. */
+ SocketInfo *infoPtr=NULL; /* The returned value. */
ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
TclThreadDataKeyGet(&dataKey);
@@ -1007,6 +1029,15 @@ CreateSocket(
infoPtr->selectEvents = FD_ACCEPT;
infoPtr->watchEvents |= FD_ACCEPT;
+ /*
+ * Register for interest in events in the select mask. Note that this
+ * automatically places the socket into non-blocking mode.
+ */
+
+ ioctlsocket(sock, (long) FIONBIO, &flag);
+ SendMessage(tsdPtr->hwnd, SOCKET_SELECT, (WPARAM) SELECT,
+ (LPARAM) infoPtr);
+
} else {
/*
* Try to bind to a local port, if specified.
@@ -1020,14 +1051,54 @@ CreateSocket(
}
/*
+ * Allocate socket info structure
+ */
+
+ infoPtr = NewSocketInfo(sock);
+
+ /*
* Set the socket into nonblocking mode if the connect should be done
- * in the background.
+ * in the background. Activate connect notification.
*/
if (async) {
- if (ioctlsocket(sock, (long) FIONBIO, &flag) == SOCKET_ERROR) {
- goto error;
- }
+
+ /* get infoPtr lock */
+ WaitForSingleObject(tsdPtr->socketListLock, INFINITE);
+
+ /*
+ * Buffer new infoPtr in the tsd memory as long as it is not in
+ * the info list. This allows the event procedure to process the
+ * event.
+ * Bugfig for 336441ed59 to not ignore notifications until the
+ * infoPtr is in the list..
+ */
+
+ tsdPtr->pendingSocketInfo = infoPtr;
+
+ /*
+ * Set connect mask to connect events
+ * This is activated by a SOCKET_SELECT message to the notifier
+ * thread.
+ */
+
+ infoPtr->selectEvents |= FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE;
+ infoPtr->flags |= SOCKET_ASYNC_CONNECT;
+
+ /*
+ * Free list lock
+ */
+ SetEvent(tsdPtr->socketListLock);
+
+ /*
+ * Activate accept notification and put in async mode
+ * Bug 336441ed59: activate notification before connect
+ * so we do not miss a notification of a fialed connect.
+ */
+ ioctlsocket(sock, (long) FIONBIO, &flag);
+ SendMessage(tsdPtr->hwnd, SOCKET_SELECT, (WPARAM) SELECT,
+ (LPARAM) infoPtr);
+
}
/*
@@ -1045,35 +1116,26 @@ CreateSocket(
* The connection is progressing in the background.
*/
- asyncConnect = 1;
- }
+ } else {
- /*
- * Add this socket to the global list of sockets.
- */
+ /*
+ * Set up the select mask for read/write events. If the connect
+ * attempt has not completed, include connect events.
+ */
- infoPtr = NewSocketInfo(sock);
+ infoPtr->selectEvents = FD_READ | FD_WRITE | FD_CLOSE;
- /*
- * Set up the select mask for read/write events. If the connect
- * attempt has not completed, include connect events.
- */
+ /*
+ * Register for interest in events in the select mask. Note that this
+ * automatically places the socket into non-blocking mode.
+ */
- infoPtr->selectEvents = FD_READ | FD_WRITE | FD_CLOSE;
- if (asyncConnect) {
- infoPtr->flags |= SOCKET_ASYNC_CONNECT;
- infoPtr->selectEvents |= FD_CONNECT;
+ ioctlsocket(sock, (long) FIONBIO, &flag);
+ SendMessage(tsdPtr->hwnd, SOCKET_SELECT, (WPARAM) SELECT,
+ (LPARAM) infoPtr);
}
}
- /*
- * Register for interest in events in the select mask. Note that this
- * automatically places the socket into non-blocking mode.
- */
-
- ioctlsocket(sock, (long) FIONBIO, &flag);
- SendMessage(tsdPtr->hwnd, SOCKET_SELECT, (WPARAM) SELECT, (LPARAM) infoPtr);
-
return infoPtr;
error:
@@ -1082,7 +1144,15 @@ CreateSocket(
Tcl_AppendResult(interp, "couldn't open socket: ",
Tcl_PosixError(interp), NULL);
}
- if (sock != INVALID_SOCKET) {
+ if (infoPtr != NULL) {
+ /*
+ * Free the allocated socket info structure and close the socket
+ */
+ TcpCloseProc(infoPtr, interp);
+ } else if (sock != INVALID_SOCKET) {
+ /*
+ * No socket structure jet - just close
+ */
closesocket(sock);
}
return NULL;
@@ -1482,7 +1552,7 @@ TcpAccept(
SetHandleInformation((HANDLE) newSocket, HANDLE_FLAG_INHERIT, 0);
/*
- * Add this socket to the global list of sockets.
+ * Allocate socket info structure
*/
newInfoPtr = NewSocketInfo(newSocket);
@@ -2248,6 +2318,7 @@ SocketProc(
int event, error;
SOCKET socket;
SocketInfo *infoPtr;
+ int info_found = 0;
ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
#ifdef _WIN64
GetWindowLongPtr(hwnd, GWLP_USERDATA);
@@ -2293,58 +2364,72 @@ SocketProc(
for (infoPtr = tsdPtr->socketList; infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (infoPtr->socket == socket) {
- /*
- * Update the socket state.
- *
- * A count of FD_ACCEPTS is stored, so if an FD_CLOSE event
- * happens, then clear the FD_ACCEPT count. Otherwise,
- * increment the count if the current event is an FD_ACCEPT.
- */
+ info_found = 1;
+ break;
+ }
+ }
+ /*
+ * Check if there is a pending info structure not jet in the
+ * list
+ */
+ if ( !info_found
+ && tsdPtr->pendingSocketInfo != NULL
+ && tsdPtr->pendingSocketInfo->socket ==socket ) {
+ infoPtr = tsdPtr->pendingSocketInfo;
+ info_found = 1;
+ }
+ if (info_found) {
- if (event & FD_CLOSE) {
- infoPtr->acceptEventCount = 0;
- infoPtr->readyEvents &= ~(FD_WRITE|FD_ACCEPT);
- } else if (event & FD_ACCEPT) {
- infoPtr->acceptEventCount++;
- }
+ /*
+ * Update the socket state.
+ *
+ * A count of FD_ACCEPTS is stored, so if an FD_CLOSE event
+ * happens, then clear the FD_ACCEPT count. Otherwise,
+ * increment the count if the current event is an FD_ACCEPT.
+ */
- if (event & FD_CONNECT) {
- /*
- * The socket is now connected, clear the async connect
- * flag.
- */
+ if (event & FD_CLOSE) {
+ infoPtr->acceptEventCount = 0;
+ infoPtr->readyEvents &= ~(FD_WRITE|FD_ACCEPT);
+ } else if (event & FD_ACCEPT) {
+ infoPtr->acceptEventCount++;
+ }
- infoPtr->flags &= ~(SOCKET_ASYNC_CONNECT);
+ if (event & FD_CONNECT) {
+ /*
+ * The socket is now connected, clear the async connect
+ * flag.
+ */
- /*
- * Remember any error that occurred so we can report
- * connection failures.
- */
+ infoPtr->flags &= ~(SOCKET_ASYNC_CONNECT);
+
+ /*
+ * Remember any error that occurred so we can report
+ * connection failures.
+ */
- if (error != ERROR_SUCCESS) {
- TclWinConvertWSAError((DWORD) error);
- infoPtr->lastError = Tcl_GetErrno();
- }
+ if (error != ERROR_SUCCESS) {
+ TclWinConvertWSAError((DWORD) error);
+ infoPtr->lastError = Tcl_GetErrno();
}
+ }
- if (infoPtr->flags & SOCKET_ASYNC_CONNECT) {
- infoPtr->flags &= ~(SOCKET_ASYNC_CONNECT);
- if (error != ERROR_SUCCESS) {
- TclWinConvertWSAError((DWORD) error);
- infoPtr->lastError = Tcl_GetErrno();
- }
- infoPtr->readyEvents |= FD_WRITE;
+ if (infoPtr->flags & SOCKET_ASYNC_CONNECT) {
+ infoPtr->flags &= ~(SOCKET_ASYNC_CONNECT);
+ if (error != ERROR_SUCCESS) {
+ TclWinConvertWSAError((DWORD) error);
+ infoPtr->lastError = Tcl_GetErrno();
}
- infoPtr->readyEvents |= event;
+ infoPtr->readyEvents |= FD_WRITE;
+ }
+ infoPtr->readyEvents |= event;
- /*
- * Wake up the Main Thread.
- */
+ /*
+ * Wake up the Main Thread.
+ */
- SetEvent(tsdPtr->readyEvent);
- Tcl_ThreadAlert(tsdPtr->threadId);
- break;
- }
+ SetEvent(tsdPtr->readyEvent);
+ Tcl_ThreadAlert(tsdPtr->threadId);
}
SetEvent(tsdPtr->socketListLock);
break;
@@ -2580,6 +2665,11 @@ TcpThreadActionProc(
WaitForSingleObject(tsdPtr->socketListLock, INFINITE);
infoPtr->nextPtr = tsdPtr->socketList;
tsdPtr->socketList = infoPtr;
+
+ if (infoPtr == tsdPtr->pendingSocketInfo) {
+ tsdPtr->pendingSocketInfo = NULL;
+ }
+
SetEvent(tsdPtr->socketListLock);
notifyCmd = SELECT;