-/*
- * monitor.c: Monitor locking functions
+/**
+ * \file
+ * Monitor locking functions
*
* Author:
* Dick Porter (dick@ximian.com)
#include <mono/metadata/threads-types.h>
#include <mono/metadata/exception.h>
#include <mono/metadata/threads.h>
-#include <mono/io-layer/io-layer.h>
#include <mono/metadata/object-internals.h>
#include <mono/metadata/class-internals.h>
#include <mono/metadata/gc-internals.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/tabledefs.h>
#include <mono/metadata/marshal.h>
+#include <mono/metadata/w32event.h>
#include <mono/utils/mono-threads.h>
#include <mono/metadata/profiler-private.h>
#include <mono/utils/mono-time.h>
#include <mono/utils/atomic.h>
+#include <mono/utils/w32api.h>
+#include <mono/utils/mono-os-wait.h>
/*
* Pull the list of opcodes
/**
* mono_locks_dump:
- * @include_untaken:
- *
+ * \param include_untaken Whether to list unheld inflated locks.
* Print a report on stdout of the managed locks currently held by
- * threads. If @include_untaken is specified, list also inflated locks
+ * threads. If \p include_untaken is specified, list also inflated locks
* which are unheld.
* This is supposed to be used in debuggers like gdb.
*/
mon->data = monitor_freelist;
monitor_freelist = mon;
#ifndef DISABLE_PERFCOUNTERS
- mono_perfcounters->gc_sync_blocks--;
+ InterlockedDecrement (&mono_perfcounters->gc_sync_blocks);
#endif
}
/* Orphaned events left by aborted threads */
while (new_->wait_list) {
LOCK_DEBUG (g_message (G_GNUC_PRETTY_FUNCTION ": (%d): Closing orphaned event %d", mono_thread_info_get_small_id (), new_->wait_list->data));
- CloseHandle (new_->wait_list->data);
+ mono_w32event_close (new_->wait_list->data);
new_->wait_list = g_slist_remove (new_->wait_list, new_->wait_list->data);
}
}
new_->data = NULL;
#ifndef DISABLE_PERFCOUNTERS
- mono_perfcounters->gc_sync_blocks++;
+ InterlockedIncrement (&mono_perfcounters->gc_sync_blocks);
#endif
return new_;
}
#endif
}
-static void
+static gboolean
mono_monitor_ensure_owned (LockWord lw, guint32 id)
{
if (lock_word_is_flat (lw)) {
if (lock_word_get_owner (lw) == id)
- return;
+ return TRUE;
} else if (lock_word_is_inflated (lw)) {
if (mon_status_get_owner (lock_word_get_inflated_lock (lw)->status) == id)
- return;
+ return TRUE;
}
mono_set_pending_exception (mono_get_exception_synchronization_lock ("Object synchronization method was called from an unsynchronized block of code."));
+ return FALSE;
}
/*
/* The object must be locked by someone else... */
#ifndef DISABLE_PERFCOUNTERS
- mono_perfcounters->thread_contentions++;
+ InterlockedIncrement (&mono_perfcounters->thread_contentions);
#endif
/* If ms is 0 we don't block, but just fail straight away */
return 0;
}
- mono_profiler_monitor_event (obj, MONO_PROFILER_MONITOR_CONTENTION);
+ MONO_PROFILER_RAISE (monitor_contention, (obj));
/* The slow path begins here. */
retry_contended:
if (G_LIKELY (tmp_status == old_status)) {
/* Success */
g_assert (mon->nest == 1);
- mono_profiler_monitor_event (obj, MONO_PROFILER_MONITOR_DONE);
+ MONO_PROFILER_RAISE (monitor_acquired, (obj));
return 1;
}
}
/* If the object is currently locked by this thread... */
if (mon_status_get_owner (old_status) == id) {
mon->nest++;
- mono_profiler_monitor_event (obj, MONO_PROFILER_MONITOR_DONE);
+ MONO_PROFILER_RAISE (monitor_acquired, (obj));
return 1;
}
}
}
- if (ms != INFINITE) {
+ if (ms != MONO_INFINITE_WAIT) {
then = mono_msec_ticks ();
}
waitms = ms;
#ifndef DISABLE_PERFCOUNTERS
- mono_perfcounters->thread_queue_len++;
- mono_perfcounters->thread_queue_max++;
+ InterlockedIncrement (&mono_perfcounters->thread_queue_len);
+ InterlockedIncrement (&mono_perfcounters->thread_queue_max);
#endif
thread = mono_thread_internal_current ();
- mono_thread_set_state (thread, ThreadState_WaitSleepJoin);
+ /*
+ * If we allow interruption, we check the test state for an abort request before going into sleep.
+ * This is a workaround to the fact that Thread.Abort does non-sticky interruption of semaphores.
+ *
+ * Semaphores don't support the sticky interruption with mono_thread_info_install_interrupt.
+ *
+ * A better fix would be to switch to wait with something that allows sticky interrupts together
+ * with wrapping it with abort_protected_block_count for the non-alertable cases.
+ * And somehow make this whole dance atomic and not crazy expensive. Good luck.
+ *
+ */
+ if (allow_interruption) {
+ if (!mono_thread_test_and_set_state (thread, ThreadState_AbortRequested, ThreadState_WaitSleepJoin)) {
+ wait_ret = MONO_SEM_TIMEDWAIT_RET_ALERTED;
+ goto done_waiting;
+ }
+ } else {
+ mono_thread_set_state (thread, ThreadState_WaitSleepJoin);
+ }
/*
* We pass ALERTABLE instead of allow_interruption since we have to check for the
wait_ret = mono_coop_sem_timedwait (mon->entry_sem, waitms, MONO_SEM_FLAGS_ALERTABLE);
mono_thread_clr_state (thread, ThreadState_WaitSleepJoin);
-
+
+done_waiting:
#ifndef DISABLE_PERFCOUNTERS
- mono_perfcounters->thread_queue_len--;
+ InterlockedDecrement (&mono_perfcounters->thread_queue_len);
#endif
if (wait_ret == MONO_SEM_TIMEDWAIT_RET_ALERTED && !allow_interruption) {
* We have to obey a stop/suspend request even if
* allow_interruption is FALSE to avoid hangs at shutdown.
*/
- if (!mono_thread_test_state (mono_thread_internal_current (), (MonoThreadState)(ThreadState_StopRequested | ThreadState_SuspendRequested | ThreadState_AbortRequested))) {
- if (ms != INFINITE) {
+ if (!mono_thread_test_state (mono_thread_internal_current (), ThreadState_SuspendRequested | ThreadState_AbortRequested)) {
+ if (ms != MONO_INFINITE_WAIT) {
now = mono_msec_ticks ();
/* it should not overflow before ~30k years */
/* Timed out or interrupted */
mon_decrement_entry_count (mon);
- mono_profiler_monitor_event (obj, MONO_PROFILER_MONITOR_FAIL);
+ MONO_PROFILER_RAISE (monitor_failed, (obj));
if (wait_ret == MONO_SEM_TIMEDWAIT_RET_ALERTED) {
LOCK_DEBUG (g_message ("%s: (%d) interrupted waiting, returning -1", __func__, id));
return -1;
- } else if (wait_ret == MONO_SEM_TIMEDWAIT_RET_ALERTED) {
+ } else if (wait_ret == MONO_SEM_TIMEDWAIT_RET_TIMEDOUT) {
LOCK_DEBUG (g_message ("%s: (%d) timed out waiting, returning FALSE", __func__, id));
return 0;
} else {
return -1;
}
-gboolean
-mono_monitor_enter (MonoObject *obj)
+/* This is an icall */
+MonoBoolean
+mono_monitor_enter_internal (MonoObject *obj)
{
+ gint32 res;
+ gboolean allow_interruption = TRUE;
if (G_UNLIKELY (!obj)) {
mono_set_pending_exception (mono_get_exception_argument_null ("obj"));
return FALSE;
}
- return mono_monitor_try_enter_internal (obj, INFINITE, FALSE) == 1;
+
+ /*
+ * An inquisitive mind could ask what's the deal with this loop.
+ * It exists to deal with interrupting a monitor enter that happened within an abort-protected block, like a .cctor.
+ *
+ * The thread will be set with a pending abort and the wait might even be interrupted. Either way, once we call mono_thread_interruption_checkpoint,
+ * it will return NULL meaning we can't be aborted right now. Once that happens we switch to non-alertable.
+ */
+ do {
+ res = mono_monitor_try_enter_internal (obj, MONO_INFINITE_WAIT, allow_interruption);
+ /*This means we got interrupted during the wait and didn't got the monitor.*/
+ if (res == -1) {
+ MonoException *exc = mono_thread_interruption_checkpoint ();
+ if (exc) {
+ mono_set_pending_exception (exc);
+ return FALSE;
+ } else {
+ //we detected a pending interruption but it turned out to be a false positive, we ignore it from now on (this feels like a hack, right?, threads.c should give us less confusing directions)
+ allow_interruption = FALSE;
+ }
+ }
+ } while (res == -1);
+ return TRUE;
}
-gboolean
+/**
+ * mono_monitor_enter:
+ */
+gboolean
+mono_monitor_enter (MonoObject *obj)
+{
+ return mono_monitor_enter_internal (obj);
+}
+
+/* Called from JITted code so we return guint32 instead of gboolean */
+guint32
mono_monitor_enter_fast (MonoObject *obj)
{
if (G_UNLIKELY (!obj)) {
return mono_monitor_try_enter_internal (obj, 0, FALSE) == 1;
}
+/**
+ * mono_monitor_try_enter:
+ */
gboolean
mono_monitor_try_enter (MonoObject *obj, guint32 ms)
{
return mono_monitor_try_enter_internal (obj, ms, FALSE) == 1;
}
+/**
+ * mono_monitor_exit:
+ */
void
mono_monitor_exit (MonoObject *obj)
{
lw.sync = obj->synchronisation;
- mono_monitor_ensure_owned (lw, mono_thread_info_get_small_id ());
+ if (!mono_monitor_ensure_owned (lw, mono_thread_info_get_small_id ()))
+ return;
if (G_UNLIKELY (lock_word_is_inflated (lw)))
mono_monitor_exit_inflated (obj);
}
void
-ves_icall_System_Threading_Monitor_Monitor_try_enter_with_atomic_var (MonoObject *obj, guint32 ms, char *lockTaken)
+ves_icall_System_Threading_Monitor_Monitor_try_enter_with_atomic_var (MonoObject *obj, guint32 ms, MonoBoolean *lockTaken)
{
gint32 res;
+ gboolean allow_interruption = TRUE;
if (G_UNLIKELY (!obj)) {
mono_set_pending_exception (mono_get_exception_argument_null ("obj"));
return;
}
do {
- res = mono_monitor_try_enter_internal (obj, ms, TRUE);
+ res = mono_monitor_try_enter_internal (obj, ms, allow_interruption);
/*This means we got interrupted during the wait and didn't got the monitor.*/
if (res == -1) {
MonoException *exc = mono_thread_interruption_checkpoint ();
if (exc) {
mono_set_pending_exception (exc);
return;
+ } else {
+ //we detected a pending interruption but it turned out to be a false positive, we ignore it from now on (this feels like a hack, right?, threads.c should give us less confusing directions)
+ allow_interruption = FALSE;
}
}
} while (res == -1);
*lockTaken = res == 1;
}
+/**
+ * mono_monitor_enter_v4:
+ */
void
mono_monitor_enter_v4 (MonoObject *obj, char *lock_taken)
{
return;
}
- ves_icall_System_Threading_Monitor_Monitor_try_enter_with_atomic_var (obj, INFINITE, lock_taken);
+ MonoBoolean taken;
+
+ ves_icall_System_Threading_Monitor_Monitor_try_enter_with_atomic_var (obj, MONO_INFINITE_WAIT, &taken);
+ *lock_taken = taken;
+}
+
+/* Called from JITted code */
+void
+mono_monitor_enter_v4_internal (MonoObject *obj, MonoBoolean *lock_taken)
+{
+ if (*lock_taken == 1) {
+ mono_set_pending_exception (mono_get_exception_argument ("lockTaken", "lockTaken is already true"));
+ return;
+ }
+
+ ves_icall_System_Threading_Monitor_Monitor_try_enter_with_atomic_var (obj, MONO_INFINITE_WAIT, lock_taken);
}
/*
* Same as mono_monitor_enter_v4, but return immediately if the
* monitor cannot be acquired.
* Returns TRUE if the lock was acquired, FALSE otherwise.
+ * Called from JITted code so we return guint32 instead of gboolean.
*/
-gboolean
-mono_monitor_enter_v4_fast (MonoObject *obj, char *lock_taken)
+guint32
+mono_monitor_enter_v4_fast (MonoObject *obj, MonoBoolean *lock_taken)
{
if (*lock_taken == 1)
return FALSE;
return res == 1;
}
-gboolean
+MonoBoolean
ves_icall_System_Threading_Monitor_Monitor_test_owner (MonoObject *obj)
{
LockWord lw;
return(FALSE);
}
-gboolean
+MonoBoolean
ves_icall_System_Threading_Monitor_Monitor_test_synchronised (MonoObject *obj)
{
LockWord lw;
id = mono_thread_info_get_small_id ();
lw.sync = obj->synchronisation;
- mono_monitor_ensure_owned (lw, id);
+ if (!mono_monitor_ensure_owned (lw, id))
+ return;
if (!lock_word_is_inflated (lw)) {
/* No threads waiting. A wait would have inflated the lock */
if (mon->wait_list != NULL) {
LOCK_DEBUG (g_message ("%s: (%d) signalling and dequeuing handle %p", __func__, mono_thread_info_get_small_id (), mon->wait_list->data));
- SetEvent (mon->wait_list->data);
+ mono_w32event_set (mon->wait_list->data);
mon->wait_list = g_slist_remove (mon->wait_list, mon->wait_list->data);
}
}
id = mono_thread_info_get_small_id ();
lw.sync = obj->synchronisation;
- mono_monitor_ensure_owned (lw, id);
+ if (!mono_monitor_ensure_owned (lw, id))
+ return;
if (!lock_word_is_inflated (lw)) {
/* No threads waiting. A wait would have inflated the lock */
while (mon->wait_list != NULL) {
LOCK_DEBUG (g_message ("%s: (%d) signalling and dequeuing handle %p", __func__, mono_thread_info_get_small_id (), mon->wait_list->data));
- SetEvent (mon->wait_list->data);
+ mono_w32event_set (mon->wait_list->data);
mon->wait_list = g_slist_remove (mon->wait_list, mon->wait_list->data);
}
}
-gboolean
+MonoBoolean
ves_icall_System_Threading_Monitor_Monitor_wait (MonoObject *obj, guint32 ms)
{
LockWord lw;
MonoThreadsSync *mon;
HANDLE event;
guint32 nest;
- guint32 ret;
+ MonoW32HandleWaitRet ret;
gboolean success = FALSE;
gint32 regain;
MonoInternalThread *thread = mono_thread_internal_current ();
lw.sync = obj->synchronisation;
- mono_monitor_ensure_owned (lw, id);
+ if (!mono_monitor_ensure_owned (lw, id))
+ return FALSE;
if (!lock_word_is_inflated (lw)) {
mono_monitor_inflate_owned (obj, id);
if (mono_thread_current_check_pending_interrupt ())
return FALSE;
- event = CreateEvent (NULL, FALSE, FALSE, NULL);
+ event = mono_w32event_create (FALSE, FALSE);
if (event == NULL) {
mono_set_pending_exception (mono_get_exception_synchronization_lock ("Failed to set up wait event"));
return FALSE;
/* This looks superfluous */
if (mono_thread_current_check_pending_interrupt ()) {
- CloseHandle (event);
+ mono_w32event_close (event);
return FALSE;
}
* signalled before we wait, we still succeed.
*/
MONO_ENTER_GC_SAFE;
- ret = WaitForSingleObjectEx (event, ms, TRUE);
+#ifdef HOST_WIN32
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_single_object_ex (event, ms, TRUE), 1);
+#else
+ ret = mono_w32handle_wait_one (event, ms, TRUE);
+#endif /* HOST_WIN32 */
MONO_EXIT_GC_SAFE;
/* Reset the thread state fairly early, so we don't have to worry
/* Regain the lock with the previous nest count */
do {
- regain = mono_monitor_try_enter_inflated (obj, INFINITE, TRUE, id);
+ regain = mono_monitor_try_enter_inflated (obj, MONO_INFINITE_WAIT, TRUE, id);
/* We must regain the lock before handling interruption requests */
} while (regain == -1);
LOCK_DEBUG (g_message ("%s: (%d) Regained %p lock %p", __func__, mono_thread_info_get_small_id (), obj, mon));
- if (ret == WAIT_TIMEOUT) {
+ if (ret == MONO_W32HANDLE_WAIT_RET_TIMEOUT) {
/* Poll the event again, just in case it was signalled
* while we were trying to regain the monitor lock
*/
MONO_ENTER_GC_SAFE;
- ret = WaitForSingleObjectEx (event, 0, FALSE);
+#ifdef HOST_WIN32
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_single_object_ex (event, 0, FALSE), 1);
+#else
+ ret = mono_w32handle_wait_one (event, 0, FALSE);
+#endif /* HOST_WIN32 */
MONO_EXIT_GC_SAFE;
}
* thread.
*/
- if (ret == WAIT_OBJECT_0) {
+ if (ret == MONO_W32HANDLE_WAIT_RET_SUCCESS_0) {
LOCK_DEBUG (g_message ("%s: (%d) Success", __func__, mono_thread_info_get_small_id ()));
success = TRUE;
} else {
*/
mon->wait_list = g_slist_remove (mon->wait_list, event);
}
- CloseHandle (event);
+ mono_w32event_close (event);
return success;
}