소스 검색

Make blocking socket calls on Windows interruptible

This second commit adds functions to socket-io.c which uses
WSAEventSelect()/WSAWaitForMultipleEvents() or overlapped I/O to make
potentielly blocking socket calls interruptible (alertable).
Niklas Therning 9 년 전
부모
커밋
53af53ae59

+ 2 - 2
mcs/class/System/System.Net.Sockets/Socket.cs

@@ -2354,8 +2354,8 @@ m_Handle, buffer, offset + sent, size - sent, socketFlags, out nativeError, is_b
 			if (!is_blocking)
 				throw new InvalidOperationException ();
 
-			int error;
-			if (!SendFile_internal (m_Handle, fileName, preBuffer, postBuffer, flags, out error, is_blocking)) {
+			int error = 0;
+			if (!SendFile_internal (m_Handle, fileName, preBuffer, postBuffer, flags, out error, is_blocking) || error != 0) {
 				SocketException exc = new SocketException (error);
 				if (exc.ErrorCode == 2 || exc.ErrorCode == 3)
 					throw new FileNotFoundException ();

+ 1 - 1
mcs/class/corlib/System/Environment.cs

@@ -57,7 +57,7 @@ namespace System {
 		 * of icalls, do not require an increment.
 		 */
 #pragma warning disable 169
-		private const int mono_corlib_version = 158;
+		private const int mono_corlib_version = 159;
 #pragma warning restore 169
 
 		[ComVisible (true)]

+ 2 - 1
mono/metadata/Makefile.am

@@ -3,7 +3,8 @@ win32_sources = \
 	console-win32.c \
 	w32mutex-win32.c \
 	w32semaphore-win32.c \
-	w32event-win32.c
+	w32event-win32.c \
+	socket-io-windows.c
 
 platform_sources = $(win32_sources)
 

+ 1 - 1
mono/metadata/appdomain.c

@@ -84,7 +84,7 @@
  * Changes which are already detected at runtime, like the addition
  * of icalls, do not require an increment.
  */
-#define MONO_CORLIB_VERSION 158
+#define MONO_CORLIB_VERSION 159
 
 typedef struct
 {

+ 24 - 0
mono/metadata/socket-io-windows-internals.h

@@ -0,0 +1,24 @@
+/*
+* socket-io-windows-internals.h: Windows specific socket code.
+*
+* Copyright 2016 Microsoft
+* Licensed under the MIT license. See LICENSE file in the project root for full license information.
+*/
+#ifndef __MONO_METADATA_SOCKET_IO_WINDOWS_INTERNALS_H__
+#define __MONO_METADATA_SOCKET_IO_WINDOWS_INTERNALS_H__
+
+#include <config.h>
+#include <glib.h>
+#include <mono/io-layer/io-layer.h>
+
+SOCKET alertable_accept (SOCKET s, struct sockaddr *addr, int *addrlen, gboolean blocking);
+int alertable_connect (SOCKET s, const struct sockaddr *name, int namelen, gboolean blocking);
+int alertable_recv (SOCKET s, char *buf, int len, int flags, gboolean blocking);
+int alertable_recvfrom (SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen, gboolean blocking);
+int alertable_WSARecv (SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, gboolean blocking);
+int alertable_send (SOCKET s, char *buf, int len, int flags, gboolean blocking);
+int alertable_sendto (SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen, gboolean blocking);
+int alertable_WSASend (SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, DWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, gboolean blocking);
+BOOL alertable_TransmitFile (SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, LPOVERLAPPED lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwReserved, gboolean blocking);
+
+#endif // __MONO_METADATA_SOCKET_IO_WINDOWS_INTERNALS_H__

+ 207 - 0
mono/metadata/socket-io-windows.c

@@ -0,0 +1,207 @@
+/*
+* socket-io-windows.c: Windows specific socket code.
+*
+* Copyright 2016 Microsoft
+* Licensed under the MIT license. See LICENSE file in the project root for full license information.
+*/
+#include <config.h>
+#include <glib.h>
+
+#include "mono/metadata/socket-io-windows-internals.h"
+
+#define LOGDEBUG(...)  
+
+static gboolean set_blocking (SOCKET sock, gboolean block)
+{
+	u_long non_block = block ? 0 : 1;
+	return ioctlsocket (sock, FIONBIO, &non_block) != SOCKET_ERROR;
+}
+
+static DWORD get_socket_timeout (SOCKET sock, int optname)
+{
+	DWORD timeout = 0;
+	int optlen = sizeof (DWORD);
+	if (getsockopt (sock, SOL_SOCKET, optname, (char *)&timeout, &optlen) == SOCKET_ERROR) {
+		WSASetLastError (0);
+		return WSA_INFINITE;
+	}
+	if (timeout == 0)
+		timeout = WSA_INFINITE; // 0 means infinite
+	return timeout;
+}
+
+/*
+* Performs an alertable wait for the specified event (FD_ACCEPT_BIT,
+* FD_CONNECT_BIT, FD_READ_BIT, FD_WRITE_BIT) on the specified socket.
+* Returns TRUE if the event is fired without errors. Calls WSASetLastError()
+* with WSAEINTR and returns FALSE if the thread is alerted. If the event is
+* fired but with an error WSASetLastError() is called to set the error and the
+* function returns FALSE.
+*/
+static gboolean alertable_socket_wait (SOCKET sock, int event_bit)
+{
+	static char *EVENT_NAMES[] = { "FD_READ", "FD_WRITE", NULL /*FD_OOB*/, "FD_ACCEPT", "FD_CONNECT", "FD_CLOSE" };
+	gboolean success = FALSE;
+	int error = -1;
+	DWORD timeout = WSA_INFINITE;
+	if (event_bit == FD_READ_BIT || event_bit == FD_WRITE_BIT) {
+		timeout = get_socket_timeout (sock, event_bit == FD_READ_BIT ? SO_RCVTIMEO : SO_SNDTIMEO);
+	}
+	WSASetLastError (0);
+	WSAEVENT event = WSACreateEvent ();
+	if (event != WSA_INVALID_EVENT) {
+		if (WSAEventSelect (sock, event, (1 << event_bit) | FD_CLOSE) != SOCKET_ERROR) {
+			LOGDEBUG (g_message ("%06d - Calling WSAWaitForMultipleEvents () on socket %d", GetCurrentThreadId (), sock));
+			DWORD ret = WSAWaitForMultipleEvents (1, &event, TRUE, timeout, TRUE);
+			if (ret == WSA_WAIT_IO_COMPLETION) {
+				LOGDEBUG (g_message ("%06d - WSAWaitForMultipleEvents () returned WSA_WAIT_IO_COMPLETION for socket %d", GetCurrentThreadId (), sock));
+				error = WSAEINTR;
+			} else if (ret == WSA_WAIT_TIMEOUT) {
+				error = WSAETIMEDOUT;
+			} else {
+				g_assert (ret == WSA_WAIT_EVENT_0);
+				WSANETWORKEVENTS ne = { 0 };
+				if (WSAEnumNetworkEvents (sock, event, &ne) != SOCKET_ERROR) {
+					if (ne.lNetworkEvents & (1 << event_bit) && ne.iErrorCode[event_bit]) {
+						LOGDEBUG (g_message ("%06d - %s error %d on socket %d", GetCurrentThreadId (), EVENT_NAMES[event_bit], ne.iErrorCode[event_bit], sock));
+						error = ne.iErrorCode[event_bit];
+					} else if (ne.lNetworkEvents & FD_CLOSE_BIT && ne.iErrorCode[FD_CLOSE_BIT]) {
+						LOGDEBUG (g_message ("%06d - FD_CLOSE error %d on socket %d", GetCurrentThreadId (), ne.iErrorCode[FD_CLOSE_BIT], sock));
+						error = ne.iErrorCode[FD_CLOSE_BIT];
+					} else {
+						LOGDEBUG (g_message ("%06d - WSAEnumNetworkEvents () finished successfully on socket %d", GetCurrentThreadId (), sock));
+						success = TRUE;
+						error = 0;
+					}
+				}
+			}
+			WSAEventSelect (sock, NULL, 0);
+		}
+		WSACloseEvent (event);
+	}
+	if (error != -1) {
+		WSASetLastError (error);
+	}
+	return success;
+}
+
+#define ALERTABLE_SOCKET_CALL(event_bit, blocking, repeat, ret, op, sock, ...) \
+	LOGDEBUG (g_message ("%06d - Performing %s " #op " () on socket %d", GetCurrentThreadId (), blocking ? "blocking" : "non-blocking", sock)); \
+	if (blocking) { \
+		if (set_blocking(sock, FALSE)) { \
+			while (-1 == (int) (ret = op (sock, __VA_ARGS__))) { \
+				int _error = WSAGetLastError ();\
+				if (_error != WSAEWOULDBLOCK && _error != WSA_IO_PENDING) \
+					break; \
+				if (!alertable_socket_wait (sock, event_bit) || !repeat) \
+					break; \
+			} \
+			int _saved_error = WSAGetLastError (); \
+			set_blocking (sock, TRUE); \
+			WSASetLastError (_saved_error); \
+		} \
+	} else { \
+		ret = op (sock, __VA_ARGS__); \
+	} \
+	int _saved_error = WSAGetLastError (); \
+	LOGDEBUG (g_message ("%06d - Finished %s " #op " () on socket %d (ret = %d, WSAGetLastError() = %d)", GetCurrentThreadId (), \
+		blocking ? "blocking" : "non-blocking", sock, ret, _saved_error)); \
+	WSASetLastError (_saved_error);
+
+SOCKET alertable_accept (SOCKET s, struct sockaddr *addr, int *addrlen, gboolean blocking)
+{
+	SOCKET newsock = INVALID_SOCKET;
+	ALERTABLE_SOCKET_CALL (FD_ACCEPT_BIT, blocking, TRUE, newsock, accept, s, addr, addrlen);
+	return newsock;
+}
+
+int alertable_connect (SOCKET s, const struct sockaddr *name, int namelen, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_CONNECT_BIT, blocking, FALSE, ret, connect, s, name, namelen);
+	ret = WSAGetLastError () != 0 ? SOCKET_ERROR : 0;
+	return ret;
+}
+
+int alertable_recv (SOCKET s, char *buf, int len, int flags, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_READ_BIT, blocking, TRUE, ret, recv, s, buf, len, flags);
+	return ret;
+}
+
+int alertable_recvfrom (SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_READ_BIT, blocking, TRUE, ret, recvfrom, s, buf, len, flags, from, fromlen);
+	return ret;
+}
+
+int alertable_WSARecv (SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_READ_BIT, blocking, TRUE, ret, WSARecv, s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);
+	return ret;
+}
+
+int alertable_send (SOCKET s, char *buf, int len, int flags, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_WRITE_BIT, blocking, FALSE, ret, send, s, buf, len, flags);
+	return ret;
+}
+
+int alertable_sendto (SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_WRITE_BIT, blocking, FALSE, ret, sendto, s, buf, len, flags, to, tolen);
+	return ret;
+}
+
+int alertable_WSASend (SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, DWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, gboolean blocking)
+{
+	int ret = SOCKET_ERROR;
+	ALERTABLE_SOCKET_CALL (FD_WRITE_BIT, blocking, FALSE, ret, WSASend, s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);
+	return ret;
+}
+
+BOOL alertable_TransmitFile (SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, LPOVERLAPPED lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwReserved, gboolean blocking)
+{
+	LOGDEBUG (g_message ("%06d - Performing %s TransmitFile () on socket %d", GetCurrentThreadId (), blocking ? "blocking" : "non-blocking", hSocket));
+
+	int error = 0;
+	if (blocking) {
+		g_assert (lpOverlapped == NULL);
+		OVERLAPPED overlapped = { 0 };
+		overlapped.hEvent = WSACreateEvent ();
+		if (overlapped.hEvent == WSA_INVALID_EVENT)
+			return FALSE;
+		if (!TransmitFile (hSocket, hFile, nNumberOfBytesToWrite, nNumberOfBytesPerSend, &overlapped, lpTransmitBuffers, dwReserved)) {
+			error = WSAGetLastError ();
+			if (error == WSA_IO_PENDING) {
+				error = 0;
+				// NOTE: .NET's Socket.SendFile() doesn't honor the Socket's SendTimeout so we shouldn't either
+				DWORD ret = WaitForSingleObjectEx (overlapped.hEvent, INFINITE, TRUE);
+				if (ret == WAIT_IO_COMPLETION) {
+					LOGDEBUG (g_message ("%06d - WaitForSingleObjectEx () returned WSA_WAIT_IO_COMPLETION for socket %d", GetCurrentThreadId (), hSocket));
+					error = WSAEINTR;
+				} else if (ret == WAIT_TIMEOUT) {
+					error = WSAETIMEDOUT;
+				} else if (ret != WAIT_OBJECT_0) {
+					error = GetLastError ();
+				}
+			}
+		}
+		WSACloseEvent (overlapped.hEvent);
+	} else {
+		if (!TransmitFile (hSocket, hFile, nNumberOfBytesToWrite, nNumberOfBytesPerSend, lpOverlapped, lpTransmitBuffers, dwReserved)) {
+			error = WSAGetLastError ();
+		}
+	}
+
+	LOGDEBUG (g_message ("%06d - Finished %s TransmitFile () on socket %d (ret = %d, WSAGetLastError() = %d)", GetCurrentThreadId (), \
+		blocking ? "blocking" : "non-blocking", hSocket, error == 0, error));
+	WSASetLastError (error);
+
+	return error == 0;
+}

