#include <mono/utils/mono-error-internals.h>
#include <mono/utils/os-event.h>
#include <mono/utils/mono-threads-debug.h>
+#include <mono/utils/unlocked.h>
#include <mono/metadata/w32handle.h>
#include <mono/metadata/w32event.h>
#include <mono/metadata/w32mutex.h>
#include <mono/metadata/abi-details.h>
#include <mono/metadata/w32error.h>
#include <mono/utils/w32api.h>
+#include <mono/utils/mono-os-wait.h>
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#include <objbase.h>
#endif
-#if defined(PLATFORM_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64)
+#if defined(HOST_ANDROID) && !defined(TARGET_ARM64) && !defined(TARGET_AMD64)
#define USE_TKILL_ON_ANDROID 1
#endif
-#ifdef PLATFORM_ANDROID
+#ifdef HOST_ANDROID
#include <errno.h>
#ifdef USE_TKILL_ON_ANDROID
/* Contains tids */
/* Protected by the threads lock */
static GHashTable *joinable_threads;
-static int joinable_thread_count;
+static gint32 joinable_thread_count;
#define SET_CURRENT_OBJECT(x) mono_tls_set_thread (x)
#define GET_CURRENT_OBJECT() (MonoInternalThread*) mono_tls_get_thread ()
static gint32 managed_thread_id_counter = 0;
-/* Class lazy loading functions */
-static GENERATE_GET_CLASS_WITH_CACHE (appdomain_unloaded_exception, "System", "AppDomainUnloadedException")
-
static void
mono_threads_lock (void)
{
#define SPECIAL_STATIC_OFFSET_TYPE_THREAD 0
#define SPECIAL_STATIC_OFFSET_TYPE_CONTEXT 1
-#define MAKE_SPECIAL_STATIC_OFFSET(index, offset, type) \
- ((SpecialStaticOffset) { .fields = { (index), (offset), (type) } }.raw)
+#define MAKE_SPECIAL_STATIC_OFFSET(idx, off, ty) \
+ ((SpecialStaticOffset) { .fields = { .index = (idx), .offset = (off), .type = (ty) } }.raw)
#define ACCESS_SPECIAL_STATIC_OFFSET(x,f) \
(((SpecialStaticOffset *) &(x))->fields.f)
return TRUE;
}
+static void
+mono_thread_detach_internal (MonoInternalThread *thread)
+{
+ gboolean removed;
+
+ g_assert (thread != NULL);
+ SET_CURRENT_OBJECT (thread);
+
+ THREAD_DEBUG (g_message ("%s: mono_thread_detach for %p (%"G_GSIZE_FORMAT")", __func__, thread, (gsize)thread->tid));
+
+#ifndef HOST_WIN32
+ mono_w32mutex_abandon ();
+#endif
+
+ if (thread->abort_state_handle) {
+ mono_gchandle_free (thread->abort_state_handle);
+ thread->abort_state_handle = 0;
+ }
+
+ thread->abort_exc = NULL;
+ thread->current_appcontext = NULL;
+
+ /*
+ * thread->synch_cs can be NULL if this was called after
+ * ves_icall_System_Threading_InternalThread_Thread_free_internal.
+ * This can happen only during shutdown.
+ * The shutting_down flag is not always set, so we can't assert on it.
+ */
+ if (thread->synch_cs)
+ LOCK_THREAD (thread);
+
+ thread->state |= ThreadState_Stopped;
+ thread->state &= ~ThreadState_Background;
+
+ if (thread->synch_cs)
+ UNLOCK_THREAD (thread);
+
+ /*
+ An interruption request has leaked to cleanup. Adjust the global counter.
+
+ This can happen is the abort source thread finds the abortee (this) thread
+ in unmanaged code. If this thread never trips back to managed code or check
+ the local flag it will be left set and positively unbalance the global counter.
+
+ Leaving the counter unbalanced will cause a performance degradation since all threads
+ will now keep checking their local flags all the time.
+ */
+ mono_thread_clear_interruption_requested (thread);
+
+ mono_threads_lock ();
+
+ if (!threads) {
+ removed = FALSE;
+ } else if (mono_g_hash_table_lookup (threads, (gpointer)thread->tid) != thread) {
+ /* We have to check whether the thread object for the
+ * tid is still the same in the table because the
+ * thread might have been destroyed and the tid reused
+ * in the meantime, in which case the tid would be in
+ * the table, but with another thread object.
+ */
+ removed = FALSE;
+ } else {
+ mono_g_hash_table_remove (threads, (gpointer)thread->tid);
+ removed = TRUE;
+ }
+
+ mono_threads_unlock ();
+
+ /* Don't close the handle here, wait for the object finalizer
+ * to do it. Otherwise, the following race condition applies:
+ *
+ * 1) Thread exits (and mono_thread_detach_internal() closes the handle)
+ *
+ * 2) Some other handle is reassigned the same slot
+ *
+ * 3) Another thread tries to join the first thread, and
+ * blocks waiting for the reassigned handle to be signalled
+ * (which might never happen). This is possible, because the
+ * thread calling Join() still has a reference to the first
+ * thread's object.
+ */
+
+ /* if the thread is not in the hash it has been removed already */
+ if (!removed) {
+ mono_domain_unset ();
+ mono_memory_barrier ();
+
+ if (mono_thread_cleanup_fn)
+ mono_thread_cleanup_fn (thread_get_tid (thread));
+
+ goto done;
+ }
+
+ mono_release_type_locks (thread);
+
+ /* Can happen when we attach the profiler helper thread in order to heapshot. */
+ if (!mono_thread_info_lookup (MONO_UINT_TO_NATIVE_THREAD_ID (thread->tid))->tools_thread)
+ MONO_PROFILER_RAISE (thread_stopped, (thread->tid));
+
+ mono_hazard_pointer_clear (mono_hazard_pointer_get (), 1);
+
+ /*
+ * This will signal async signal handlers that the thread has exited.
+ * The profiler callback needs this to be set, so it cannot be done earlier.
+ */
+ mono_domain_unset ();
+ mono_memory_barrier ();
+
+ if (thread == mono_thread_internal_current ())
+ mono_thread_pop_appdomain_ref ();
+
+ mono_free_static_data (thread->static_data);
+ thread->static_data = NULL;
+ ref_stack_destroy (thread->appdomain_refs);
+ thread->appdomain_refs = NULL;
+
+ g_assert (thread->suspended);
+ mono_os_event_destroy (thread->suspended);
+ g_free (thread->suspended);
+ thread->suspended = NULL;
+
+ if (mono_thread_cleanup_fn)
+ mono_thread_cleanup_fn (thread_get_tid (thread));
+
+ mono_memory_barrier ();
+
+ if (mono_gc_is_moving ()) {
+ MONO_GC_UNREGISTER_ROOT (thread->thread_pinning_ref);
+ thread->thread_pinning_ref = NULL;
+ }
+
+done:
+ SET_CURRENT_OBJECT (NULL);
+ mono_domain_unset ();
+
+ mono_thread_info_unset_internal_thread_gchandle ((MonoThreadInfo*) thread->thread_info);
+
+ /* Don't need to close the handle to this thread, even though we took a
+ * reference in mono_thread_attach (), because the GC will do it
+ * when the Thread object is finalised.
+ */
+}
+
typedef struct {
gint32 ref;
MonoThread *thread;
mono_thread_detach_internal (internal);
- internal->tid = 0;
-
return(0);
}
return mono_thread_attach_full (domain, FALSE);
}
-void
-mono_thread_detach_internal (MonoInternalThread *thread)
-{
- gboolean removed;
-
- g_assert (thread != NULL);
- SET_CURRENT_OBJECT (thread);
-
- THREAD_DEBUG (g_message ("%s: mono_thread_detach for %p (%"G_GSIZE_FORMAT")", __func__, thread, (gsize)thread->tid));
-
-#ifndef HOST_WIN32
- mono_w32mutex_abandon ();
-#endif
-
- if (thread->abort_state_handle) {
- mono_gchandle_free (thread->abort_state_handle);
- thread->abort_state_handle = 0;
- }
-
- thread->abort_exc = NULL;
- thread->current_appcontext = NULL;
-
- /*
- * thread->synch_cs can be NULL if this was called after
- * ves_icall_System_Threading_InternalThread_Thread_free_internal.
- * This can happen only during shutdown.
- * The shutting_down flag is not always set, so we can't assert on it.
- */
- if (thread->synch_cs)
- LOCK_THREAD (thread);
-
- thread->state |= ThreadState_Stopped;
- thread->state &= ~ThreadState_Background;
-
- if (thread->synch_cs)
- UNLOCK_THREAD (thread);
-
- /*
- An interruption request has leaked to cleanup. Adjust the global counter.
-
- This can happen is the abort source thread finds the abortee (this) thread
- in unmanaged code. If this thread never trips back to managed code or check
- the local flag it will be left set and positively unbalance the global counter.
-
- Leaving the counter unbalanced will cause a performance degradation since all threads
- will now keep checking their local flags all the time.
- */
- mono_thread_clear_interruption_requested (thread);
-
- mono_threads_lock ();
-
- if (!threads) {
- removed = FALSE;
- } else if (mono_g_hash_table_lookup (threads, (gpointer)thread->tid) != thread) {
- /* We have to check whether the thread object for the
- * tid is still the same in the table because the
- * thread might have been destroyed and the tid reused
- * in the meantime, in which case the tid would be in
- * the table, but with another thread object.
- */
- removed = FALSE;
- } else {
- mono_g_hash_table_remove (threads, (gpointer)thread->tid);
- removed = TRUE;
- }
-
- mono_threads_unlock ();
-
- /* Don't close the handle here, wait for the object finalizer
- * to do it. Otherwise, the following race condition applies:
- *
- * 1) Thread exits (and mono_thread_detach_internal() closes the handle)
- *
- * 2) Some other handle is reassigned the same slot
- *
- * 3) Another thread tries to join the first thread, and
- * blocks waiting for the reassigned handle to be signalled
- * (which might never happen). This is possible, because the
- * thread calling Join() still has a reference to the first
- * thread's object.
- */
-
- /* if the thread is not in the hash it has been removed already */
- if (!removed) {
- mono_domain_unset ();
- mono_memory_barrier ();
-
- if (mono_thread_cleanup_fn)
- mono_thread_cleanup_fn (thread_get_tid (thread));
-
- goto done;
- }
-
- mono_release_type_locks (thread);
-
- /* Can happen when we attach the profiler helper thread in order to heapshot. */
- if (!mono_thread_info_lookup (MONO_UINT_TO_NATIVE_THREAD_ID (thread->tid))->tools_thread)
- MONO_PROFILER_RAISE (thread_stopped, (thread->tid));
-
- mono_hazard_pointer_clear (mono_hazard_pointer_get (), 1);
-
- /*
- * This will signal async signal handlers that the thread has exited.
- * The profiler callback needs this to be set, so it cannot be done earlier.
- */
- mono_domain_unset ();
- mono_memory_barrier ();
-
- if (thread == mono_thread_internal_current ())
- mono_thread_pop_appdomain_ref ();
-
- mono_free_static_data (thread->static_data);
- thread->static_data = NULL;
- ref_stack_destroy (thread->appdomain_refs);
- thread->appdomain_refs = NULL;
-
- g_assert (thread->suspended);
- mono_os_event_destroy (thread->suspended);
- g_free (thread->suspended);
- thread->suspended = NULL;
-
- if (mono_thread_cleanup_fn)
- mono_thread_cleanup_fn (thread_get_tid (thread));
-
- mono_memory_barrier ();
-
- if (mono_gc_is_moving ()) {
- MONO_GC_UNREGISTER_ROOT (thread->thread_pinning_ref);
- thread->thread_pinning_ref = NULL;
- }
-
-done:
- SET_CURRENT_OBJECT (NULL);
- mono_domain_unset ();
-
- mono_thread_info_unset_internal_thread_gchandle ((MonoThreadInfo*) thread->thread_info);
-
- /* Don't need to close the handle to this thread, even though we took a
- * reference in mono_thread_attach (), because the GC will do it
- * when the Thread object is finalised.
- */
-}
-
/**
* mono_thread_detach:
*/
}
gboolean
-ves_icall_System_Threading_Thread_Join_internal(MonoThread *this_obj, int ms)
+ves_icall_System_Threading_Thread_Join_internal (MonoThread *this_obj, int ms)
{
MonoInternalThread *thread = this_obj->internal_thread;
MonoThreadHandle *handle = thread->handle;
return FALSE;
}
+ MonoNativeThreadId tid = thread_get_tid (thread);
+
UNLOCK_THREAD (thread);
- if(ms== -1) {
- ms=MONO_INFINITE_WAIT;
- }
+ if (ms == -1)
+ ms = MONO_INFINITE_WAIT;
THREAD_DEBUG (g_message ("%s: joining thread handle %p, %d ms", __func__, handle, ms));
-
+
mono_thread_set_state (cur_thread, ThreadState_WaitSleepJoin);
- ret=mono_join_uninterrupted (handle, ms, &error);
+ ret = mono_join_uninterrupted (handle, ms, &error);
mono_thread_clr_state (cur_thread, ThreadState_WaitSleepJoin);
mono_error_set_pending_exception (&error);
- if(ret==MONO_THREAD_INFO_WAIT_RET_SUCCESS_0) {
+ if (ret == MONO_THREAD_INFO_WAIT_RET_SUCCESS_0) {
THREAD_DEBUG (g_message ("%s: join successful", __func__));
- return(TRUE);
+#ifdef HOST_WIN32
+ /* TODO: Do this on Unix platforms as well. See PR #5454 for context. */
+ /* Wait for the thread to really exit */
+ MONO_ENTER_GC_SAFE;
+ /* This shouldn't block */
+ mono_threads_join_lock ();
+ mono_native_thread_join (tid);
+ mono_threads_join_unlock ();
+ MONO_EXIT_GC_SAFE;
+#endif
+
+ return TRUE;
}
THREAD_DEBUG (g_message ("%s: join failed", __func__));
- return(FALSE);
+ return FALSE;
}
#define MANAGED_WAIT_FAILED 0x7fffffff
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
if (numhandles != 1)
- ret = mono_w32handle_convert_wait_ret (WaitForMultipleObjectsEx (numhandles, handles, waitall, timeoutLeft, TRUE), numhandles);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_multiple_objects_ex(numhandles, handles, waitall, timeoutLeft, TRUE), numhandles);
else
- ret = mono_w32handle_convert_wait_ret (WaitForSingleObjectEx (handles [0], timeoutLeft, TRUE), 1);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_single_object_ex (handles [0], timeoutLeft, TRUE), 1);
#else
/* mono_w32handle_wait_multiple optimizes the case for numhandles == 1 */
ret = mono_w32handle_wait_multiple (handles, numhandles, waitall, timeoutLeft, TRUE);
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
- ret = mono_w32handle_convert_wait_ret (SignalObjectAndWait (toSignal, toWait, ms, TRUE), 1);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_signal_object_and_wait (toSignal, toWait, ms, TRUE), 1);
#else
ret = mono_w32handle_signal_and_wait (toSignal, toWait, ms, TRUE);
#endif
}
static gboolean
-request_thread_abort (MonoInternalThread *thread, MonoObject *state)
+request_thread_abort (MonoInternalThread *thread, MonoObject *state, gboolean appdomain_unload)
{
LOCK_THREAD (thread);
}
thread->state |= ThreadState_AbortRequested;
+ if (appdomain_unload)
+ thread->flags |= MONO_THREAD_FLAG_APPDOMAIN_ABORT;
+ else
+ thread->flags &= ~MONO_THREAD_FLAG_APPDOMAIN_ABORT;
+
if (thread->abort_state_handle)
mono_gchandle_free (thread->abort_state_handle);
if (state) {
void
ves_icall_System_Threading_Thread_Abort (MonoInternalThread *thread, MonoObject *state)
{
- if (!request_thread_abort (thread, state))
+ if (!request_thread_abort (thread, state, FALSE))
return;
if (thread == mono_thread_internal_current ()) {
* \p thread MUST NOT be the current thread.
*/
void
-mono_thread_internal_abort (MonoInternalThread *thread)
+mono_thread_internal_abort (MonoInternalThread *thread, gboolean appdomain_unload)
{
g_assert (thread != mono_thread_internal_current ());
- if (!request_thread_abort (thread, NULL))
+ if (!request_thread_abort (thread, NULL, appdomain_unload))
return;
async_abort_internal (thread, TRUE);
}
ves_icall_System_Threading_Thread_ResetAbort (MonoThread *this_obj)
{
MonoInternalThread *thread = mono_thread_internal_current ();
- gboolean was_aborting;
+ gboolean was_aborting, is_domain_abort;
LOCK_THREAD (thread);
was_aborting = thread->state & ThreadState_AbortRequested;
- thread->state &= ~ThreadState_AbortRequested;
+ is_domain_abort = thread->flags & MONO_THREAD_FLAG_APPDOMAIN_ABORT;
+
+ if (was_aborting && !is_domain_abort)
+ thread->state &= ~ThreadState_AbortRequested;
UNLOCK_THREAD (thread);
if (!was_aborting) {
const char *msg = "Unable to reset abort because no abort was requested";
mono_set_pending_exception (mono_get_exception_thread_state (msg));
return;
+ } else if (is_domain_abort) {
+ /* Silently ignore abort resets in unloading appdomains */
+ return;
}
mono_get_eh_callbacks ()->mono_clear_abort_threshold ();
{
MonoInternalThread *internal = thread->internal_thread;
- if (!request_thread_abort (internal, NULL))
+ if (!request_thread_abort (internal, NULL, FALSE))
return;
if (internal == mono_thread_internal_current ()) {
void
mono_thread_cleanup (void)
{
+ mono_threads_join_threads ();
+
#if !defined(RUN_IN_SUBTHREAD) && !defined(HOST_WIN32)
/* The main thread must abandon any held mutexes (particularly
* important for named mutexes as they are shared across
wait->num++;
THREAD_DEBUG (g_print ("%s: Aborting id: %"G_GSIZE_FORMAT"\n", __func__, (gsize)thread->tid));
- mono_thread_internal_abort (thread);
+ mono_thread_internal_abort (thread, FALSE);
}
return TRUE;
if (user_data.wait.num > 0) {
/* Abort the threads outside the threads lock */
for (i = 0; i < user_data.wait.num; ++i)
- mono_thread_internal_abort (user_data.wait.threads [i]);
+ mono_thread_internal_abort (user_data.wait.threads [i], TRUE);
/*
* We should wait for the threads either to abort, or to leave the
return TRUE;
}
+void
+mono_thread_self_abort (void)
+{
+ MonoError error;
+ self_abort_internal (&error);
+ mono_error_set_pending_exception (&error);
+}
+
/*
* mono_thread_get_undeniable_exception:
*
}
/* this will consume pending APC calls */
-#ifdef HOST_WIN32
- WaitForSingleObjectEx (GetCurrentThread(), 0, TRUE);
+#ifdef USE_WINDOWS_BACKEND
+ mono_win32_wait_for_single_object_ex (GetCurrentThread (), 0, TRUE);
#endif
+
/* Clear the interrupted flag of the thread so it can wait again */
mono_thread_info_clear_self_interrupt ();
/* this will awake the thread if it is in WaitForSingleObject
or similar */
- /* Our implementation of this function ignores the func argument */
-#ifdef HOST_WIN32
- QueueUserAPC ((PAPCFUNC)dummy_apc, thread->native_handle, (ULONG_PTR)NULL);
+#ifdef USE_WINDOWS_BACKEND
+ mono_win32_interrupt_wait (thread->thread_info, thread->native_handle, (DWORD)thread->tid);
#else
mono_thread_info_self_interrupt ();
#endif
if (!joinable_threads)
joinable_threads = g_hash_table_new (NULL, NULL);
g_hash_table_insert (joinable_threads, tid, tid);
- joinable_thread_count ++;
+ UnlockedIncrement (&joinable_thread_count);
joinable_threads_unlock ();
mono_gc_finalize_notify ();
gboolean found;
/* Fastpath */
- if (!joinable_thread_count)
+ if (!UnlockedRead (&joinable_thread_count))
return;
while (TRUE) {
g_hash_table_iter_next (&iter, &key, (void**)&tid);
thread = (pthread_t)tid;
g_hash_table_remove (joinable_threads, key);
- joinable_thread_count --;
+ UnlockedDecrement (&joinable_thread_count);
found = TRUE;
}
joinable_threads_unlock ();
joinable_threads = g_hash_table_new (NULL, NULL);
if (g_hash_table_lookup (joinable_threads, tid)) {
g_hash_table_remove (joinable_threads, tid);
- joinable_thread_count --;
+ UnlockedDecrement (&joinable_thread_count);
found = TRUE;
}
joinable_threads_unlock ();