diff --git a/mono/metadata/w32file-win32.c b/mono/metadata/w32file-win32.c index f428031a7a37..3ac8bf2506df 100644 --- a/mono/metadata/w32file-win32.c +++ b/mono/metadata/w32file-win32.c @@ -92,24 +92,24 @@ cancel_w32_io (HANDLE file_handle) gboolean mono_w32file_read (gpointer handle, gpointer buffer, guint32 numbytes, guint32 *bytesread) { - gboolean res = FALSE; + gboolean interrupted; + guint32 last_error; + gboolean res; + + MonoThreadInfo *info = mono_thread_info_current (); + + mono_win32_enter_blocking_io_call (info, (HANDLE)handle); + MONO_ENTER_GC_SAFE; + res = ReadFile ((HANDLE)handle, buffer, numbytes, (PDWORD)bytesread, NULL); + /* need to save and restore since clients expect error code set for + * failed IO calls and mono_thread_info_uninstall_interrupt overwrites value */ + last_error = mono_w32error_get_last (); + + MONO_EXIT_GC_SAFE; + mono_win32_leave_blocking_io_call (info, (HANDLE)handle); + mono_w32error_set_last (last_error); - mono_thread_info_install_interrupt (cancel_w32_io, handle, &interrupted); - if (!interrupted) - { - guint32 last_error; - MONO_ENTER_GC_SAFE; - res = ReadFile (handle, buffer, numbytes, bytesread, NULL); - MONO_PROFILER_RAISE (fileio, (1, *bytesread)); - MONO_EXIT_GC_SAFE; - - /* need to save and restore since clients expect error code set for - * failed IO calls and mono_thread_info_uninstall_interrupt overwrites value */ - last_error = mono_w32error_get_last (); - mono_thread_info_uninstall_interrupt (&interrupted); - mono_w32error_set_last (last_error); - } return res; } @@ -117,24 +117,21 @@ mono_w32file_read (gpointer handle, gpointer buffer, guint32 numbytes, guint32 * gboolean mono_w32file_write (gpointer handle, gconstpointer buffer, guint32 numbytes, guint32 *byteswritten) { - gboolean res = FALSE; gboolean interrupted; + guint32 last_error; + gboolean res; + + MonoThreadInfo *info = mono_thread_info_current (); - mono_thread_info_install_interrupt (cancel_w32_io, handle, &interrupted); - if (!interrupted) - { - guint32 last_error; - MONO_ENTER_GC_SAFE; - res = WriteFile (handle, buffer, numbytes, byteswritten, NULL); - MONO_PROFILER_RAISE (fileio, (0, *byteswritten)); - MONO_EXIT_GC_SAFE; - - /* need to save and restore since clients expect error code set for - * failed IO calls and mono_thread_info_uninstall_interrupt overwrites value */ - last_error = mono_w32error_get_last (); - mono_thread_info_uninstall_interrupt (&interrupted); - mono_w32error_set_last (last_error); - } + mono_win32_enter_blocking_io_call (info, (HANDLE)handle); + MONO_ENTER_GC_SAFE; + res = WriteFile ((HANDLE)handle, buffer, numbytes, (PDWORD)byteswritten, NULL); + /* need to save and restore since clients expect error code set for + * failed IO calls and mono_thread_info_uninstall_interrupt overwrites value */ + last_error = mono_w32error_get_last (); + MONO_EXIT_GC_SAFE; + mono_win32_leave_blocking_io_call (info, (HANDLE)handle); + mono_w32error_set_last (last_error); return res; } diff --git a/mono/metadata/w32socket-win32.c b/mono/metadata/w32socket-win32.c index ff89617d270d..8c4b55c3aebd 100644 --- a/mono/metadata/w32socket-win32.c +++ b/mono/metadata/w32socket-win32.c @@ -37,280 +37,144 @@ mono_w32socket_cleanup (void) { } -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 mono_win32_wsa_wait_for_multiple_events () on socket %d", GetCurrentThreadId (), sock)); - DWORD ret = mono_win32_wsa_wait_for_multiple_events (1, &event, TRUE, timeout, TRUE); - if (ret == WSA_WAIT_IO_COMPLETION) { - LOGDEBUG (g_message ("%06d - mono_win32_wsa_wait_for_multiple_events () 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); +#define INTERRUPTABLE_SOCKET_CALL(blocking, ret, op, sock, ...) \ + MonoThreadInfo *info = mono_thread_info_current (); \ + if (blocking) \ + mono_win32_enter_blocking_io_call (info, (HANDLE)sock); \ + MONO_ENTER_GC_SAFE; \ + ret = op (sock, __VA_ARGS__); \ + MONO_EXIT_GC_SAFE; \ + if (blocking) \ + mono_win32_leave_blocking_io_call (info, (HANDLE)sock); SOCKET mono_w32socket_accept (SOCKET s, struct sockaddr *addr, socklen_t *addrlen, gboolean blocking) { - SOCKET newsock = INVALID_SOCKET; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_ACCEPT_BIT, blocking, TRUE, newsock, accept, s, addr, addrlen); - MONO_EXIT_GC_SAFE; - return newsock; + SOCKET ret = INVALID_SOCKET; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, accept, s, addr, addrlen); + return ret; } int mono_w32socket_connect (SOCKET s, const struct sockaddr *name, int namelen, gboolean blocking) { int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_CONNECT_BIT, blocking, FALSE, ret, connect, s, name, namelen); - ret = WSAGetLastError () != 0 ? SOCKET_ERROR : 0; - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, connect, s, name, namelen); return ret; } int mono_w32socket_recv (SOCKET s, char *buf, int len, int flags, gboolean blocking) { - MonoInternalThread *curthread = mono_thread_internal_current (); int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_READ_BIT, blocking, TRUE, ret, recv, s, buf, len, flags); - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, recv, s, buf, len, flags); return ret; } int mono_w32socket_recvfrom (SOCKET s, char *buf, int len, int flags, struct sockaddr *from, socklen_t *fromlen, gboolean blocking) { int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_READ_BIT, blocking, TRUE, ret, recvfrom, s, buf, len, flags, from, fromlen); - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, recvfrom, s, buf, len, flags, from, fromlen); return ret; } int mono_w32socket_recvbuffers (SOCKET s, WSABUF *lpBuffers, guint32 dwBufferCount, guint32 *lpNumberOfBytesRecvd, guint32 *lpFlags, gpointer lpOverlapped, gpointer lpCompletionRoutine, gboolean blocking) { int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_READ_BIT, blocking, TRUE, ret, WSARecv, s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine); - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, WSARecv, s, lpBuffers, dwBufferCount, (LPDWORD)lpNumberOfBytesRecvd, (LPDWORD)lpFlags, (LPWSAOVERLAPPED)lpOverlapped, (LPWSAOVERLAPPED_COMPLETION_ROUTINE)lpCompletionRoutine); return ret; } int mono_w32socket_send (SOCKET s, char *buf, int len, int flags, gboolean blocking) { int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_WRITE_BIT, blocking, TRUE, ret, send, s, buf, len, flags); - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, send, s, (const char *)buf, len, flags); return ret; } int mono_w32socket_sendto (SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen, gboolean blocking) { int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_WRITE_BIT, blocking, TRUE, ret, sendto, s, buf, len, flags, to, tolen); - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, sendto, s, buf, len, flags, to, tolen); return ret; } int mono_w32socket_sendbuffers (SOCKET s, WSABUF *lpBuffers, guint32 dwBufferCount, guint32 *lpNumberOfBytesRecvd, guint32 lpFlags, gpointer lpOverlapped, gpointer lpCompletionRoutine, gboolean blocking) { int ret = SOCKET_ERROR; - MONO_ENTER_GC_SAFE; - ALERTABLE_SOCKET_CALL (FD_WRITE_BIT, blocking, TRUE, ret, WSASend, s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine); - MONO_EXIT_GC_SAFE; + INTERRUPTABLE_SOCKET_CALL (blocking, ret, WSASend, s, lpBuffers, dwBufferCount, (LPDWORD)lpNumberOfBytesRecvd, lpFlags, (LPWSAOVERLAPPED)lpOverlapped, (LPWSAOVERLAPPED_COMPLETION_ROUTINE)lpCompletionRoutine); return ret; } #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT | HAVE_UWP_WINAPI_SUPPORT) -BOOL mono_w32socket_transmit_file (SOCKET hSocket, gpointer hFile, TRANSMIT_FILE_BUFFERS *lpTransmitBuffers, guint32 dwReserved, gboolean blocking) +static gint +internal_w32socket_transmit_file (SOCKET sock, gpointer file, TRANSMIT_FILE_BUFFERS *lpTransmitBuffers, guint32 dwReserved, gboolean blocking) { - LOGDEBUG (g_message ("%06d - Performing %s TransmitFile () on socket %d", GetCurrentThreadId (), blocking ? "blocking" : "non-blocking", hSocket)); + gint ret = ERROR_NOT_SUPPORTED; + LPFN_TRANSMITFILE transmit_file; + GUID transmit_file_guid = WSAID_TRANSMITFILE; + DWORD output_bytes; - int error = 0, ret; + if (!WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &transmit_file_guid, sizeof (GUID), &transmit_file, sizeof (LPFN_TRANSMITFILE), &output_bytes, NULL, NULL)) { + MonoThreadInfo *info = mono_thread_info_current (); - MONO_ENTER_GC_SAFE; + if (blocking) + mono_win32_enter_blocking_io_call (info, (HANDLE)sock); - if (blocking) { - OVERLAPPED overlapped = { 0 }; - overlapped.hEvent = WSACreateEvent (); - if (overlapped.hEvent == WSA_INVALID_EVENT) { - ret = FALSE; - goto done; - } - if (!TransmitFile (hSocket, hFile, 0, 0, &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 = mono_win32_wait_for_single_object_ex (overlapped.hEvent, INFINITE, TRUE); - if (ret == WAIT_IO_COMPLETION) { - LOGDEBUG (g_message ("%06d - mono_win32_wait_for_single_object_ex () 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, 0, 0, NULL, lpTransmitBuffers, dwReserved)) { - error = WSAGetLastError (); - } - } + MONO_ENTER_GC_SAFE; + if (transmit_file (sock, file, 0, 0, NULL, lpTransmitBuffers, dwReserved)) + ret = 0; + MONO_EXIT_GC_SAFE; - 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); + if (blocking) + mono_win32_leave_blocking_io_call (info, (HANDLE)sock); + } - ret = error == 0; + if (ret != 0) + ret = WSAGetLastError (); -done: - MONO_EXIT_GC_SAFE; return ret; } -#endif /* #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT | HAVE_UWP_WINAPI_SUPPORT) */ -#if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT | HAVE_UWP_WINAPI_SUPPORT) -gint -mono_w32socket_disconnect (SOCKET sock, gboolean reuse) +static gint +internal_w32socket_disconnect (SOCKET sock, gboolean reuse, gboolean blocking) { + gint ret = ERROR_NOT_SUPPORTED; LPFN_DISCONNECTEX disconnect; - LPFN_TRANSMITFILE transmit_file; + GUID disconnect_guid = WSAID_DISCONNECTEX; DWORD output_bytes; - gint ret; - MONO_ENTER_GC_SAFE; + if (!WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &disconnect_guid, sizeof (GUID), &disconnect, sizeof (LPFN_DISCONNECTEX), &output_bytes, NULL, NULL)) { + MonoThreadInfo *info = mono_thread_info_current (); - /* Use the SIO_GET_EXTENSION_FUNCTION_POINTER to determine - * the address of the disconnect method without taking - * a hard dependency on a single provider - * - * For an explanation of why this is done, you can read the - * article at http://www.codeproject.com/internet/jbsocketserver3.asp - * - * I _think_ the extension function pointers need to be looked - * up for each socket. - * - * FIXME: check the best way to store pointers to functions in - * managed objects that still works on 64bit platforms. */ + if (blocking) + mono_win32_enter_blocking_io_call (info, (HANDLE)sock); + MONO_ENTER_GC_SAFE; + if (disconnect (sock, NULL, reuse ? TF_REUSE_SOCKET : 0, 0)) + ret = 0; + MONO_EXIT_GC_SAFE; - GUID disconnect_guid = WSAID_DISCONNECTEX; - ret = WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &disconnect_guid, sizeof (GUID), &disconnect, sizeof (LPFN_DISCONNECTEX), &output_bytes, NULL, NULL); - if (ret == 0) { - if (!disconnect (sock, NULL, reuse ? TF_REUSE_SOCKET : 0, 0)) { - ret = WSAGetLastError (); - goto done; - } - - ret = 0; - goto done; + if (blocking) + mono_win32_leave_blocking_io_call (info, (HANDLE)sock); } - GUID transmit_file_guid = WSAID_TRANSMITFILE; - ret = WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &transmit_file_guid, sizeof (GUID), &transmit_file, sizeof (LPFN_TRANSMITFILE), &output_bytes, NULL, NULL); - if (ret == 0) { - if (!transmit_file (sock, NULL, 0, 0, NULL, NULL, TF_DISCONNECT | (reuse ? TF_REUSE_SOCKET : 0))) { - ret = WSAGetLastError (); - goto done; - } - - ret = 0; - goto done; - } + if (ret != 0) + ret = WSAGetLastError (); - ret = ERROR_NOT_SUPPORTED; + return ret; +} + +BOOL mono_w32socket_transmit_file (SOCKET hSocket, gpointer hFile, TRANSMIT_FILE_BUFFERS *lpTransmitBuffers, guint32 dwReserved, gboolean blocking) +{ + return internal_w32socket_transmit_file (hSocket, hFile, lpTransmitBuffers, dwReserved, blocking) == 0 ? TRUE : FALSE; +} + +gint +mono_w32socket_disconnect (SOCKET sock, gboolean reuse) +{ + gint ret = SOCKET_ERROR; + + ret = internal_w32socket_disconnect (sock, reuse, TRUE); + if (ret == 0) + ret = internal_w32socket_transmit_file (sock, NULL, NULL, TF_DISCONNECT | (reuse ? TF_REUSE_SOCKET : 0), TRUE); -done: - MONO_EXIT_GC_SAFE; return ret; } #endif /* #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT | HAVE_UWP_WINAPI_SUPPORT) */ diff --git a/mono/utils/mono-os-wait-win32.c b/mono/utils/mono-os-wait-win32.c index 9f84a34da905..6788da5fd205 100644 --- a/mono/utils/mono-os-wait-win32.c +++ b/mono/utils/mono-os-wait-win32.c @@ -11,83 +11,7 @@ #include #include #include - -enum ThreadWaitInfo { - THREAD_WAIT_INFO_CLEARED = 0, - THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT = 1 << 0, - THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT = 1 << 1, - THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT = 1 << 2 -}; - -static inline void -request_interrupt (gpointer thread_info, HANDLE native_thread_handle, gint32 pending_apc_slot, PAPCFUNC apc_callback, DWORD tid) -{ - /* - * On Windows platforms, an async interrupt/abort request queues an APC - * that needs to be processed by target thread before it can return from an - * alertable OS wait call and complete the mono interrupt/abort request. - * Uncontrolled queuing of APC's could flood the APC queue preventing the target thread - * to return from its alertable OS wait call, blocking the interrupt/abort requests to complete - * This check makes sure that only one APC per type gets queued, preventing potential flooding - * of the APC queue. NOTE, this code will execute regardless if targeted thread is currently in - * an alertable wait or not. This is done to prevent races between interrupt/abort requests and - * alertable wait calls. Threads already in an alertable wait should handle WAIT_IO_COMPLETION - * return scenarios and restart the alertable wait operation if needed or take other actions - * (like service the interrupt/abort request). - */ - MonoThreadInfo *info = (MonoThreadInfo *)thread_info; - gint32 old_wait_info, new_wait_info; - - do { - old_wait_info = mono_atomic_load_i32 (&info->thread_wait_info); - if (old_wait_info & pending_apc_slot) - return; - - new_wait_info = old_wait_info | pending_apc_slot; - } while (mono_atomic_cas_i32 (&info->thread_wait_info, new_wait_info, old_wait_info) != old_wait_info); - - THREADS_INTERRUPT_DEBUG ("%06d - Interrupting/Aborting syscall in thread %06d", GetCurrentThreadId (), tid); - QueueUserAPC (apc_callback, native_thread_handle, (ULONG_PTR)NULL); -} - -static void CALLBACK -interrupt_apc (ULONG_PTR param) -{ - THREADS_INTERRUPT_DEBUG ("%06d - interrupt_apc () called", GetCurrentThreadId ()); -} - -void -mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid) -{ - request_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT, interrupt_apc, tid); -} - -static void CALLBACK -abort_apc (ULONG_PTR param) -{ - THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ()); -} - -void -mono_win32_abort_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid) -{ - request_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT, abort_apc, tid); -} - -static inline void -enter_alertable_wait (MonoThreadInfo *info) -{ - // Clear any previous flags. Set alertable wait flag. - mono_atomic_xchg_i32 (&info->thread_wait_info, THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT); -} - -static inline void -leave_alertable_wait (MonoThreadInfo *info) -{ - // Clear any previous flags. Thread is exiting alertable wait state, and info around pending interrupt/abort APC's - // can now be discarded as well, thread is out of wait operation and can proceed it's execution. - mono_atomic_xchg_i32 (&info->thread_wait_info, THREAD_WAIT_INFO_CLEARED); -} +#include "mono-logger-internals.h" DWORD mono_win32_sleep_ex (DWORD timeout, BOOL alertable) @@ -95,17 +19,13 @@ mono_win32_sleep_ex (DWORD timeout, BOOL alertable) DWORD result = WAIT_FAILED; MonoThreadInfo *info = mono_thread_info_current_unchecked (); - if (alertable && info) { - enter_alertable_wait (info); - } + if (info) + mono_win32_enter_alertable_wait (info); result = SleepEx (timeout, alertable); - // NOTE, leave_alertable_wait should not affect GetLastError but - // if changed, GetLastError needs to be preserved and reset before returning. - if (alertable && info) { - leave_alertable_wait (info); - } + if (info) + mono_win32_leave_alertable_wait (info); return result; } @@ -116,17 +36,13 @@ mono_win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertab DWORD result = WAIT_FAILED; MonoThreadInfo *info = mono_thread_info_current_unchecked (); - if (alertable && info) { - enter_alertable_wait (info); - } + if (info) + mono_win32_enter_alertable_wait (info); result = WaitForSingleObjectEx (handle, timeout, alertable); - // NOTE, leave_alertable_wait should not affect GetLastError but - // if changed, GetLastError needs to be preserved and reset before returning. - if (alertable && info) { - leave_alertable_wait (info); - } + if (info) + mono_win32_leave_alertable_wait (info); return result; } @@ -137,16 +53,32 @@ mono_win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOO DWORD result = WAIT_FAILED; MonoThreadInfo *info = mono_thread_info_current_unchecked (); - if (alertable && info) { - enter_alertable_wait (info); - } + if (info) + mono_win32_enter_alertable_wait (info); result = WaitForMultipleObjectsEx (count, handles, waitAll, timeout, alertable); - // NOTE, leave_alertable_wait should not affect GetLastError but - // if changed, GetLastError needs to be preserved and reset before returning. - if (alertable && info) { - leave_alertable_wait (info); + if (info) + mono_win32_leave_alertable_wait (info); + + // This is not perfect, but it is the best you can do in usermode and matches CoreCLR. + // i.e. handle-based instead of object-based. + + if (result == WAIT_FAILED && waitAll && + count > 1 && count <= MAXIMUM_WAIT_OBJECTS + && GetLastError () == ERROR_INVALID_PARAMETER) { + gpointer handles_sorted [MAXIMUM_WAIT_OBJECTS]; // 64 + memcpy (handles_sorted, handles, count * sizeof (handles [0])); + qsort (handles_sorted, count, sizeof (handles_sorted [0]), g_direct_equal); + for (DWORD i = 1; i < count; ++i) { + if (handles_sorted [i - 1] == handles_sorted [i]) { + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_IO_LAYER_HANDLE, "%s: handle %p is duplicated", __func__, handles_sorted [i]); + // Preserve LastError, but reduce triggering write breakpoints. + if (GetLastError () != ERROR_INVALID_PARAMETER) + SetLastError (ERROR_INVALID_PARAMETER); + break; + } + } } return result; @@ -161,17 +93,13 @@ mono_win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout DWORD result = WAIT_FAILED; MonoThreadInfo *info = mono_thread_info_current_unchecked (); - if (alertable && info) { - enter_alertable_wait (info); - } + if (info) + mono_win32_enter_alertable_wait (info); result = SignalObjectAndWait (toSignal, toWait, timeout, alertable); - // NOTE, leave_alertable_wait should not affect GetLastError but - // if changed, GetLastError needs to be preserved and reset before returning. - if (alertable && info) { - leave_alertable_wait (info); - } + if (info) + mono_win32_leave_alertable_wait (info); return result; } @@ -186,17 +114,13 @@ mono_win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, MonoThreadInfo *info = mono_thread_info_current_unchecked (); BOOL alertable = flags & MWMO_ALERTABLE; - if (alertable && info) { - enter_alertable_wait (info); - } + if (info) + mono_win32_enter_alertable_wait (info); result = MsgWaitForMultipleObjectsEx (count, handles, timeout, wakeMask, flags); - // NOTE, leave_alertable_wait should not affect GetLastError but - // if changed, GetLastError needs to be preserved and reset before returning. - if (alertable && info) { - leave_alertable_wait (info); - } + if (info) + mono_win32_leave_alertable_wait (info); return result; } @@ -208,17 +132,13 @@ mono_win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handle DWORD result = WAIT_FAILED; MonoThreadInfo *info = mono_thread_info_current_unchecked (); - if (alertable && info) { - enter_alertable_wait (info); - } + if (info) + mono_win32_enter_alertable_wait (info); result = WSAWaitForMultipleEvents (count, handles, waitAll, timeout, alertable); - // NOTE, leave_alertable_wait should not affect GetLastError but - // if changed, GetLastError needs to be preserved and reset before returning. - if (alertable && info) { - leave_alertable_wait (info); - } + if (info) + mono_win32_leave_alertable_wait (info); return result; } diff --git a/mono/utils/mono-os-wait.h b/mono/utils/mono-os-wait.h index 566ed9757dcd..4edb17a22156 100644 --- a/mono/utils/mono-os-wait.h +++ b/mono/utils/mono-os-wait.h @@ -29,12 +29,6 @@ mono_win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, DWORD mono_win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable); -void -mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid); - -void -mono_win32_abort_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid); - #endif #endif /* _MONO_UTILS_OS_WAIT_H_ */ diff --git a/mono/utils/mono-threads-windows.c b/mono/utils/mono-threads-windows.c index 18e83f545c76..d7a47fce1037 100644 --- a/mono/utils/mono-threads-windows.c +++ b/mono/utils/mono-threads-windows.c @@ -17,6 +17,157 @@ #include #include +enum Win32APCInfo { + WIN32_APC_INFO_CLEARED = 0, + WIN32_APC_INFO_ALERTABLE_WAIT_SLOT = 1 << 0, + WIN32_APC_INFO_PENDING_INTERRUPT_SLOT = 1 << 1, + WIN32_APC_INFO_PENDING_ABORT_SLOT = 1 << 2 +}; + +static inline void +request_interrupt (gpointer thread_info, HANDLE native_thread_handle, gint32 pending_apc_slot, PAPCFUNC apc_callback, DWORD tid) +{ + /* + * On Windows platforms, an async interrupt/abort request queues an APC + * that needs to be processed by target thread before it can return from an + * alertable OS wait call and complete the mono interrupt/abort request. + * Uncontrolled queuing of APC's could flood the APC queue preventing the target thread + * to return from its alertable OS wait call, blocking the interrupt/abort requests to complete. + * This check makes sure that only one APC per type gets queued, preventing potential flooding + * of the APC queue. NOTE, this code will execute regardless if targeted thread is currently in + * an alertable wait or not. This is done to prevent races between interrupt/abort requests and + * alertable wait calls. Threads already in an alertable wait should handle WAIT_IO_COMPLETION + * return scenarios and restart the alertable wait operation if needed or take other actions + * (like service the interrupt/abort request). + */ + MonoThreadInfo *info = (MonoThreadInfo *)thread_info; + gint32 old_apc_info, new_apc_info; + + do { + old_apc_info = mono_atomic_load_i32 (&info->win32_apc_info); + if (old_apc_info & pending_apc_slot) + return; + + new_apc_info = old_apc_info | pending_apc_slot; + } while (mono_atomic_cas_i32 (&info->win32_apc_info, new_apc_info, old_apc_info) != old_apc_info); + + THREADS_INTERRUPT_DEBUG ("%06d - Interrupting/Aborting syscall in thread %06d", GetCurrentThreadId (), tid); + QueueUserAPC (apc_callback, native_thread_handle, (ULONG_PTR)NULL); +} + +static void CALLBACK +interrupt_apc (ULONG_PTR param) +{ + THREADS_INTERRUPT_DEBUG ("%06d - interrupt_apc () called", GetCurrentThreadId ()); +} + +void +mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid) +{ + request_interrupt (thread_info, native_thread_handle, WIN32_APC_INFO_PENDING_INTERRUPT_SLOT, interrupt_apc, tid); +} + +static void CALLBACK +abort_apc (ULONG_PTR param) +{ + THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ()); + + MonoThreadInfo *info = mono_thread_info_current_unchecked (); + if (info) { + // Check if pending interrupt is still relevant and current thread has not left alertable wait region. + // NOTE, can only be reset by current thread, currently running this APC. + gint32 win32_apc_info = mono_atomic_load_i32 (&info->win32_apc_info); + if (win32_apc_info & WIN32_APC_INFO_PENDING_ABORT_SLOT) { + // Check if current thread registered an IO handle when entering alertable wait (blocking IO call). + // No need for CAS on win32_apc_info_io_handle since its only loaded/stored by current thread + // currently running APC. + HANDLE io_handle = (HANDLE)info->win32_apc_info_io_handle; + if (io_handle != INVALID_HANDLE_VALUE) { + // In order to break IO waits, cancel all outstanding IO requests. + // Start to cancel IO requests for the registered IO handle issued by current thread. + // NOTE, this is NOT a blocking call. + CancelIo (io_handle); + } + } + } +} + +// Attempt to cancel sync blocking IO on abort syscall requests. +// NOTE, the effect of the canceled IO operation is unknown so the caller need +// to close used resources (file, socket) to get back to a known state. The need +// to abort blocking IO calls is normally part of doing a thread abort, then the +// thread is going away meaning that no more IO calls will be issued against the +// same resource that was part of the cancelation. Current implementation of +// .NET Framework and .NET Core currently don't support the ability to abort a thread +// blocked on sync IO calls, see https://github.com/dotnet/corefx/issues/5749. +// Since there is no solution covering all scenarios aborting blocking syscall this +// will be on best effort and there might still be a slight risk that the blocking call +// won't abort (depending on overlapped IO support for current file, socket). +static void +suspend_abort_syscall (PVOID thread_info, HANDLE native_thread_handle, DWORD tid) +{ + request_interrupt (thread_info, native_thread_handle, WIN32_APC_INFO_PENDING_ABORT_SLOT, abort_apc, tid); + +#if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) + // In case thread is blocked on sync IO preventing it from running above queued APC, cancel + // all outputstanding sync IO for target thread. If its not blocked on a sync IO request, below + // call will just fail and nothing will be canceled. If thread is waiting on overlapped IO, + // the queued APC will take care of cancel specific outstanding IO requests. + CancelSynchronousIo (native_thread_handle); +#endif +} + +static inline void +enter_alertable_wait_ex (MonoThreadInfo *info, HANDLE io_handle) +{ + // Only loaded/stored by current thread, here or in APC (also running on current thread). + g_assert (info->win32_apc_info_io_handle == (gpointer)INVALID_HANDLE_VALUE); + info->win32_apc_info_io_handle = io_handle; + + //Set alertable wait flag. + mono_atomic_xchg_i32 (&info->win32_apc_info, WIN32_APC_INFO_ALERTABLE_WAIT_SLOT); +} + +static inline void +leave_alertable_wait_ex (MonoThreadInfo *info, HANDLE io_handle) +{ + // Clear any previous flags. Thread is exiting alertable wait region, and info around pending interrupt/abort APC's + // can now be discarded, thread is out of wait operation and can proceed execution. + mono_atomic_xchg_i32 (&info->win32_apc_info, WIN32_APC_INFO_CLEARED); + + // Only loaded/stored by current thread, here or in APC (also running on current thread). + g_assert (info->win32_apc_info_io_handle == io_handle); + info->win32_apc_info_io_handle = (gpointer)INVALID_HANDLE_VALUE; +} + +void +mono_win32_enter_alertable_wait (THREAD_INFO_TYPE *info) +{ + if (info) + enter_alertable_wait_ex (info, INVALID_HANDLE_VALUE); +} + +void +mono_win32_leave_alertable_wait (THREAD_INFO_TYPE *info) +{ + if (info) + leave_alertable_wait_ex (info, INVALID_HANDLE_VALUE); +} + +void +mono_win32_enter_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle) +{ + if (info) + enter_alertable_wait_ex (info, io_handle); +} + +void +mono_win32_leave_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle) +{ + if (info) + leave_alertable_wait_ex (info, io_handle); +} + void mono_threads_suspend_init (void) { @@ -63,7 +214,7 @@ mono_threads_suspend_begin_async_suspend (MonoThreadInfo *info, gboolean interru THREADS_SUSPEND_DEBUG ("thread state %p -> %d\n", (void*)id, res); if (info->suspend_can_continue) { if (interrupt_kernel) - mono_win32_interrupt_wait (info, handle, id); + suspend_abort_syscall (info, handle, id); } else { THREADS_SUSPEND_DEBUG ("FAILSAFE RESUME/2 %p -> %d\n", (void*)info->native_handle, 0); } @@ -77,14 +228,12 @@ mono_threads_suspend_check_suspend_result (MonoThreadInfo *info) return info->suspend_can_continue; } - - void mono_threads_suspend_abort_syscall (MonoThreadInfo *info) { DWORD id = mono_thread_info_get_tid(info); g_assert (info->native_handle); - mono_win32_abort_wait (info, info->native_handle, id); + suspend_abort_syscall (info, info->native_handle, id); } gboolean diff --git a/mono/utils/mono-threads.c b/mono/utils/mono-threads.c index bb4b730bbab5..def33049924f 100644 --- a/mono/utils/mono-threads.c +++ b/mono/utils/mono-threads.c @@ -397,6 +397,11 @@ register_thread (MonoThreadInfo *info) info->profiler_signal_ack = 1; +#ifdef USE_WINDOWS_BACKEND + info->win32_apc_info = 0; + info->win32_apc_info_io_handle = INVALID_HANDLE_VALUE; +#endif + mono_threads_suspend_register (info); THREADS_DEBUG ("registering info %p tid %p small id %x\n", info, mono_thread_info_get_tid (info), info->small_id); diff --git a/mono/utils/mono-threads.h b/mono/utils/mono-threads.h index 4011d1b1cb48..63b240d830d4 100644 --- a/mono/utils/mono-threads.h +++ b/mono/utils/mono-threads.h @@ -234,7 +234,8 @@ typedef struct { gint32 thread_pending_native_join; #ifdef USE_WINDOWS_BACKEND - gint32 thread_wait_info; + gint32 win32_apc_info; + gpointer win32_apc_info_io_handle; #endif } MonoThreadInfo; @@ -658,4 +659,22 @@ typedef void (*background_job_cb)(void); void mono_threads_schedule_background_job (background_job_cb cb); #endif +#ifdef USE_WINDOWS_BACKEND + +void +mono_win32_enter_alertable_wait (THREAD_INFO_TYPE *info); + +void +mono_win32_leave_alertable_wait (THREAD_INFO_TYPE *info); + +void +mono_win32_enter_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle); + +void +mono_win32_leave_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle); + +void +mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid); +#endif + #endif /* __MONO_THREADS_H__ */