[threads] Fix another sleep compilation error on win32
[mono.git] / mono / utils / mono-threads.c
index 781757f3d4edd8912006a2d66094a7c9d55cad61..1c1a1842eb2f9e5a965431547e4220a50eb3ec16 100644 (file)
 
 #include <config.h>
 
+/* enable pthread extensions */
+#ifdef TARGET_MACH
+#define _DARWIN_C_SOURCE
+#endif
+
 #include <mono/utils/mono-compiler.h>
 #include <mono/utils/mono-semaphore.h>
 #include <mono/utils/mono-threads.h>
@@ -19,6 +24,7 @@
 #include <mono/utils/mono-mmap.h>
 #include <mono/utils/atomic.h>
 #include <mono/utils/mono-time.h>
+#include <mono/utils/mono-lazy-init.h>
 
 
 #include <errno.h>
@@ -70,24 +76,24 @@ void
 mono_threads_notify_initiator_of_abort (MonoThreadInfo* info)
 {
        THREADS_SUSPEND_DEBUG ("[INITIATOR-NOTIFY-ABORT] %p\n", mono_thread_info_get_tid (info));
-       MONO_SEM_POST (&suspend_semaphore);
        InterlockedIncrement (&abort_posts);
+       MONO_SEM_POST (&suspend_semaphore);
 }
 
 void
 mono_threads_notify_initiator_of_suspend (MonoThreadInfo* info)
 {
        THREADS_SUSPEND_DEBUG ("[INITIATOR-NOTIFY-SUSPEND] %p\n", mono_thread_info_get_tid (info));
-       MONO_SEM_POST (&suspend_semaphore);
        InterlockedIncrement (&suspend_posts);
+       MONO_SEM_POST (&suspend_semaphore);
 }
 
 void
 mono_threads_notify_initiator_of_resume (MonoThreadInfo* info)
 {
        THREADS_SUSPEND_DEBUG ("[INITIATOR-NOTIFY-RESUME] %p\n", mono_thread_info_get_tid (info));
-       MONO_SEM_POST (&suspend_semaphore);
        InterlockedIncrement (&resume_posts);
+       MONO_SEM_POST (&suspend_semaphore);
 }
 
 static void
@@ -129,8 +135,9 @@ void
 mono_threads_begin_global_suspend (void)
 {
        g_assert (pending_suspends == 0);
-       THREADS_SUSPEND_DEBUG ("------ BEGIN GLOBAL OP sp %d rp %d ap %d wd %d po %d\n", suspend_posts, resume_posts,
+       THREADS_SUSPEND_DEBUG ("------ BEGIN GLOBAL OP sp %d rp %d ap %d wd %d po %d (sp + rp + ap == wd) (wd == po)\n", suspend_posts, resume_posts,
                abort_posts, waits_done, pending_ops);
+       g_assert ((suspend_posts + resume_posts + abort_posts) == waits_done);
        mono_threads_core_begin_global_suspend ();
 }
 
@@ -140,6 +147,7 @@ mono_threads_end_global_suspend (void)
        g_assert (pending_suspends == 0);
        THREADS_SUSPEND_DEBUG ("------ END GLOBAL OP sp %d rp %d ap %d wd %d po %d\n", suspend_posts, resume_posts,
                abort_posts, waits_done, pending_ops);
+       g_assert ((suspend_posts + resume_posts + abort_posts) == waits_done);
        mono_threads_core_end_global_suspend ();
 }
 
@@ -149,7 +157,7 @@ dump_threads (void)
        MonoThreadInfo *info;
        MonoThreadInfo *cur = mono_thread_info_current ();
 
