Merge pull request #3029 from lambdageek/dev/simplify-monoerror-threads
[mono.git] / mono / metadata / threads.c
index 7b554b0f4af825c3c00d8d12758e0973d523d1ec..50ac9158aae755b6e03de139dc42989f42aa5e38 100644 (file)
@@ -41,6 +41,8 @@
 #include <mono/utils/mono-tls.h>
 #include <mono/utils/atomic.h>
 #include <mono/utils/mono-memory-model.h>
+#include <mono/utils/mono-threads-coop.h>
+#include <mono/utils/mono-error-internals.h>
 
 #include <mono/metadata/gc-internals.h>
 #include <mono/metadata/reflection-internals.h>
@@ -79,15 +81,6 @@ extern int tkill (pid_t tid, int signal);
 #define LOCK_THREAD(thread) lock_thread((thread))
 #define UNLOCK_THREAD(thread) unlock_thread((thread))
 
-/* Provide this for systems with glib < 2.6 */
-#ifndef G_GSIZE_FORMAT
-#   if GLIB_SIZEOF_LONG == 8
-#       define G_GSIZE_FORMAT "lu"
-#   else
-#       define G_GSIZE_FORMAT "u"
-#   endif
-#endif
-
 typedef struct
 {
        guint32 (*func)(void *);
@@ -204,7 +197,7 @@ static void mono_init_static_data_info (StaticDataInfo *static_data);
 static guint32 mono_alloc_static_data_slot (StaticDataInfo *static_data, guint32 size, guint32 align);
 static gboolean mono_thread_resume (MonoInternalThread* thread);
 static void async_abort_internal (MonoInternalThread *thread, gboolean install_async_abort);
-static void self_abort_internal (void);
+static void self_abort_internal (MonoError *error);
 static void async_suspend_internal (MonoInternalThread *thread, gboolean interrupt);
 static void self_suspend_internal (void);
 
@@ -257,8 +250,14 @@ mono_thread_get_tls_key (void)
 gint32
 mono_thread_get_tls_offset (void)
 {
-       int offset;
+       int offset = -1;
+
+#ifdef HOST_WIN32
+       if (current_object_key)
+               offset = current_object_key;
+#else
        MONO_THREAD_VAR_OFFSET (tls_current_object,offset);
+#endif
        return offset;
 }
 
@@ -575,21 +574,22 @@ set_current_thread_for_domain (MonoDomain *domain, MonoInternalThread *thread, M
 }
 
 static MonoThread*
-create_thread_object (MonoDomain *domain, MonoError *error)
+create_thread_object (MonoDomain *domain)
 {
+       MonoError error;
        MonoVTable *vt = mono_class_vtable (domain, mono_defaults.thread_class);
-       MonoThread *t = (MonoThread*)mono_object_new_mature (vt, error);
+       MonoThread *t = (MonoThread*)mono_object_new_mature (vt, &error);
+       /* only possible failure mode is OOM, from which we don't expect to recover. */
+       mono_error_assert_ok (&error);
        return t;
 }
 
 static MonoThread*
-new_thread_with_internal (MonoDomain *domain, MonoInternalThread *internal, MonoError *error)
+new_thread_with_internal (MonoDomain *domain, MonoInternalThread *internal)
 {
        MonoThread *thread;
 
-       thread = create_thread_object (domain, error);
-       if (!mono_error_ok (error))
-               return NULL;
+       thread = create_thread_object (domain);
 
        MONO_OBJECT_SETREF (thread, internal_thread, internal);
 
@@ -597,15 +597,16 @@ new_thread_with_internal (MonoDomain *domain, MonoInternalThread *internal, Mono
 }
 
 static MonoInternalThread*
-create_internal_thread (MonoError *error)
+create_internal_thread ()
 {
+       MonoError error;
        MonoInternalThread *thread;
        MonoVTable *vt;
 
        vt = mono_class_vtable (mono_get_root_domain (), mono_defaults.internal_thread_class);
-       thread = (MonoInternalThread*) mono_object_new_mature (vt, error);
-       if (!mono_error_ok (error))
-               return NULL;
+       thread = (MonoInternalThread*) mono_object_new_mature (vt, &error);
+       /* only possible failure mode is OOM, from which we don't exect to recover */
+       mono_error_assert_ok (&error);
 
        thread->synch_cs = g_new0 (MonoCoopMutex, 1);
        mono_coop_mutex_init_recursive (thread->synch_cs);
@@ -621,14 +622,12 @@ create_internal_thread (MonoError *error)
 }
 
 static gboolean
-init_root_domain_thread (MonoInternalThread *thread, MonoThread *candidate, MonoError *error)
+init_root_domain_thread (MonoInternalThread *thread, MonoThread *candidate)
 {
        MonoDomain *domain = mono_get_root_domain ();
 
-       mono_error_init (error);
        if (!candidate || candidate->obj.vtable->domain != domain) {
-               candidate = new_thread_with_internal (domain, thread, error);
-               return_val_if_nok (error, FALSE);
+               candidate = new_thread_with_internal (domain, thread);
        }
        set_current_thread_for_domain (domain, thread, candidate);
        g_assert (!thread->root_domain_thread);
@@ -687,8 +686,7 @@ static guint32 WINAPI start_wrapper_internal(void *data)
        /* We have to do this here because mono_thread_new_init()
           requires that root_domain_thread is set up. */
        thread_adjust_static_data (internal);
-       init_root_domain_thread (internal, start_info->obj, &error);
-       mono_error_raise_exception (&error); /* FIXME don't raise here */
+       init_root_domain_thread (internal, start_info->obj);
 
        /* This MUST be called before any managed code can be
         * executed, as it calls the callback function that (for the
@@ -744,7 +742,8 @@ static guint32 WINAPI start_wrapper_internal(void *data)
                g_assert (start_delegate != NULL);
                args [0] = start_arg;
                /* we may want to handle the exception here. See comment below on unhandled exceptions */
-               mono_runtime_delegate_invoke (start_delegate, args, NULL);
+               mono_runtime_delegate_invoke_checked (start_delegate, args, &error);
+               mono_error_raise_exception (&error); /* FIXME don't raise here */
        }
 
        /* If the thread calls ExitThread at all, this remaining code
@@ -927,11 +926,9 @@ mono_thread_create_internal (MonoDomain *domain, gpointer func, gpointer arg, gb
 
        mono_error_init (error);
 
-       thread = create_thread_object (domain, error);
-       return_val_if_nok (error, NULL);
+       thread = create_thread_object (domain);
 
-       internal = create_internal_thread (error);
-       return_val_if_nok (error, NULL);
+       internal = create_internal_thread ();
 
        MONO_OBJECT_SETREF (thread, internal_thread, internal);
 
@@ -969,15 +966,13 @@ mono_thread_create_checked (MonoDomain *domain, gpointer func, gpointer arg, Mon
 MonoThread *
 mono_thread_attach (MonoDomain *domain)
 {
-       MonoError error;
-       MonoThread *thread = mono_thread_attach_full (domain, FALSE, &error);
-       mono_error_raise_exception (&error);
+       MonoThread *thread = mono_thread_attach_full (domain, FALSE);
 
        return thread;
 }
 
 MonoThread *
-mono_thread_attach_full (MonoDomain *domain, gboolean force_attach, MonoError *error)
+mono_thread_attach_full (MonoDomain *domain, gboolean force_attach)
 {
        MonoThreadInfo *info;
        MonoInternalThread *thread;
@@ -985,8 +980,6 @@ mono_thread_attach_full (MonoDomain *domain, gboolean force_attach, MonoError *e
        HANDLE thread_handle;
        MonoNativeThreadId tid;
 
-       mono_error_init (error);
-
        if ((thread = mono_thread_internal_current ())) {
                if (domain != mono_domain_get ())
                        mono_domain_set (domain, TRUE);
@@ -998,9 +991,7 @@ mono_thread_attach_full (MonoDomain *domain, gboolean force_attach, MonoError *e
                g_error ("Thread %"G_GSIZE_FORMAT" calling into managed code is not registered with the GC. On UNIX, this can be fixed by #include-ing <gc.h> before <pthread.h> in the file containing the thread creation code.", mono_native_thread_id_get ());
        }
 
-       thread = create_internal_thread (error);
-       if (!mono_error_ok (error))
-               return NULL;
+       thread = create_internal_thread ();
 
        thread_handle = mono_thread_info_open_handle ();
        g_assert (thread_handle);
@@ -1018,9 +1009,7 @@ mono_thread_attach_full (MonoDomain *domain, gboolean force_attach, MonoError *e
        thread->thread_info = info;
        thread->small_id = info->small_id;
 
-       current_thread = new_thread_with_internal (domain, thread, error);
-       if (!mono_error_ok (error))
-               return NULL;
+       current_thread = new_thread_with_internal (domain, thread);
 
        if (!handle_store (current_thread, force_attach)) {
                /* Mono is shutting down, so just wait for the end */
@@ -1035,8 +1024,7 @@ mono_thread_attach_full (MonoDomain *domain, gboolean force_attach, MonoError *e
 
        thread_adjust_static_data (thread);
 
-       init_root_domain_thread (thread, current_thread, error);
-       return_val_if_nok (error, NULL);
+       init_root_domain_thread (thread, current_thread);
 
        if (domain != mono_get_root_domain ())
                set_current_thread_for_domain (domain, thread, current_thread);
@@ -1128,11 +1116,9 @@ mono_thread_exit ()
 void
 ves_icall_System_Threading_Thread_ConstructInternalThread (MonoThread *this_obj)
 {
-       MonoError error;
        MonoInternalThread *internal;
 
-       internal = create_internal_thread (&error);
-       mono_error_raise_exception (&error);
+       internal = create_internal_thread ();
 
        internal->state = ThreadState_Unstarted;
 
@@ -1242,8 +1228,7 @@ ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms)
                if (alerted) {
                        MonoException* exc = mono_thread_execute_interruption ();
                        if (exc) {
-                               mono_set_pending_exception (exc);
-                               return;
+                               mono_raise_exception (exc);
                        } else {
                                // FIXME: !INFINITE
                                if (ms != INFINITE)
@@ -1363,7 +1348,8 @@ ves_icall_System_Threading_Thread_GetName_internal (MonoInternalThread *this_obj
        
        UNLOCK_THREAD (this_obj);
 
-       mono_error_raise_exception (&error);
+       if (mono_error_set_pending_exception (&error))
+               return NULL;
        
        return str;
 }
@@ -1491,7 +1477,6 @@ ves_icall_System_Threading_Thread_ByteArrayToCurrentDomain (MonoArray *arr)
 MonoThread *
 mono_thread_current (void)
 {
-       MonoError error;
        MonoDomain *domain = mono_domain_get ();
        MonoInternalThread *internal = mono_thread_internal_current ();
        MonoThread **current_thread_ptr;
@@ -1501,8 +1486,7 @@ mono_thread_current (void)
 
        if (!*current_thread_ptr) {
                g_assert (domain != mono_get_root_domain ());
-               *current_thread_ptr = new_thread_with_internal (domain, internal, &error);
-               mono_error_raise_exception (&error); /* FIXME don't raise here */
+               *current_thread_ptr = new_thread_with_internal (domain, internal);
        }
        return *current_thread_ptr;
 }
@@ -1511,7 +1495,6 @@ mono_thread_current (void)
 static MonoThread *
 mono_thread_current_for_thread (MonoInternalThread *internal)
 {
-       MonoError error;
        MonoDomain *domain = mono_domain_get ();
        MonoThread **current_thread_ptr;
 
@@ -1520,8 +1503,7 @@ mono_thread_current_for_thread (MonoInternalThread *internal)
 
        if (!*current_thread_ptr) {
                g_assert (domain != mono_get_root_domain ());
-               *current_thread_ptr = new_thread_with_internal (domain, internal, &error);
-               mono_error_raise_exception (&error); /* FIXME don't raise here */
+               *current_thread_ptr = new_thread_with_internal (domain, internal);
        }
        return *current_thread_ptr;
 }
@@ -1580,6 +1562,15 @@ ves_icall_System_Threading_Thread_Join_internal(MonoThread *this_obj, int ms)
        return(FALSE);
 }
 
+#define MANAGED_WAIT_FAILED 0x7fffffff
+
+static gint32
+map_native_wait_result_to_managed (gint32 val)
+{
+       /* WAIT_FAILED in waithandle.cs is different from WAIT_FAILED in Win32 API */
+       return val == WAIT_FAILED ? MANAGED_WAIT_FAILED : val;
+}
+
 static gint32
 mono_wait_uninterrupted (MonoInternalThread *thread, guint32 numhandles, gpointer *handles, gboolean waitall, gint32 ms, MonoError *error)
 {
@@ -1636,7 +1627,7 @@ gint32 ves_icall_System_Threading_WaitHandle_WaitAll_internal(MonoArray *mono_ha
 
        /* Do this WaitSleepJoin check before creating objects */
        if (mono_thread_current_check_pending_interrupt ())
-               return WAIT_FAILED;
+               return map_native_wait_result_to_managed (WAIT_FAILED);
 
        /* We fail in managed if the array has more than 64 elements */
        numhandles = (guint32)mono_array_length(mono_handles);
@@ -1662,7 +1653,7 @@ gint32 ves_icall_System_Threading_WaitHandle_WaitAll_internal(MonoArray *mono_ha
        mono_error_set_pending_exception (&error);
 
        /* WAIT_FAILED in waithandle.cs is different from WAIT_FAILED in Win32 API */
-       return ret == WAIT_FAILED ? 0x7fffffff : ret;
+       return map_native_wait_result_to_managed (ret);
 }
 
 gint32 ves_icall_System_Threading_WaitHandle_WaitAny_internal(MonoArray *mono_handles, gint32 ms)
@@ -1677,11 +1668,11 @@ gint32 ves_icall_System_Threading_WaitHandle_WaitAny_internal(MonoArray *mono_ha
 
        /* Do this WaitSleepJoin check before creating objects */
        if (mono_thread_current_check_pending_interrupt ())
-               return WAIT_FAILED;
+               return map_native_wait_result_to_managed (WAIT_FAILED);
 
        numhandles = mono_array_length(mono_handles);
        if (numhandles > MAXIMUM_WAIT_OBJECTS)
-               return WAIT_FAILED;
+               return map_native_wait_result_to_managed (WAIT_FAILED);
 
        for(i = 0; i < numhandles; i++) {       
                waitHandle = mono_array_get(mono_handles, MonoObject*, i);
@@ -1705,14 +1696,14 @@ gint32 ves_icall_System_Threading_WaitHandle_WaitAny_internal(MonoArray *mono_ha
         * These need to be here.  See MSDN dos on WaitForMultipleObjects.
         */
        if (ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + numhandles - 1) {
-               return ret - WAIT_OBJECT_0;
+               return map_native_wait_result_to_managed (ret - WAIT_OBJECT_0);
        }
        else if (ret >= WAIT_ABANDONED_0 && ret <= WAIT_ABANDONED_0 + numhandles - 1) {
-               return ret - WAIT_ABANDONED_0;
+               return map_native_wait_result_to_managed (ret - WAIT_ABANDONED_0);
        }
        else {
                /* WAIT_FAILED in waithandle.cs is different from WAIT_FAILED in Win32 API */
-               return ret == WAIT_FAILED ? 0x7fffffff : ret;
+               return map_native_wait_result_to_managed (ret);
        }
 }
 
@@ -1729,7 +1720,7 @@ gint32 ves_icall_System_Threading_WaitHandle_WaitOne_internal(HANDLE handle, gin
        }
        
        if (mono_thread_current_check_pending_interrupt ())
-               return WAIT_FAILED;
+               return map_native_wait_result_to_managed (WAIT_FAILED);
 
        mono_thread_set_state (thread, ThreadState_WaitSleepJoin);
        
@@ -1738,8 +1729,7 @@ gint32 ves_icall_System_Threading_WaitHandle_WaitOne_internal(HANDLE handle, gin
        mono_thread_clr_state (thread, ThreadState_WaitSleepJoin);
 
        mono_error_set_pending_exception (&error);
-       /* WAIT_FAILED in waithandle.cs is different from WAIT_FAILED in Win32 API */
-       return ret == WAIT_FAILED ? 0x7fffffff : ret;
+       return map_native_wait_result_to_managed (ret);
 }
 
 gint32
@@ -1752,7 +1742,7 @@ ves_icall_System_Threading_WaitHandle_SignalAndWait_Internal (HANDLE toSignal, H
                ms = INFINITE;
 
        if (mono_thread_current_check_pending_interrupt ())
-               return WAIT_FAILED;
+               return map_native_wait_result_to_managed (WAIT_FAILED);
 
        mono_thread_set_state (thread, ThreadState_WaitSleepJoin);
        
@@ -1762,8 +1752,7 @@ ves_icall_System_Threading_WaitHandle_SignalAndWait_Internal (HANDLE toSignal, H
        
        mono_thread_clr_state (thread, ThreadState_WaitSleepJoin);
 
-       /* WAIT_FAILED in waithandle.cs is different from WAIT_FAILED in Win32 API */
-       return ret == WAIT_FAILED ? 0x7fffffff : ret;
+       return map_native_wait_result_to_managed (ret);
 }
 
 HANDLE ves_icall_System_Threading_Mutex_CreateMutex_internal (MonoBoolean owned, MonoString *name, MonoBoolean *created)
@@ -2221,8 +2210,8 @@ mono_thread_current_check_pending_interrupt (void)
        return throw_;
 }
 
-void
-ves_icall_System_Threading_Thread_Abort (MonoInternalThread *thread, MonoObject *state)
+static gboolean
+request_thread_abort (MonoInternalThread *thread, MonoObject *state)
 {
        LOCK_THREAD (thread);
        
@@ -2231,13 +2220,13 @@ ves_icall_System_Threading_Thread_Abort (MonoInternalThread *thread, MonoObject
                (thread->state & ThreadState_Stopped) != 0)
        {
                UNLOCK_THREAD (thread);
-               return;
+               return FALSE;
        }
 
        if ((thread->state & ThreadState_Unstarted) != 0) {
                thread->state |= ThreadState_Aborted;
                UNLOCK_THREAD (thread);
-               return;
+               return FALSE;
        }
 
        thread->state |= ThreadState_AbortRequested;
@@ -2259,11 +2248,39 @@ ves_icall_System_Threading_Thread_Abort (MonoInternalThread *thread, MonoObject
                mono_thread_resume (thread);
 
        UNLOCK_THREAD (thread);
+       return TRUE;
+}
 
-       if (thread == mono_thread_internal_current ())
-               self_abort_internal ();
-       else
+void
+ves_icall_System_Threading_Thread_Abort (MonoInternalThread *thread, MonoObject *state)
+{
+       if (!request_thread_abort (thread, state))
+               return;
+
+       if (thread == mono_thread_internal_current ()) {
+               MonoError error;
+               self_abort_internal (&error);
+               mono_error_set_pending_exception (&error);
+       } else {
                async_abort_internal (thread, TRUE);
+       }
+}
+
+/**
+ * mono_thread_internal_abort:
+ *
+ * Request thread @thread to be aborted.
+ *
+ * @thread MUST NOT be the current thread.
+ */
+void
+mono_thread_internal_abort (MonoInternalThread *thread)
+{
+       g_assert (thread != mono_thread_internal_current ());
+
+       if (!request_thread_abort (thread, NULL))
+               return;
+       async_abort_internal (thread, TRUE);
 }
 
 void
@@ -2463,7 +2480,8 @@ is_running_protected_wrapper (void)
        return found;
 }
 
-void mono_thread_internal_stop (MonoInternalThread *thread)
+static gboolean
+request_thread_stop (MonoInternalThread *thread)
 {
        LOCK_THREAD (thread);
 
@@ -2471,7 +2489,7 @@ void mono_thread_internal_stop (MonoInternalThread *thread)
                (thread->state & ThreadState_Stopped) != 0)
        {
                UNLOCK_THREAD (thread);
-               return;
+               return FALSE;
        }
        
        /* Make sure the thread is awake */
@@ -2481,16 +2499,45 @@ void mono_thread_internal_stop (MonoInternalThread *thread)
        thread->state &= ~ThreadState_AbortRequested;
        
        UNLOCK_THREAD (thread);
+       return TRUE;
+}
+
+/**
+ * mono_thread_internal_stop:
+ *
+ * Request thread @thread to stop.
+ *
+ * @thread MUST NOT be the current thread.
+ */
+void
+mono_thread_internal_stop (MonoInternalThread *thread)
+{
+       g_assert (thread != mono_thread_internal_current ());
+
+       if (!request_thread_stop (thread))
+               return;
        
-       if (thread == mono_thread_internal_current ())
-               self_abort_internal ();
-       else
-               async_abort_internal (thread, TRUE);
+       async_abort_internal (thread, TRUE);
 }
 
 void mono_thread_stop (MonoThread *thread)
 {
-       mono_thread_internal_stop (thread->internal_thread);
+       MonoInternalThread *internal = thread->internal_thread;
+
+       if (!request_thread_stop (internal))
+               return;
+       
+       if (internal == mono_thread_internal_current ()) {
+               MonoError error;
+               self_abort_internal (&error);
+               /*
+               This function is part of the embeding API and has no way to return the exception
+               to be thrown. So what we do is keep the old behavior and raise the exception.
+               */
+               mono_error_raise_exception (&error);
+       } else {
+               async_abort_internal (internal, TRUE);
+       }
 }
 
 gint8
@@ -3107,9 +3154,8 @@ remove_and_abort_threads (gpointer key, gpointer value, gpointer user)
                if (handle == NULL)
                        return FALSE;
 
-               /* printf ("A: %d\n", wait->num); */
-               wait->handles[wait->num]=thread->handle;
-               wait->threads[wait->num]=thread;
+               wait->handles[wait->num] = handle;
+               wait->threads[wait->num] = thread;
                wait->num++;
 
                THREAD_DEBUG (g_print ("%s: Aborting id: %"G_GSIZE_FORMAT"\n", __func__, (gsize)thread->tid));
@@ -3839,7 +3885,7 @@ mono_threads_abort_appdomain_threads (MonoDomain *domain, int timeout)
                if (user_data.wait.num > 0) {
                        /* Abort the threads outside the threads lock */
                        for (i = 0; i < user_data.wait.num; ++i)
-                               ves_icall_System_Threading_Thread_Abort (user_data.wait.threads [i], NULL);
+                               mono_thread_internal_abort (user_data.wait.threads [i]);
 
                        /*
                         * We should wait for the threads either to abort, or to leave the
@@ -4796,18 +4842,20 @@ async_abort_internal (MonoInternalThread *thread, gboolean install_async_abort)
 }
 
 static void
-self_abort_internal (void)
+self_abort_internal (MonoError *error)
 {
        MonoException *exc;
 
+       mono_error_init (error);
+
        /* FIXME this is insanely broken, it doesn't cause interruption to happen synchronously
         * since passing FALSE to mono_thread_request_interruption makes sure it returns NULL */
 
        exc = mono_thread_request_interruption (TRUE);
        if (exc)
-               mono_raise_exception (exc);
-
-       mono_thread_info_self_interrupt ();
+               mono_error_set_exception_instance (error, exc);
+       else
+               mono_thread_info_self_interrupt ();
 }
 
 typedef struct {
@@ -5032,3 +5080,101 @@ ves_icall_System_Threading_Thread_GetStackTraces (MonoArray **out_threads, MonoA
 {
        mono_threads_get_thread_dump (out_threads, out_stack_traces);
 }
+
+/*
+ * mono_threads_attach_coop: called by native->managed wrappers
+ *
+ * In non-coop mode:
+ *  - @dummy: is NULL
+ *  - @return: the original domain which needs to be restored, or NULL.
+ *
+ * In coop mode:
+ *  - @dummy: contains the original domain
+ *  - @return: a cookie containing current MonoThreadInfo*.
+ */
+gpointer
+mono_threads_attach_coop (MonoDomain *domain, gpointer *dummy)
+{
+       MonoDomain *orig;
+       gboolean fresh_thread;
+
+       if (!domain) {
+               /* Happens when called from AOTed code which is only used in the root domain. */
+               domain = mono_get_root_domain ();
+       }
+
+       g_assert (domain);
+
+       /* On coop, when we detached, we moved the thread from  RUNNING->BLOCKING.
+        * If we try to reattach we do a BLOCKING->RUNNING transition.  If the thread
+        * is fresh, mono_thread_attach() will do a STARTING->RUNNING transition so
+        * we're only responsible for making the cookie. */
+       if (mono_threads_is_coop_enabled ()) {
+               MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+               fresh_thread = !info || !mono_thread_info_is_live (info);
+       }
+
+       if (!mono_thread_internal_current ()) {
+               mono_thread_attach_full (domain, FALSE);
+
+               // #678164
+               mono_thread_set_state (mono_thread_internal_current (), ThreadState_Background);
+       }
+
+       orig = mono_domain_get ();
+       if (orig != domain)
+               mono_domain_set (domain, TRUE);
+
+       if (!mono_threads_is_coop_enabled ())
+               return orig != domain ? orig : NULL;
+
+       if (fresh_thread) {
+               *dummy = NULL;
+               /* mono_thread_attach put the thread in RUNNING mode from STARTING, but we need to
+                * return the right cookie. */
+               return mono_threads_enter_gc_unsafe_region_cookie (mono_thread_info_current ());
+       } else {
+               *dummy = orig;
+               /* thread state (BLOCKING|RUNNING) -> RUNNING */
+               return mono_threads_enter_gc_unsafe_region (dummy);
+       }
+}
+
+/*
+ * mono_threads_detach_coop: called by native->managed wrappers
+ *
+ * In non-coop mode:
+ *  - @cookie: the original domain which needs to be restored, or NULL.
+ *  - @dummy: is NULL
+ *
+ * In coop mode:
+ *  - @cookie: contains current MonoThreadInfo* if it was in BLOCKING mode, NULL otherwise
+ *  - @dummy: contains the original domain
+ */
+void
+mono_threads_detach_coop (gpointer cookie, gpointer *dummy)
+{
+       MonoDomain *domain, *orig;
+
+       if (!mono_threads_is_coop_enabled ()) {
+               orig = (MonoDomain*) cookie;
+               if (orig)
+                       mono_domain_set (orig, TRUE);
+       } else {
+               orig = (MonoDomain*) *dummy;
+
+               domain = mono_domain_get ();
+               g_assert (domain);
+
+               /* it won't do anything if cookie is NULL
+                * thread state RUNNING -> (RUNNING|BLOCKING) */
+               mono_threads_exit_gc_unsafe_region (cookie, dummy);
+
+               if (orig != domain) {
+                       if (!orig)
+                               mono_domain_unset ();
+                       else
+                               mono_domain_set (orig, TRUE);
+               }
+       }
+}