#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>
#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>
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
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 ();
}
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 ();
}
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");
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
}
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);
info->stack_start_limit = staddr;
info->stack_end = staddr + stsize;
+ info->stackdata = g_byte_array_new ();
+
mono_threads_platform_register (info);
/*
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.
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);
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;
}
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
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);
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)) {
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;
/*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 ();
void
mono_thread_info_suspend_lock (void)
{
+ MONO_TRY_BLOCKING;
MONO_SEM_WAIT_UNITERRUPTIBLE (&global_suspend_semaphore);
+ MONO_FINISH_TRY_BLOCKING;
}
void
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;
}
{
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)
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. */