-       MOSTLY_ASYNC_SAFE_PRINTF ("STATE CUE CARD: (? means a positive number, usually 1 or 2)\n");
+       MOSTLY_ASYNC_SAFE_PRINTF ("STATE CUE CARD: (? means a positive number, usually 1 or 2, * means any number)\n");
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x0\t- starting (GOOD, unless the thread is running managed code)\n");
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x1\t- running (BAD, unless it's the gc thread)\n");
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x2\t- detached (GOOD, unless the thread is running managed code)\n");
@@ -157,11 +165,19 @@ dump_threads (void)
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x?04\t- self suspended (GOOD)\n");
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x?05\t- async suspend requested (BAD)\n");
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x?06\t- self suspend requested (BAD)\n");
-       MOSTLY_ASYNC_SAFE_PRINTF ("\t0x07\t- blocking (GOOD)\n");
+       MOSTLY_ASYNC_SAFE_PRINTF ("\t0x*07\t- blocking (GOOD)\n");
        MOSTLY_ASYNC_SAFE_PRINTF ("\t0x?08\t- blocking with pending suspend (GOOD)\n");
 
        FOREACH_THREAD_SAFE (info) {
+#ifdef TARGET_MACH
+               char thread_name [256] = { 0 };
+               pthread_getname_np (mono_thread_info_get_tid (info), thread_name, 255);
+
+               MOSTLY_ASYNC_SAFE_PRINTF ("--thread %p id %p [%p] (%s) state %x  %s\n", info, (void *) mono_thread_info_get_tid (info), (void*)(size_t)info->native_handle, thread_name, info->thread_state, info == cur ? "GC INITIATOR" : "" );
+#else
                MOSTLY_ASYNC_SAFE_PRINTF ("--thread %p id %p [%p] state %x  %s\n", info, (void *) mono_thread_info_get_tid (info), (void*)(size_t)info->native_handle, info->thread_state, info == cur ? "GC INITIATOR" : "" );
+#endif
+
        } END_FOREACH_THREAD_SAFE
 }
 
@@ -172,10 +188,10 @@ mono_threads_wait_pending_operations (void)
        int c = pending_suspends;
 
        /* Wait threads to park */
