#include <mono/utils/mono-time.h>
#include <mono/utils/dtrace.h>
#include <mono/utils/mono-threads.h>
+#include <mono/utils/mono-threads-coop.h>
#include <mono/utils/atomic.h>
#include <mono/utils/mono-coop-semaphore.h>
#include <mono/utils/hazard-pointer.h>
#endif
typedef struct DomainFinalizationReq {
+ gint32 ref;
MonoDomain *domain;
- HANDLE done_event;
+ MonoCoopSem done;
} DomainFinalizationReq;
-static gboolean gc_disabled = FALSE;
+static gboolean gc_disabled;
-static gboolean finalizing_root_domain = FALSE;
+static gboolean finalizing_root_domain;
-gboolean log_finalizers = FALSE;
-gboolean mono_do_not_finalize = FALSE;
-gchar **mono_do_not_finalize_class_names = NULL;
+gboolean log_finalizers;
+gboolean mono_do_not_finalize;
+volatile gboolean suspend_finalizers;
+gchar **mono_do_not_finalize_class_names ;
#define mono_finalizer_lock() mono_coop_mutex_lock (&finalizer_mutex)
#define mono_finalizer_unlock() mono_coop_mutex_unlock (&finalizer_mutex)
static MonoCoopMutex finalizer_mutex;
static MonoCoopMutex reference_queue_mutex;
-static GSList *domains_to_finalize= NULL;
-static MonoMList *threads_to_finalize = NULL;
+static GSList *domains_to_finalize;
+static MonoMList *threads_to_finalize;
static gboolean finalizer_thread_exited;
/* Uses finalizer_mutex */
static MonoInternalThread *gc_thread;
-static void object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*), MonoError *error);
+#ifdef TARGET_WIN32
+static HANDLE pending_done_event;
+#else
+static gboolean pending_done;
+static MonoCoopCond pending_done_cond;
+static MonoCoopMutex pending_done_mutex;
+#endif
+
+static void object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*));
static void reference_queue_proccess_all (void);
static void mono_reference_queue_cleanup (void);
static void reference_queue_clear_for_domain (MonoDomain *domain);
-static HANDLE pending_done_event;
+
static guint32
guarded_wait (HANDLE handle, guint32 timeout, gboolean alertable)
{
guint32 result;
- MONO_PREPARE_BLOCKING;
+ MONO_ENTER_GC_SAFE;
result = WaitForSingleObjectEx (handle, timeout, alertable);
- MONO_FINISH_BLOCKING;
+ MONO_EXIT_GC_SAFE;
return result;
}
-static void
-add_thread_to_finalize (MonoInternalThread *thread)
+typedef struct {
+ MonoCoopCond *cond;
+ MonoCoopMutex *mutex;
+} BreakCoopAlertableWaitUD;
+
+static inline void
+break_coop_alertable_wait (gpointer user_data)
+{
+ BreakCoopAlertableWaitUD *ud = (BreakCoopAlertableWaitUD*)user_data;
+
+ mono_coop_mutex_lock (ud->mutex);
+ mono_coop_cond_signal (ud->cond);
+ mono_coop_mutex_unlock (ud->mutex);
+}
+
+/*
+ * coop_cond_timedwait_alertable:
+ *
+ * Wait on COND/MUTEX. If ALERTABLE is non-null, the wait can be interrupted.
+ * In that case, *ALERTABLE will be set to TRUE, and 0 is returned.
+ */
+static inline gint
+coop_cond_timedwait_alertable (MonoCoopCond *cond, MonoCoopMutex *mutex, guint32 timeout_ms, gboolean *alertable)
+{
+ int res;
+
+ if (alertable) {
+ BreakCoopAlertableWaitUD ud;
+
+ *alertable = FALSE;
+ ud.cond = cond;
+ ud.mutex = mutex;
+ mono_thread_info_install_interrupt (break_coop_alertable_wait, &ud, alertable);
+ if (*alertable)
+ return 0;
+ }
+ res = mono_coop_cond_timedwait (cond, mutex, timeout_ms);
+ if (alertable) {
+ mono_thread_info_uninstall_interrupt (alertable);
+ if (*alertable)
+ return 0;
+ }
+ return res;
+}
+
+static gboolean
+add_thread_to_finalize (MonoInternalThread *thread, MonoError *error)
{
+ mono_error_init (error);
mono_finalizer_lock ();
if (!threads_to_finalize)
MONO_GC_REGISTER_ROOT_SINGLE (threads_to_finalize, MONO_ROOT_SOURCE_FINALIZER_QUEUE, "finalizable threads list");
- threads_to_finalize = mono_mlist_append (threads_to_finalize, (MonoObject*)thread);
+ threads_to_finalize = mono_mlist_append_checked (threads_to_finalize, (MonoObject*)thread, error);
mono_finalizer_unlock ();
+ return is_ok (error);
}
-static gboolean suspend_finalizers = FALSE;
/*
* actually, we might want to queue the finalize requests in a separate thread,
* but we need to be careful about the execution domain of the thread...
#endif
/* make sure the finalizer is not called again if the object is resurrected */
- object_register_finalizer ((MonoObject *)obj, NULL, &error);
- mono_error_assert_ok (&error); /* FIXME don't swallow the error */
+ object_register_finalizer ((MonoObject *)obj, NULL);
if (log_finalizers)
g_log ("mono-gc-finalizers", G_LOG_LEVEL_MESSAGE, "<%s at %p> Registered finalizer as processed.", o->vtable->klass->name, o);
/* Don't finalize threadpool threads when
shutting down - they're finalized when the
threadpool shuts down. */
- add_thread_to_finalize (t);
+ if (!add_thread_to_finalize (t, &error))
+ goto unhandled_error;
return;
}
}
if (!domain->finalize_runtime_invoke) {
MonoMethod *invoke = mono_marshal_get_runtime_invoke (mono_class_get_method_from_name_flags (mono_defaults.object_class, "Finalize", 0, 0), TRUE);
- domain->finalize_runtime_invoke = mono_compile_method (invoke);
+ domain->finalize_runtime_invoke = mono_compile_method_checked (invoke, &error);
+ mono_error_assert_ok (&error); /* expect this not to fail */
}
runtime_invoke = (RuntimeInvokeFunction)domain->finalize_runtime_invoke;
mono_runtime_class_init_full (o->vtable, &error);
- mono_error_raise_exception (&error); /* FIXME don't raise here */
+ if (!is_ok (&error))
+ goto unhandled_error;
if (G_UNLIKELY (MONO_GC_FINALIZE_INVOKE_ENABLED ())) {
MONO_GC_FINALIZE_INVOKE ((unsigned long)o, mono_object_get_size (o),
if (log_finalizers)
g_log ("mono-gc-finalizers", G_LOG_LEVEL_MESSAGE, "<%s at %p> Returned from finalizer.", o->vtable->klass->name, o);
+unhandled_error:
+ if (!is_ok (&error))
+ exc = (MonoObject*)mono_error_convert_to_exception (&error);
if (exc)
mono_thread_internal_unhandled_exception (exc);
void
mono_gc_finalize_threadpool_threads (void)
{
- MonoError error;
while (threads_to_finalize) {
MonoInternalThread *thread = (MonoInternalThread*) mono_mlist_get_data (threads_to_finalize);
/* Force finalization of the thread. */
thread->threadpool_thread = FALSE;
- mono_object_register_finalizer ((MonoObject*)thread, &error);
- mono_error_assert_ok (&error); /* FIXME don't swallow the error */
+ mono_object_register_finalizer ((MonoObject*)thread);
mono_gc_run_finalize (thread, NULL);
* since that, too, can cause the underlying pointer to be offset.
*/
static void
-object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*), MonoError *error)
+object_register_finalizer (MonoObject *obj, void (*callback)(void *, void*))
{
MonoDomain *domain;
- mono_error_init (error);
-
- if (obj == NULL) {
- mono_error_set_argument_null (error, "obj", "");
- return;
- }
+ g_assert (obj != NULL);
domain = obj->vtable->domain;
*
*/
void
-mono_object_register_finalizer (MonoObject *obj, MonoError *error)
+mono_object_register_finalizer (MonoObject *obj)
{
/* g_print ("Registered finalizer on %p %s.%s\n", obj, mono_object_class (obj)->name_space, mono_object_class (obj)->name); */
- object_register_finalizer (obj, mono_gc_run_finalize, error);
+ object_register_finalizer (obj, mono_gc_run_finalize);
}
/**
mono_domain_finalize (MonoDomain *domain, guint32 timeout)
{
DomainFinalizationReq *req;
- guint32 res;
- HANDLE done_event;
MonoInternalThread *thread = mono_thread_internal_current ();
+ gint res;
+ gboolean ret;
+ gint64 start;
#if defined(__native_client__)
return FALSE;
mono_gc_collect (mono_gc_max_generation ());
- done_event = CreateEvent (NULL, TRUE, FALSE, NULL);
- if (done_event == NULL) {
- return FALSE;
- }
-
req = g_new0 (DomainFinalizationReq, 1);
+ req->ref = 2;
req->domain = domain;
- req->done_event = done_event;
+ mono_coop_sem_init (&req->done, 0);
if (domain == mono_get_root_domain ())
finalizing_root_domain = TRUE;
if (timeout == -1)
timeout = INFINITE;
+ if (timeout != INFINITE)
+ start = mono_msec_ticks ();
- while (TRUE) {
- res = guarded_wait (done_event, timeout, TRUE);
- /* printf ("WAIT RES: %d.\n", res); */
+ ret = TRUE;
- if (res == WAIT_IO_COMPLETION) {
- if ((thread->state & (ThreadState_StopRequested | ThreadState_SuspendRequested)) != 0)
- return FALSE;
- } else if (res == WAIT_TIMEOUT) {
- /* We leak the handle here */
- return FALSE;
+ for (;;) {
+ if (timeout == INFINITE) {
+ res = mono_coop_sem_wait (&req->done, MONO_SEM_FLAGS_ALERTABLE);
} else {
+ gint64 elapsed = mono_msec_ticks () - start;
+ if (elapsed >= timeout) {
+ ret = FALSE;
+ break;
+ }
+
+ res = mono_coop_sem_timedwait (&req->done, timeout - elapsed, MONO_SEM_FLAGS_ALERTABLE);
+ }
+
+ if (res == MONO_SEM_TIMEDWAIT_RET_SUCCESS) {
break;
+ } else if (res == MONO_SEM_TIMEDWAIT_RET_ALERTED) {
+ if ((thread->state & (ThreadState_StopRequested | ThreadState_SuspendRequested)) != 0) {
+ ret = FALSE;
+ break;
+ }
+ } else if (res == MONO_SEM_TIMEDWAIT_RET_TIMEDOUT) {
+ ret = FALSE;
+ break;
+ } else {
+ g_error ("%s: unknown result %d", __func__, res);
}
}
- CloseHandle (done_event);
+ if (!ret) {
+ /* Try removing the req from domains_to_finalize:
+ * - if it's not found: the domain is being finalized,
+ * so we the ref count is already decremented
+ * - if it's found: the domain is not yet being finalized,
+ * so we can safely decrement the ref */
+
+ gboolean found;
+
+ mono_finalizer_lock ();
+
+ found = g_slist_index (domains_to_finalize, req) != -1;
+ if (found)
+ domains_to_finalize = g_slist_remove (domains_to_finalize, req);
+
+ mono_finalizer_unlock ();
+
+ if (found) {
+ /* We have to decrement it wherever we
+ * remove it from domains_to_finalize */
+ if (InterlockedDecrement (&req->ref) != 1)
+ g_error ("%s: req->ref should be 1, as we are the first one to decrement it", __func__);
+ }
+
+ goto done;
+ }
if (domain == mono_get_root_domain ()) {
mono_threadpool_ms_cleanup ();
mono_profiler_appdomain_event (domain, MONO_PROFILE_END_UNLOAD);
- return TRUE;
+done:
+ if (InterlockedDecrement (&req->ref) == 0) {
+ mono_coop_sem_destroy (&req->done);
+ g_free (req);
+ }
+
+ return ret;
}
void
void
ves_icall_System_GC_ReRegisterForFinalize (MonoObject *obj)
{
- MonoError error;
-
MONO_CHECK_ARG_NULL (obj,);
- object_register_finalizer (obj, mono_gc_run_finalize, &error);
- mono_error_set_pending_exception (&error);
+ object_register_finalizer (obj, mono_gc_run_finalize);
}
void
ves_icall_System_GC_SuppressFinalize (MonoObject *obj)
{
- MonoError error;
-
MONO_CHECK_ARG_NULL (obj,);
/* delegates have no finalizers, but we register them to deal with the
* generated for it that needs cleaned up, but user wants to suppress
* their derived object finalizer. */
- object_register_finalizer (obj, NULL, &error);
- mono_error_set_pending_exception (&error);
+ object_register_finalizer (obj, NULL);
}
void
if (gc_thread == NULL)
return;
+#ifdef TARGET_WIN32
ResetEvent (pending_done_event);
mono_gc_finalize_notify ();
/* g_print ("Waiting for pending finalizers....\n"); */
guarded_wait (pending_done_event, INFINITE, TRUE);
/* g_print ("Done pending....\n"); */
+#else
+ gboolean alerted = FALSE;
+ mono_coop_mutex_lock (&pending_done_mutex);
+ pending_done = FALSE;
+ mono_gc_finalize_notify ();
+ while (!pending_done) {
+ coop_cond_timedwait_alertable (&pending_done_cond, &pending_done_mutex, INFINITE, &alerted);
+ if (alerted)
+ break;
+ }
+ mono_coop_mutex_unlock (&pending_done_mutex);
+#endif
}
void
}
static MonoCoopSem finalizer_sem;
-static volatile gboolean finished=FALSE;
+static volatile gboolean finished;
+/*
+ * mono_gc_finalize_notify:
+ *
+ * Notify the finalizer thread that finalizers etc.
+ * are available to be processed.
+ */
void
mono_gc_finalize_notify (void)
{
* Run the finalizers of all finalizable objects in req->domain.
*/
static void
-finalize_domain_objects (DomainFinalizationReq *req)
+finalize_domain_objects (void)
{
- MonoDomain *domain = req->domain;
+ DomainFinalizationReq *req = NULL;
+ MonoDomain *domain;
-#if HAVE_SGEN_GC
-#define NUM_FOBJECTS 64
- MonoObject *to_finalize [NUM_FOBJECTS];
- int count;
-#endif
+ if (domains_to_finalize) {
+ mono_finalizer_lock ();
+ if (domains_to_finalize) {
+ req = (DomainFinalizationReq *)domains_to_finalize->data;
+ domains_to_finalize = g_slist_remove (domains_to_finalize, req);
+ }
+ mono_finalizer_unlock ();
+ }
+
+ if (!req)
+ return;
+
+ domain = req->domain;
/* Process finalizers which are already in the queue */
mono_gc_invoke_finalizers ();
g_ptr_array_free (objs, TRUE);
}
#elif defined(HAVE_SGEN_GC)
- while ((count = mono_gc_finalizers_for_domain (domain, to_finalize, NUM_FOBJECTS))) {
- int i;
- for (i = 0; i < count; ++i) {
- mono_gc_run_finalize (to_finalize [i], 0);
- }
- }
+ mono_gc_finalize_domain (domain);
+ mono_gc_invoke_finalizers ();
#endif
/* cleanup the reference queue */
reference_queue_clear_for_domain (domain);
/* printf ("DONE.\n"); */
- SetEvent (req->done_event);
+ mono_coop_sem_post (&req->done);
- /* The event is closed in mono_domain_finalize if we get here */
- g_free (req);
+ if (InterlockedDecrement (&req->ref) == 0) {
+ /* mono_domain_finalize already returned, and
+ * doesn't hold a reference to req anymore. */
+ mono_coop_sem_destroy (&req->done);
+ g_free (req);
+ }
}
static guint32
finalizer_thread (gpointer unused)
{
MonoError error;
+ gboolean wait = TRUE;
+
mono_thread_set_name_internal (mono_thread_internal_current (), mono_string_new (mono_get_root_domain (), "Finalizer"), FALSE, &error);
mono_error_assert_ok (&error);
- gboolean wait = TRUE;
-
/* Register a hazard free queue pump callback */
mono_hazard_pointer_install_free_queue_size_callback (hazard_free_queue_is_too_big);
mono_attach_maybe_start ();
- if (domains_to_finalize) {
- mono_finalizer_lock ();
- if (domains_to_finalize) {
- DomainFinalizationReq *req = (DomainFinalizationReq *)domains_to_finalize->data;
- domains_to_finalize = g_slist_remove (domains_to_finalize, req);
- mono_finalizer_unlock ();
-
- finalize_domain_objects (req);
- } else {
- mono_finalizer_unlock ();
- }
- }
+ finalize_domain_objects ();
/* If finished == TRUE, mono_gc_cleanup has been called (from mono_runtime_cleanup),
* before the domain is unloaded.
hazard_free_queue_pump ();
/* Avoid posting the pending done event until there are pending finalizers */
- if (mono_coop_sem_timedwait (&finalizer_sem, 0, MONO_SEM_FLAGS_NONE) == 0) {
+ if (mono_coop_sem_timedwait (&finalizer_sem, 0, MONO_SEM_FLAGS_NONE) == MONO_SEM_TIMEDWAIT_RET_SUCCESS) {
/* Don't wait again at the start of the loop */
wait = FALSE;
} else {
+#ifdef TARGET_WIN32
SetEvent (pending_done_event);
+#else
+ mono_coop_mutex_lock (&pending_done_mutex);
+ pending_done = TRUE;
+ mono_coop_cond_signal (&pending_done_cond);
+ mono_coop_mutex_unlock (&pending_done_mutex);
+#endif
}
}
return;
}
+#ifdef TARGET_WIN32
pending_done_event = CreateEvent (NULL, TRUE, FALSE, NULL);
g_assert (pending_done_event);
+#else
+ mono_coop_cond_init (&pending_done_cond);
+ mono_coop_mutex_init (&pending_done_mutex);
+#endif
+
mono_coop_cond_init (&exited_cond);
mono_coop_sem_init (&finalizer_sem, 0);
if (!gc_disabled) {
finished = TRUE;
if (mono_thread_internal_current () != gc_thread) {
- gboolean timed_out = FALSE;
gint64 start_ticks = mono_msec_ticks ();
gint64 end_ticks = start_ticks + 2000;
/* Set a flag which the finalizer thread can check */
suspend_finalizers = TRUE;
+ mono_gc_suspend_finalizers ();
/* Try to abort the thread, in the hope that it is running managed code */
- mono_thread_internal_stop (gc_thread);
+ mono_thread_internal_abort (gc_thread);
/* Wait for it to stop */
ret = guarded_wait (gc_thread->handle, 100, TRUE);
if (ret == WAIT_TIMEOUT) {
- /*
- * The finalizer thread refused to die. There is not much we
- * can do here, since the runtime is shutting down so the
- * state the finalizer thread depends on will vanish.
+ /*
+ * The finalizer thread refused to exit. Make it stop.
*/
- g_warning ("Shutting down finalizer thread timed out.");
- timed_out = TRUE;
+ mono_thread_internal_stop (gc_thread);
+ ret = guarded_wait (gc_thread->handle, 100, TRUE);
+ g_assert (ret != WAIT_TIMEOUT);
+ /* The thread can't set this flag */
+ finalizer_thread_exited = TRUE;
}
}
- if (!timed_out) {
- int ret;
+ int ret;
- /* Wait for the thread to actually exit */
- ret = guarded_wait (gc_thread->handle, INFINITE, TRUE);
- g_assert (ret == WAIT_OBJECT_0);
+ /* Wait for the thread to actually exit */
+ ret = guarded_wait (gc_thread->handle, INFINITE, TRUE);
+ g_assert (ret == WAIT_OBJECT_0);
- mono_thread_join (GUINT_TO_POINTER (gc_thread->tid));
- }
+ mono_thread_join (GUINT_TO_POINTER (gc_thread->tid));
g_assert (finalizer_thread_exited);
}
gc_thread = NULL;
gboolean
mono_gc_reference_queue_add (MonoReferenceQueue *queue, MonoObject *obj, void *user_data)
{
- MonoError error;
RefQueueEntry *entry;
if (queue->should_be_deleted)
return FALSE;
+ g_assert (obj != NULL);
+
entry = g_new0 (RefQueueEntry, 1);
entry->user_data = user_data;
entry->domain = mono_object_domain (obj);
entry->gchandle = mono_gchandle_new_weakref (obj, TRUE);
- mono_object_register_finalizer (obj, &error);
- mono_error_assert_ok (&error);
+ mono_object_register_finalizer (obj);
ref_list_push (&queue->queue, entry);
return TRUE;