+ 33 - 2
mono/metadata/socket-io.c

@@ -94,6 +94,9 @@
 #endif
 
 #include "mono/io-layer/socket-wrappers.h"
+#ifdef HOST_WIN32
+#include "mono/metadata/socket-io-windows-internals.h"
+#endif
 
 #define LOGDEBUG(...)  
 /* define LOGDEBUG(...) g_message(__VA_ARGS__)  */
@@ -765,7 +768,7 @@ ves_icall_System_Net_Sockets_Socket_Accept_internal (SOCKET sock, gint32 *werror
 	{
 		MonoInternalThread *curthread = mono_thread_internal_current ();
 		curthread->interrupt_on_stop = (gpointer)TRUE;
-		newsock = _wapi_accept (sock, NULL, 0);
+		newsock = alertable_accept (sock, NULL, 0, blocking);
 		curthread->interrupt_on_stop = (gpointer)FALSE;
 	}
 #else
@@ -1331,7 +1334,11 @@ ves_icall_System_Net_Sockets_Socket_Connect_internal (SOCKET sock, MonoObject *s
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_connect (sock, sa, sa_size, blocking);
+#else
 	ret = _wapi_connect (sock, sa, sa_size);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 
@@ -1469,7 +1476,7 @@ ves_icall_System_Net_Sockets_Socket_Receive_internal (SOCKET sock, MonoArray *bu
 #ifdef HOST_WIN32
 	{
 		curthread->interrupt_on_stop = (gpointer)TRUE;
-		ret = _wapi_recv (sock, buf, count, recvflags);
+		ret = alertable_recv (sock, buf, count, recvflags, blocking);
 		curthread->interrupt_on_stop = (gpointer)FALSE;
 	}
 #else
@@ -1519,7 +1526,11 @@ ves_icall_System_Net_Sockets_Socket_Receive_array_internal (SOCKET sock, MonoArr
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_WSARecv (sock, wsabufs, count, &recv, &recvflags, NULL, NULL, blocking);
+#else
 	ret = WSARecv (sock, wsabufs, count, &recv, &recvflags, NULL, NULL);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 
@@ -1579,7 +1590,11 @@ ves_icall_System_Net_Sockets_Socket_ReceiveFrom_internal (SOCKET sock, MonoArray
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_recvfrom (sock, buf, count, recvflags, sa, &sa_size, blocking);
+#else
 	ret = _wapi_recvfrom (sock, buf, count, recvflags, sa, &sa_size);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 
@@ -1651,7 +1666,11 @@ ves_icall_System_Net_Sockets_Socket_Send_internal (SOCKET sock, MonoArray *buffe
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_send (sock, buf, count, sendflags, blocking);
+#else
 	ret = _wapi_send (sock, buf, count, sendflags);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 
@@ -1696,7 +1715,11 @@ ves_icall_System_Net_Sockets_Socket_Send_array_internal (SOCKET sock, MonoArray
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_WSASend (sock, wsabufs, count, &sent, sendflags, NULL, NULL, blocking);
+#else
 	ret = WSASend (sock, wsabufs, count, &sent, sendflags, NULL, NULL);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 
@@ -1762,7 +1785,11 @@ ves_icall_System_Net_Sockets_Socket_SendTo_internal (SOCKET sock, MonoArray *buf
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_sendto (sock, buf, count, sendflags, sa, sa_size, blocking);
+#else
 	ret = _wapi_sendto (sock, buf, count, sendflags, sa, sa_size);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 
@@ -2812,7 +2839,11 @@ ves_icall_System_Net_Sockets_Socket_SendFile_internal (SOCKET sock, MonoString *
 
 	MONO_ENTER_GC_SAFE;
 
+#ifdef HOST_WIN32
+	ret = alertable_TransmitFile (sock, file, 0, 0, NULL, &buffers, flags, blocking);
+#else
 	ret = TransmitFile (sock, file, 0, 0, NULL, &buffers, flags);
+#endif
 
 	MONO_EXIT_GC_SAFE;
 

+ 2 - 0
mono/utils/mono-threads-windows-abort-syscall.c

@@ -24,6 +24,7 @@ mono_threads_abort_syscall_init (void)
 static void CALLBACK
 abort_apc (ULONG_PTR param)
 {
+	THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ());
 }
 
 void
@@ -35,6 +36,7 @@ mono_threads_suspend_abort_syscall (MonoThreadInfo *info)
 	handle = OpenThread (THREAD_ALL_ACCESS, FALSE, id);
 	g_assert (handle);
 
+	THREADS_INTERRUPT_DEBUG ("%06d - Aborting syscall in thread %06d", GetCurrentThreadId (), id);
 	QueueUserAPC ((PAPCFUNC)abort_apc, handle, (ULONG_PTR)NULL);
 
 	CloseHandle (handle);

+ 2 - 0
msvc/libmonoruntime.vcxproj

@@ -26,6 +26,7 @@
     <ClCompile Include="..\mono\metadata\class.c" />
     <ClCompile Include="..\mono\metadata\cominterop.c" />
     <ClCompile Include="..\mono\metadata\console-win32.c" />
+    <ClCompile Include="..\mono\metadata\socket-io-windows.c" />
     <ClCompile Include="..\mono\metadata\w32mutex-win32.c" />
     <ClCompile Include="..\mono\metadata\w32semaphore-win32.c" />
     <ClCompile Include="..\mono\metadata\w32event-win32.c" />
@@ -144,6 +145,7 @@
     <ClInclude Include="..\mono\metadata\seq-points-data.h" />
     <ClInclude Include="..\mono\metadata\sgen-bridge-internals.h" />
     <ClInclude Include="..\mono\metadata\sgen-client-mono.h" />
+    <ClInclude Include="..\mono\metadata\socket-io-windows-internals.h" />
     <ClInclude Include="..\mono\metadata\threadpool-ms-io.h" />
     <ClInclude Include="..\mono\metadata\threadpool-ms.h" />
     <ClInclude Include="..\mono\sgen\gc-internal-agnostic.h" />

+ 6 - 0
msvc/libmonoruntime.vcxproj.filters

@@ -253,6 +253,9 @@
     <ClCompile Include="..\mono\metadata\sgen-os-coop.c">
       <Filter>Source Files\sgen</Filter>
     </ClCompile>
+    <ClCompile Include="..\mono\metadata\socket-io-windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\mono\metadata\appdomain.h">
@@ -501,6 +504,9 @@
     <ClInclude Include="..\mono\metadata\sre-internals.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\mono\metadata\socket-io-windows-internals.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Filter Include="Header Files">