+       THREADS_SUSPEND_DEBUG ("[INITIATOR-WAIT-COUNT] %d\n", c);
        if (pending_suspends) {
                MonoStopwatch suspension_time;
                mono_stopwatch_start (&suspension_time);
-               THREADS_SUSPEND_DEBUG ("[INITIATOR-WAIT-COUNT] %d\n", c);
                for (i = 0; i < pending_suspends; ++i) {
                        THREADS_SUSPEND_DEBUG ("[INITIATOR-WAIT-WAITING]\n");
                        InterlockedIncrement (&waits_done);
@@ -311,6 +327,8 @@ register_thread (MonoThreadInfo *info, gpointer baseptr)
        info->stack_start_limit = staddr;
        info->stack_end = staddr + stsize;
 
+       info->stackdata = g_byte_array_new ();
+
        mono_threads_platform_register (info);
 
        /*
@@ -357,14 +375,7 @@ unregister_thread (void *arg)
        if (threads_callbacks.thread_detach)
                threads_callbacks.thread_detach (info);
 
-       /*
-       Since the thread info lock is taken from within blocking sections, we can't check from there, so it must be done here.
-       This ensures that we won't lose any suspend requests as a suspend initiator must hold the lock.
-       Once we're holding the suspend lock, no threads can suspend us and once we unregister, no thread can find us. 
-       */
-       MONO_PREPARE_BLOCKING
        mono_thread_info_suspend_lock ();
-       MONO_FINISH_BLOCKING
 
        /*
        Now perform the callback that must be done under locks.
@@ -379,6 +390,8 @@ unregister_thread (void *arg)
 
        mono_thread_info_suspend_unlock ();
 
+       g_byte_array_free (info->stackdata, /*free_segment=*/TRUE);
+
        /*now it's safe to free the thread info.*/
        mono_thread_hazardous_free_or_queue (info, free_thread_info, TRUE, FALSE);
        mono_thread_small_id_free (small_id);
@@ -422,7 +435,7 @@ mono_threads_unregister_current_thread (MonoThreadInfo *info)
 MonoThreadInfo*
 mono_thread_info_current_unchecked (void)
 {
-       return (MonoThreadInfo*)mono_native_tls_get_value (thread_info_key);
+       return mono_threads_inited ? (MonoThreadInfo*)mono_native_tls_get_value (thread_info_key) : NULL;
 }
 
 
@@ -578,6 +591,7 @@ mono_threads_init (MonoThreadInfoCallbacks *callbacks, size_t info_size)
        res = mono_native_tls_alloc (&thread_info_key, (void *) unregister_thread);
        res = mono_native_tls_alloc (&thread_exited_key, (void *) thread_exited_dtor);
 #endif
+
        g_assert (res);
 
 #ifndef HAVE_KW_THREAD
@@ -593,6 +607,7 @@ mono_threads_init (MonoThreadInfoCallbacks *callbacks, size_t info_size)
        mono_lls_init (&thread_list, NULL);
        mono_thread_smr_init ();
        mono_threads_init_platform ();
+       mono_threads_init_abort_syscall ();
 
 #if defined(__MACH__)
        mono_mach_init (thread_info_key);
@@ -681,7 +696,7 @@ mono_thread_info_end_self_suspend (void)
                return;
        THREADS_SUSPEND_DEBUG ("FINISH SELF SUSPEND OF %p\n", info);
 
-       g_assert (mono_threads_get_runtime_callbacks ()->thread_state_init_from_sigctx (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX], NULL));
+       mono_threads_get_runtime_callbacks ()->thread_state_init (&info->thread_saved_state [SELF_SUSPEND_STATE_INDEX]);
 
        /* commit the saved state and notify others if needed */
        switch (mono_threads_transition_state_poll (info)) {
@@ -800,10 +815,6 @@ is_thread_in_critical_region (MonoThreadInfo *info)
        if (info->inside_critical_region)
                return TRUE;
 
-       if (threads_callbacks.mono_thread_in_critical_region && threads_callbacks.mono_thread_in_critical_region (info)) {
-               return TRUE;
-       }
-
        /* Are we inside a GC critical region? */
        if (threads_callbacks.mono_thread_in_critical_region && threads_callbacks.mono_thread_in_critical_region (info)) {
                return TRUE;
@@ -888,6 +899,7 @@ mono_thread_info_safe_suspend_and_run (MonoNativeThreadId id, gboolean interrupt
        /*FIXME: unify this with self-suspend*/
        g_assert (id != mono_native_thread_id_get ());
 
+       /* This can block during stw */
        mono_thread_info_suspend_lock ();
        mono_threads_begin_global_suspend ();
 
@@ -972,7 +984,9 @@ STW to make sure no unsafe pending suspend is in progress.
 void
 mono_thread_info_suspend_lock (void)
 {
+       MONO_TRY_BLOCKING;
        MONO_SEM_WAIT_UNITERRUPTIBLE (&global_suspend_semaphore);
+       MONO_FINISH_TRY_BLOCKING;
 }
 
 void
@@ -1005,7 +1019,7 @@ mono_thread_info_abort_socket_syscall_for_close (MonoNativeThreadId tid)
        if (!info)
                return;
 
-       if (mono_thread_info_run_state (info) > STATE_RUNNING) {
+       if (mono_thread_info_run_state (info) == STATE_DETACHED) {
                mono_hazard_pointer_clear (hp, 1);
                return;
        }
@@ -1092,6 +1106,135 @@ mono_thread_info_yield (void)
 {
        return mono_threads_core_yield ();
 }
+static mono_lazy_init_t sleep_init = MONO_LAZY_INIT_STATUS_NOT_INITIALIZED;
+static mono_mutex_t sleep_mutex;
+static mono_cond_t sleep_cond;
+
+static void
+sleep_initialize (void)
+{
+       mono_mutex_init (&sleep_mutex);
+       mono_cond_init (&sleep_cond, NULL);
+}
+
+static void
+sleep_interrupt (gpointer data)
+{
+       mono_mutex_lock (&sleep_mutex);
+       mono_cond_broadcast (&sleep_cond);
+       mono_mutex_unlock (&sleep_mutex);
+}
+
+static inline guint32
+sleep_interruptable (guint32 ms, gboolean *alerted)
+{
+       guint32 start, now, end;
+
+       g_assert (INFINITE == G_MAXUINT32);
+
+       g_assert (alerted);
+       *alerted = FALSE;
+
+       start = mono_msec_ticks ();
+
+       if (start < G_MAXUINT32 - ms) {
+               end = start + ms;
+       } else {
+               /* start + ms would overflow guint32 */
+               end = G_MAXUINT32;
+       }
+
+       mono_lazy_initialize (&sleep_init, sleep_initialize);
+
+       mono_mutex_lock (&sleep_mutex);
+
+       for (now = mono_msec_ticks (); ms == INFINITE || now - start < ms; now = mono_msec_ticks ()) {
+               mono_thread_info_install_interrupt (sleep_interrupt, NULL, alerted);
+               if (*alerted) {
+                       mono_mutex_unlock (&sleep_mutex);
+                       return WAIT_IO_COMPLETION;
+               }
+
+               if (ms < INFINITE)
+                       mono_cond_timedwait_ms (&sleep_cond, &sleep_mutex, end - now);
+               else
+                       mono_cond_wait (&sleep_cond, &sleep_mutex);
+
+               mono_thread_info_uninstall_interrupt (alerted);
+               if (*alerted) {
+                       mono_mutex_unlock (&sleep_mutex);
+                       return WAIT_IO_COMPLETION;
+               }
+       }
+
+       mono_mutex_unlock (&sleep_mutex);
+
+       return 0;
+}
+
+gint
+mono_thread_info_sleep (guint32 ms, gboolean *alerted)
+{
+       if (ms == 0) {
+               MonoThreadInfo *info;
+
+               mono_thread_info_yield ();
+
+               info = mono_thread_info_current ();
+               if (info && mono_thread_info_is_interrupt_state (info))
+                       return WAIT_IO_COMPLETION;
+
+               return 0;
+       }
+
+       if (alerted)
+               return sleep_interruptable (ms, alerted);
+
+       if (ms == INFINITE) {
+               do {
+#ifdef HOST_WIN32
+                       Sleep (G_MAXUINT32);
+#else
+                       sleep (G_MAXUINT32);
+#endif
+               } while (1);
+       } else {
+               int ret;
+#if defined (__linux__) && !defined(PLATFORM_ANDROID)
+               struct timespec start, target;
+
+               /* Use clock_nanosleep () to prevent time drifting problems when nanosleep () is interrupted by signals */
+               ret = clock_gettime (CLOCK_MONOTONIC, &start);
+               g_assert (ret == 0);
+
+               target = start;
+               target.tv_sec += ms / 1000;
+               target.tv_nsec += (ms % 1000) * 1000000;
+               if (target.tv_nsec > 999999999) {
+                       target.tv_nsec -= 999999999;
+                       target.tv_sec ++;
+               }
+
+               do {
+                       ret = clock_nanosleep (CLOCK_MONOTONIC, TIMER_ABSTIME, &target, NULL);
+               } while (ret != 0);
+#elif HOST_WIN32
+               Sleep (ms);
+#else
+               struct timespec req, rem;
+
+               req.tv_sec = ms / 1000;
+               req.tv_nsec = (ms % 1000) * 1000000;
+
+               do {
+                       memset (&rem, 0, sizeof (rem));
+                       ret = nanosleep (&req, &rem);
+               } while (ret != 0);
+#endif /* __linux__ */
+       }
+
+       return 0;
+}
 
 gpointer
 mono_thread_info_tls_get (THREAD_INFO_TYPE *info, MonoTlsKey key)
@@ -1157,42 +1300,204 @@ mono_thread_info_set_name (MonoNativeThreadId tid, const char *name)
        mono_threads_core_set_name (tid, name);
 }
 
+#define INTERRUPT_STATE ((MonoThreadInfoInterruptToken*) (size_t) -1)
+
+struct _MonoThreadInfoInterruptToken {
+       void (*callback) (gpointer data);
+       gpointer data;
+};
+
 /*
- * mono_thread_info_prepare_interrupt:
+ * mono_thread_info_install_interrupt: install an interruption token for the current thread.
  *
- *   See wapi_prepare_interrupt ().
+ *  - @callback: must be able to be called from another thread and always cancel the wait
+ *  - @data: passed to the callback
+ *  - @interrupted: will be set to TRUE if a token is already installed, FALSE otherwise
+ *     if set to TRUE, it must mean that the thread is in interrupted state
  */
-gpointer
-mono_thread_info_prepare_interrupt (HANDLE thread_handle)
+void
+mono_thread_info_install_interrupt (void (*callback) (gpointer data), gpointer data, gboolean *interrupted)
 {
-       return mono_threads_core_prepare_interrupt (thread_handle);
+       MonoThreadInfo *info;
+       MonoThreadInfoInterruptToken *previous_token, *token;
+
+       g_assert (callback);
+
+       g_assert (interrupted);
+       *interrupted = FALSE;
+
+       info = mono_thread_info_current ();
+       g_assert (info);
+
+       /* The memory of this token can be freed at 2 places:
+        *  - if the token is not interrupted: it will be freed in uninstall, as info->interrupt_token has not been replaced
+        *     by the INTERRUPT_STATE flag value, and it still contains the pointer to the memory location
+        *  - if the token is interrupted: it will be freed in finish, as the token is now owned by the prepare/finish
+        *     functions, and info->interrupt_token does not contains a pointer to the memory anymore */
+       token = g_new0 (MonoThreadInfoInterruptToken, 1);
+       token->callback = callback;
+       token->data = data;
+
+       previous_token = InterlockedCompareExchangePointer ((gpointer*) &info->interrupt_token, token, NULL);
+
+       if (previous_token) {
+               if (previous_token != INTERRUPT_STATE)
+                       g_error ("mono_thread_info_install_interrupt: previous_token should be INTERRUPT_STATE (%p), but it was %p", INTERRUPT_STATE, previous_token);
+
+               g_free (token);
+
+               *interrupted = TRUE;
+       }
+
+       THREADS_INTERRUPT_DEBUG ("interrupt install    tid %p token %p previous_token %p interrupted %s\n",
+               mono_thread_info_get_tid (info), token, previous_token, *interrupted ? "TRUE" : "FALSE");
 }
 
 void
-mono_thread_info_finish_interrupt (gpointer wait_handle)
+mono_thread_info_uninstall_interrupt (gboolean *interrupted)
+{
+       MonoThreadInfo *info;
+       MonoThreadInfoInterruptToken *previous_token;
+
+       g_assert (interrupted);
+       *interrupted = FALSE;
+
+       info = mono_thread_info_current ();
+       g_assert (info);
+
+       previous_token = InterlockedExchangePointer ((gpointer*) &info->interrupt_token, NULL);
+
+       /* only the installer can uninstall the token */
+       g_assert (previous_token);
+
+       if (previous_token == INTERRUPT_STATE) {
+               /* if it is interrupted, then it is going to be freed in finish interrupt */
+               *interrupted = TRUE;
+       } else {
+               g_free (previous_token);
+       }
+
+       THREADS_INTERRUPT_DEBUG ("interrupt uninstall  tid %p previous_token %p interrupted %s\n",
+               mono_thread_info_get_tid (info), previous_token, *interrupted ? "TRUE" : "FALSE");
+}
+
+static MonoThreadInfoInterruptToken*
+set_interrupt_state (MonoThreadInfo *info)
+{
+       MonoThreadInfoInterruptToken *token, *previous_token;
+
+       g_assert (info);
+
+       /* Atomically obtain the token the thread is
+       * waiting on, and change it to a flag value. */
+
+       do {
+               previous_token = info->interrupt_token;
+
+               /* Already interrupted */
+               if (previous_token == INTERRUPT_STATE) {
+                       token = NULL;
+                       break;
+               }
+
+               token = previous_token;
+       } while (InterlockedCompareExchangePointer ((gpointer*) &info->interrupt_token, INTERRUPT_STATE, previous_token) != previous_token);
+
+       return token;
+}
+
+/*
+ * mono_thread_info_prepare_interrupt:
+ *
+ * The state of the thread info interrupt token is set to 'interrupted' which means that :
+ *  - if the thread calls one of the WaitFor functions, the function will return with
+ *     WAIT_IO_COMPLETION instead of waiting
+ *  - if the thread was waiting when this function was called, the wait will be broken
+ *
+ * It is possible that the wait functions return WAIT_IO_COMPLETION, but the target thread
+ * didn't receive the interrupt signal yet, in this case it should call the wait function
+ * again. This essentially means that the target thread will busy wait until it is ready to
+ * process the interruption.
+ */
+MonoThreadInfoInterruptToken*
+mono_thread_info_prepare_interrupt (MonoThreadInfo *info)
 {
-       mono_threads_core_finish_interrupt (wait_handle);
+       MonoThreadInfoInterruptToken *token;
+
+       token = set_interrupt_state (info);
+
+       THREADS_INTERRUPT_DEBUG ("interrupt prepare    tid %p token %p\n",
+               mono_thread_info_get_tid (info), token);
+
+       return token;
 }
 
 void
-mono_thread_info_interrupt (HANDLE thread_handle)
+mono_thread_info_finish_interrupt (MonoThreadInfoInterruptToken *token)
 {
-       gpointer wait_handle;
+       THREADS_INTERRUPT_DEBUG ("interrupt finish     token %p\n", token);
+
+       if (token == NULL)
+               return;
+
+       g_assert (token->callback);
 
-       wait_handle = mono_thread_info_prepare_interrupt (thread_handle);
-       mono_thread_info_finish_interrupt (wait_handle);
+       token->callback (token->data);
+
+       g_free (token);
 }
-       
+
 void
 mono_thread_info_self_interrupt (void)
 {
-       mono_threads_core_self_interrupt ();
+       MonoThreadInfo *info;
+       MonoThreadInfoInterruptToken *token;
+
+       info = mono_thread_info_current ();
+       g_assert (info);
+
+       token = set_interrupt_state (info);
+       g_assert (!token);
+
+       THREADS_INTERRUPT_DEBUG ("interrupt self       tid %p\n",
+               mono_thread_info_get_tid (info));
 }
 
+/* Clear the interrupted flag of the current thread, set with
+ * mono_thread_info_self_interrupt, so it can wait again */
 void
-mono_thread_info_clear_interruption (void)
+mono_thread_info_clear_self_interrupt ()
 {
-       mono_threads_core_clear_interruption ();
+       MonoThreadInfo *info;
+       MonoThreadInfoInterruptToken *previous_token;
+
+       info = mono_thread_info_current ();
+       g_assert (info);
+
+       previous_token = InterlockedCompareExchangePointer ((gpointer*) &info->interrupt_token, NULL, INTERRUPT_STATE);
+       g_assert (previous_token == NULL || previous_token == INTERRUPT_STATE);
+
+       THREADS_INTERRUPT_DEBUG ("interrupt clear self tid %p previous_token %p\n", mono_thread_info_get_tid (info), previous_token);
+}
+
+gboolean
+mono_thread_info_is_interrupt_state (MonoThreadInfo *info)
+{
+       g_assert (info);
+       return InterlockedReadPointer ((gpointer*) &info->interrupt_token) == INTERRUPT_STATE;
+}
+
+void
+mono_thread_info_describe_interrupt_token (MonoThreadInfo *info, GString *text)
+{
+       g_assert (info);
+
+       if (!InterlockedReadPointer ((gpointer*) &info->interrupt_token))
+               g_string_append_printf (text, "not waiting");
+       else if (InterlockedReadPointer ((gpointer*) &info->interrupt_token) == INTERRUPT_STATE)
+               g_string_append_printf (text, "interrupted state");
+       else
+               g_string_append_printf (text, "waiting");
 }
 
 /* info must be self or be held in a hazard pointer. */