+/*
+ * os-event-unix.c: MonoOSEvent on Unix
+ *
+ * Author:
+ * Ludovic Henry (luhenry@microsoft.com)
+ *
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
+ */
+
+#include "os-event.h"
+
+#include "atomic.h"
+#include "mono-lazy-init.h"
+#include "mono-threads.h"
+#include "mono-time.h"
+
+static mono_lazy_init_t status = MONO_LAZY_INIT_STATUS_NOT_INITIALIZED;
+
+static mono_mutex_t signal_mutex;
+static mono_cond_t signal_cond;
+
+static void
+initialize (void)
+{
+ mono_os_mutex_init (&signal_mutex);
+ mono_os_cond_init (&signal_cond);
+}
+
+void
+mono_os_event_init (MonoOSEvent *event, gboolean manual, gboolean initial)
+{
+ g_assert (event);
+
+ mono_lazy_initialize (&status, initialize);
+
+ mono_os_mutex_init (&event->mutex);
+ mono_os_cond_init (&event->cond);
+ event->signalled = initial;
+ event->manual = manual;
+ event->set_count = (initial && !manual) ? 1 : 0;
+}
+
+void
+mono_os_event_destroy (MonoOSEvent *event)
+{
+ g_assert (mono_lazy_is_initialized (&status));
+
+ g_assert (event);
+
+ mono_os_mutex_destroy (&event->mutex);
+ mono_os_cond_destroy (&event->cond);
+}
+
+static void
+mono_os_event_signal (MonoOSEvent *event, gboolean broadcast)
+{
+ g_assert (event);
+
+ mono_os_mutex_lock (&signal_mutex);
+
+ event->signalled = TRUE;
+
+ if (broadcast)
+ mono_os_cond_broadcast (&event->cond);
+ else
+ mono_os_cond_signal (&event->cond);
+
+ mono_os_cond_broadcast (&signal_cond);
+
+ mono_os_mutex_unlock (&signal_mutex);
+}
+
+void
+mono_os_event_set (MonoOSEvent *event)
+{
+ g_assert (mono_lazy_is_initialized (&status));
+
+ g_assert (event);
+
+ mono_os_mutex_lock (&event->mutex);
+
+ if (event->manual) {
+ mono_os_event_signal (event, FALSE);
+ } else {
+ event->set_count = 1;
+ mono_os_event_signal (event, TRUE);
+ }
+
+ mono_os_mutex_unlock (&event->mutex);
+}
+
+void
+mono_os_event_reset (MonoOSEvent *event)
+{
+ g_assert (mono_lazy_is_initialized (&status));
+
+ g_assert (event);
+
+ mono_os_mutex_lock (&event->mutex);
+
+ if (event->signalled)
+ event->signalled = FALSE;
+
+ event->set_count = 0;
+
+ mono_os_mutex_unlock (&event->mutex);
+}
+
+static gboolean
+mono_os_event_own (MonoOSEvent *event)
+{
+ g_assert (event);
+
+ if (!event->signalled)
+ return FALSE;
+
+ if (!event->manual) {
+ g_assert (event->set_count > 0);
+ event->set_count -= 1;
+
+ if (event->set_count == 0)
+ mono_os_event_signal (event, FALSE);
+ }
+
+ return TRUE;
+}
+
+MonoOSEventWaitRet
+mono_os_event_wait_one (MonoOSEvent *event, guint32 timeout)
+{
+ MonoOSEventWaitRet ret;
+ gint64 start;
+
+ g_assert (mono_lazy_is_initialized (&status));
+
+ g_assert (event);
+
+ mono_os_mutex_lock (&event->mutex);
+
+ if (timeout != MONO_INFINITE_WAIT)
+ start = mono_msec_ticks ();
+
+ for (;;) {
+ if (mono_os_event_own (event)) {
+ ret = MONO_OS_EVENT_WAIT_RET_SUCCESS_0;
+ goto done;
+ }
+
+ if (timeout == MONO_INFINITE_WAIT) {
+ mono_os_cond_wait (&event->cond, &event->mutex);
+ } else {
+ gint64 elapsed;
+ gint res;
+
+ elapsed = mono_msec_ticks () - start;
+ if (elapsed >= timeout) {
+ ret = MONO_OS_EVENT_WAIT_RET_TIMEOUT;
+ goto done;
+ }
+
+ res = mono_os_cond_timedwait (&event->cond, &event->mutex, timeout - elapsed);
+ if (res != 0) {
+ ret = MONO_OS_EVENT_WAIT_RET_TIMEOUT;
+ goto done;
+ }
+ }
+ }
+
+done:
+ mono_os_mutex_unlock (&event->mutex);
+
+ return ret;
+}
+
+static void
+mono_os_event_lock_events (MonoOSEvent **events, gsize nevents)
+{
+ gint i, j;
+
+retry:
+ for (i = 0; i < nevents; ++i) {
+ gint res;
+
+ res = mono_os_mutex_trylock (&events [i]->mutex);
+ if (res != 0) {
+ for (j = i - 1; j >= 0; j--)
+ mono_os_mutex_unlock (&events [j]->mutex);
+
+ mono_thread_info_yield ();
+
+ goto retry;
+ }
+ }
+}
+
+static void
+mono_os_event_unlock_events (MonoOSEvent **events, gsize nevents)
+{
+ gint i;
+
+ for (i = 0; i < nevents; ++i)
+ mono_os_mutex_unlock (&events [i]->mutex);
+}
+
+MonoOSEventWaitRet
+mono_os_event_wait_multiple (MonoOSEvent **events, gsize nevents, gboolean waitall, guint32 timeout)
+{
+ MonoOSEventWaitRet ret;
+ gint64 start;
+ gint i;
+
+ g_assert (mono_lazy_is_initialized (&status));
+
+ g_assert (events);
+ g_assert (nevents > 0);
+ g_assert (nevents <= MONO_OS_EVENT_WAIT_MAXIMUM_OBJECTS);
+
+ if (nevents == 1)
+ return mono_os_event_wait_one (events [0], timeout);
+
+ for (i = 0; i < nevents; ++i) {
+ g_assert (events [i]);
+ }
+
+ if (timeout != MONO_INFINITE_WAIT)
+ start = mono_msec_ticks ();
+
+ for (;;) {
+ gint count, lowest;
+ gboolean signalled;
+
+ mono_os_event_lock_events (events, nevents);
+
+ count = 0;
+ lowest = -1;
+
+ for (i = 0; i < nevents; ++i) {
+ if (events [i]->signalled) {
+ count += 1;
+ if (lowest == -1)
+ lowest = i;
+ }
+ }
+
+ signalled = (waitall && count == nevents) || (!waitall && count > 0);
+
+ if (signalled) {
+ for (i = 0; i < nevents; ++i)
+ mono_os_event_own (events [i]);
+ }
+
+ mono_os_event_unlock_events (events, nevents);
+
+ if (signalled) {
+ ret = MONO_OS_EVENT_WAIT_RET_SUCCESS_0 + lowest;
+ goto done;
+ }
+
+ mono_os_mutex_lock (&signal_mutex);
+
+ if (waitall) {
+ signalled = TRUE;
+ for (i = 0; i < nevents; ++i) {
+ if (!events [i]->signalled) {
+ signalled = FALSE;
+ break;
+ }
+ }
+ } else {
+ signalled = FALSE;
+ for (i = 0; i < nevents; ++i) {
+ if (events [i]->signalled) {
+ signalled = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (signalled) {
+ mono_os_mutex_unlock (&signal_mutex);
+ continue;
+ }
+
+ if (timeout == MONO_INFINITE_WAIT) {
+ mono_os_cond_wait (&signal_cond, &signal_mutex);
+ } else {
+ gint64 elapsed;
+ gint res;
+
+ elapsed = mono_msec_ticks () - start;
+ if (elapsed >= timeout) {
+ mono_os_mutex_unlock (&signal_mutex);
+
+ ret = MONO_OS_EVENT_WAIT_RET_TIMEOUT;
+ goto done;
+ }
+
+ res = mono_os_cond_timedwait (&signal_cond, &signal_mutex, timeout - elapsed);
+ if (res != 0) {
+ mono_os_mutex_unlock (&signal_mutex);
+
+ ret = MONO_OS_EVENT_WAIT_RET_TIMEOUT;
+ goto done;
+ }
+ }
+
+ mono_os_mutex_unlock (&signal_mutex);
+ }
+
+done:
+ return ret;
+}