/*#define LIBGC_DEBUG(a) do { a; } while (0)*/
#define LIBGC_DEBUG(a)
+/* Provide this for systems with glib < 2.6 */
+#ifndef G_GSIZE_FORMAT
+# if GLIB_SIZEOF_LONG == 8
+# define G_GSIZE_FORMAT "lu"
+# else
+# define G_GSIZE_FORMAT "u"
+# endif
+#endif
+
struct StartInfo
{
guint32 (*func)(void *);
#define CULTURES_START_IDX 0
#define UICULTURES_START_IDX NUM_CACHED_CULTURES
-/*
- * The "os_handle" field of the WaitHandle class.
- */
-static MonoClassField *wait_handle_os_handle_field = NULL;
-
/* Controls access to the 'threads' hash table */
#define mono_threads_lock() EnterCriticalSection (&threads_mutex)
#define mono_threads_unlock() LeaveCriticalSection (&threads_mutex)
static MonoThreadAttachCB mono_thread_attach_cb = NULL;
/* function called at thread cleanup */
-static MonoThreadCleanupFunc mono_thread_cleanup = NULL;
+static MonoThreadCleanupFunc mono_thread_cleanup_fn = NULL;
/* The default stack size for each thread */
static guint32 default_stacksize = 0;
static guint32 mono_alloc_static_data_slot (StaticDataInfo *static_data, guint32 size, guint32 align);
static gboolean mono_thread_resume (MonoThread* thread);
static void mono_thread_start (MonoThread *thread);
+static void signal_thread_state_change (MonoThread *thread);
/* Spin lock for InterlockedXXX 64 bit functions */
#define mono_interlocked_lock() EnterCriticalSection (&interlocked_mutex)
*/
}
-/*
- * Tell the Mono Debugger about a newly created thread.
- * mono_debugger_event() is a no-op if we're not running inside the debugger.
- */
-static void debugger_thread_created (MonoThread *thread)
-{
- mono_debugger_event (MONO_DEBUGGER_EVENT_THREAD_CREATED,
- (guint64) (gsize) &thread->end_stack, thread->tid);
-}
-
-/*
- * Tell the Mono Debugger that a thrad is about to exit.
- * mono_debugger_event() is a no-op if we're not running inside the debugger.
- */
-static void debugger_thread_exited (MonoThread *thread)
-{
- mono_debugger_event (MONO_DEBUGGER_EVENT_THREAD_EXITED,
- (guint64) (gsize) &thread->end_stack, thread->tid);
-}
-
static void thread_cleanup (MonoThread *thread)
{
g_assert (thread != NULL);
thread->cached_culture_info = NULL;
- debugger_thread_exited (thread);
-
- if (mono_thread_cleanup)
- mono_thread_cleanup (thread);
+ if (mono_thread_cleanup_fn)
+ mono_thread_cleanup_fn (thread);
}
static guint32 WINAPI start_wrapper(void *data)
struct StartInfo *start_info=(struct StartInfo *)data;
guint32 (*start_func)(void *);
void *start_arg;
- guint32 tid;
+ gsize tid;
MonoThread *thread=start_info->obj;
MonoObject *start_delegate = start_info->delegate;
THREAD_DEBUG (g_message ("%s: (%"G_GSIZE_FORMAT") Start wrapper", __func__, GetCurrentThreadId ()));
-
+
/* We can be sure start_info->obj->tid and
* start_info->obj->handle have been set, because the thread
* was created suspended, and these values were set before the
tid=thread->tid;
SET_CURRENT_OBJECT (thread);
+
+ /* Every thread references the appdomain which created it */
+ mono_thread_push_appdomain_ref (start_info->domain);
if (!mono_domain_set (start_info->domain, FALSE)) {
/* No point in raising an appdomain_unloaded exception here */
/* FIXME: Cleanup here */
+ mono_thread_pop_appdomain_ref ();
return 0;
}
mono_thread_new_init (tid, &tid, start_func);
thread->stack_ptr = &tid;
- debugger_thread_created (thread);
-
LIBGC_DEBUG (g_message ("%s: (%"G_GSIZE_FORMAT",%d) Setting thread stack to %p", __func__, GetCurrentThreadId (), getpid (), thread->stack_ptr));
THREAD_DEBUG (g_message ("%s: (%"G_GSIZE_FORMAT") Setting current_object_key to %p", __func__, GetCurrentThreadId (), thread));
g_free (start_info);
- /* Every thread references the appdomain which created it */
- mono_thread_push_appdomain_ref (mono_domain_get ());
-
thread_adjust_static_data (thread);
#ifdef DEBUG
g_message ("%s: start_wrapper for %"G_GSIZE_FORMAT, __func__,
thread->tid);
#endif
- /* start_func is set only for unamanged start functions */
+ /* start_func is set only for unmanaged start functions */
if (start_func) {
start_func (start_arg);
} else {
THREAD_DEBUG (g_message ("%s: (%"G_GSIZE_FORMAT") Start wrapper terminating", __func__, GetCurrentThreadId ()));
+ thread_cleanup (thread);
+
/* Remove the reference to the thread object in the TLS data,
* so the thread object can be finalized. This won't be
* reached if the thread threw an uncaught exception, so those
*/
SET_CURRENT_OBJECT (NULL);
- thread_cleanup (thread);
-
return(0);
}
HANDLE thread_handle;
struct StartInfo *start_info;
gsize tid;
-
+
thread=(MonoThread *)mono_object_new (domain,
mono_defaults.thread_class);
ResumeThread (thread_handle);
}
+/*
+ * mono_thread_get_stack_bounds:
+ *
+ * Return the address and size of the current threads stack. Return NULL as the stack
+ * address if the stack address cannot be determined.
+ */
+static void
+mono_thread_get_stack_bounds (guint8 **staddr, size_t *stsize)
+{
+#ifndef PLATFORM_WIN32
+ pthread_attr_t attr;
+ guint8 *current = (guint8*)&attr;
+
+ pthread_attr_init (&attr);
+#ifdef HAVE_PTHREAD_GETATTR_NP
+ pthread_getattr_np (pthread_self(), &attr);
+#else
+#ifdef HAVE_PTHREAD_ATTR_GET_NP
+ pthread_attr_get_np (pthread_self(), &attr);
+#elif defined(sun)
+ *staddr = NULL;
+ pthread_attr_getstacksize (&attr, &stsize);
+#else
+ *staddr = NULL;
+ *stsize = 0;
+ return;
+#endif
+#endif
+
+#ifndef sun
+ pthread_attr_getstack (&attr, (void**)staddr, stsize);
+ if (*staddr)
+ g_assert ((current > *staddr) && (current < *staddr + *stsize));
+#endif
+#endif
+}
+
MonoThread *
mono_thread_attach (MonoDomain *domain)
{
}
if (!mono_gc_register_thread (&domain)) {
- g_error ("Thread %p calling into managed code is not registered with the GC. On UNIX, this can be fixed by #include-ing <gc.h> before <pthread.h> in the file containing the thread creation code.", GetCurrentThread ());
+ g_error ("Thread %"G_GSIZE_FORMAT" calling into managed code is not registered with the GC. On UNIX, this can be fixed by #include-ing <gc.h> before <pthread.h> in the file containing the thread creation code.", GetCurrentThreadId ());
}
thread = (MonoThread *)mono_object_new (domain,
tid=GetCurrentThreadId ();
-#ifdef PLATFORM_WIN32
/*
* The handle returned by GetCurrentThread () is a pseudo handle, so it can't be used to
* refer to the thread from other threads for things like aborting.
*/
DuplicateHandle (GetCurrentProcess (), thread_handle, GetCurrentProcess (), &thread_handle,
THREAD_ALL_ACCESS, TRUE, 0);
-#endif
thread->handle=thread_handle;
thread->tid=tid;
SET_CURRENT_OBJECT (thread);
mono_domain_set (domain, TRUE);
- debugger_thread_created (thread);
-
thread_adjust_static_data (thread);
if (mono_thread_attach_cb) {
- mono_thread_attach_cb (tid, &tid);
+ guint8 *staddr;
+ size_t stsize;
+
+ mono_thread_get_stack_bounds (&staddr, &stsize);
+
+ if (staddr == NULL)
+ mono_thread_attach_cb (tid, &tid);
+ else
+ mono_thread_attach_cb (tid, staddr + stsize);
}
return(thread);
g_return_if_fail (thread != NULL);
THREAD_DEBUG (g_message ("%s: mono_thread_detach for %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
- SET_CURRENT_OBJECT (NULL);
thread_cleanup (thread);
+ SET_CURRENT_OBJECT (NULL);
+
/* Don't need to CloseHandle this thread, even though we took a
* reference in mono_thread_attach (), because the GC will do it
* when the Thread object is finalised.
{
MonoThread *thread = mono_thread_current ();
- SET_CURRENT_OBJECT (NULL);
thread_cleanup (thread);
+ SET_CURRENT_OBJECT (NULL);
/* we could add a callback here for embedders to use. */
if (thread == mono_thread_get_main ())
THREAD_DEBUG (g_message ("%s: Sleeping for %d ms", __func__, ms));
+ mono_thread_current_check_pending_interrupt ();
+
mono_monitor_enter (thread->synch_lock);
thread->state |= ThreadState_WaitSleepJoin;
mono_monitor_exit (thread->synch_lock);
mono_monitor_exit (thread->synch_lock);
}
+void ves_icall_System_Threading_Thread_SpinWait_internal (gint32 iterations)
+{
+ gint32 i;
+
+ for(i = 0; i < iterations; i++) {
+ /* We're busy waiting, but at least we can tell the
+ * scheduler to let someone else have a go...
+ */
+ Sleep (0);
+ }
+}
+
gint32
ves_icall_System_Threading_Thread_GetDomainID (void)
{
return FALSE;
}
+ mono_thread_current_check_pending_interrupt ();
+
this->state |= ThreadState_WaitSleepJoin;
mono_monitor_exit (this->synch_lock);
guint32 ret;
guint32 i;
MonoObject *waitHandle;
- MonoClass *klass;
MonoThread *thread = mono_thread_current ();
MONO_ARCH_SAVE_REGS;
+ /* Do this WaitSleepJoin check before creating objects */
+ mono_thread_current_check_pending_interrupt ();
+
numhandles = mono_array_length(mono_handles);
handles = g_new0(HANDLE, numhandles);
- if (wait_handle_os_handle_field == 0) {
- /* Get the field os_handle which will contain the actual handle */
- klass = mono_class_from_name(mono_defaults.corlib, "System.Threading", "WaitHandle");
- wait_handle_os_handle_field = mono_class_get_field_from_name(klass, "os_handle");
- }
-
for(i = 0; i < numhandles; i++) {
- waitHandle = mono_array_get(mono_handles, MonoObject*, i);
- mono_field_get_value(waitHandle, wait_handle_os_handle_field, &handles[i]);
+ waitHandle = mono_array_get(mono_handles, MonoObject*, i);
+ handles [i] = mono_wait_handle_get_handle ((MonoWaitHandle *) waitHandle);
}
if(ms== -1) {
guint32 ret;
guint32 i;
MonoObject *waitHandle;
- MonoClass *klass;
MonoThread *thread = mono_thread_current ();
MONO_ARCH_SAVE_REGS;
+ /* Do this WaitSleepJoin check before creating objects */
+ mono_thread_current_check_pending_interrupt ();
+
numhandles = mono_array_length(mono_handles);
handles = g_new0(HANDLE, numhandles);
- if (wait_handle_os_handle_field == 0) {
- /* Get the field os_handle which will contain the actual handle */
- klass = mono_class_from_name(mono_defaults.corlib, "System.Threading", "WaitHandle");
- wait_handle_os_handle_field = mono_class_get_field_from_name(klass, "os_handle");
- }
-
for(i = 0; i < numhandles; i++) {
- waitHandle = mono_array_get(mono_handles, MonoObject*, i);
- mono_field_get_value(waitHandle, wait_handle_os_handle_field, &handles[i]);
+ waitHandle = mono_array_get(mono_handles, MonoObject*, i);
+ handles [i] = mono_wait_handle_get_handle ((MonoWaitHandle *) waitHandle);
}
if(ms== -1) {
ms=INFINITE;
}
+ mono_thread_current_check_pending_interrupt ();
+
mono_monitor_enter (thread->synch_lock);
thread->state |= ThreadState_WaitSleepJoin;
mono_monitor_exit (thread->synch_lock);
return(mutex);
}
-void ves_icall_System_Threading_Mutex_ReleaseMutex_internal (HANDLE handle ) {
+MonoBoolean ves_icall_System_Threading_Mutex_ReleaseMutex_internal (HANDLE handle ) {
MONO_ARCH_SAVE_REGS;
- ReleaseMutex(handle);
+ return(ReleaseMutex (handle));
}
HANDLE ves_icall_System_Threading_Mutex_OpenMutex_internal (MonoString *name,
void
ves_icall_System_Threading_Thread_MemoryBarrier (void)
{
- /* Should be implemented as a JIT intrinsic */
- mono_raise_exception (mono_get_exception_not_implemented (NULL));
+ mono_threads_lock ();
+ mono_threads_unlock ();
}
void
return state;
}
+void ves_icall_System_Threading_Thread_Interrupt_internal (MonoThread *this)
+{
+ gboolean throw = FALSE;
+
+ mono_monitor_enter (this->synch_lock);
+
+ /* Clear out any previous request */
+ this->thread_interrupt_requested = FALSE;
+
+ if (this->state & ThreadState_WaitSleepJoin) {
+ throw = TRUE;
+ } else {
+ this->thread_interrupt_requested = TRUE;
+ }
+
+ mono_monitor_exit (this->synch_lock);
+
+ if (throw) {
+ signal_thread_state_change (this);
+ }
+}
+
+void mono_thread_current_check_pending_interrupt ()
+{
+ MonoThread *thread = mono_thread_current ();
+ gboolean throw = FALSE;
+
+ mono_monitor_enter (thread->synch_lock);
+
+ if (thread->thread_interrupt_requested) {
+ throw = TRUE;
+ thread->thread_interrupt_requested = FALSE;
+ }
+
+ mono_monitor_exit (thread->synch_lock);
+
+ if (throw) {
+ mono_raise_exception (mono_get_exception_thread_interrupted ());
+ }
+}
+
int
mono_thread_get_abort_signal (void)
{
-#if defined (__MINGW32__) || defined (_MSC_VER)
+#ifdef PLATFORM_WIN32
return -1;
#else
#ifndef SIGRTMIN
/* fallback to the old way */
return SIGRTMIN;
#endif
-#endif /*defined (__MINGW32__) || defined (_MSC_VER) */
+#endif /* PLATFORM_WIN32 */
}
-#if defined (__MINGW32__) || defined (_MSC_VER)
+#ifdef PLATFORM_WIN32
static void CALLBACK interruption_request_apc (ULONG_PTR param)
{
MonoException* exc = mono_thread_request_interruption (FALSE);
if (exc) mono_raise_exception (exc);
}
-#endif /* defined (__MINGW32__) || defined (_MSC_VER) */
+#endif /* PLATFORM_WIN32 */
/*
* signal_thread_state_change
mono_raise_exception (exc);
}
-#if defined (__MINGW32__) || defined (_MSC_VER)
+#ifdef PLATFORM_WIN32
QueueUserAPC ((PAPCFUNC)interruption_request_apc, thread->handle, NULL);
#else
/* fixme: store the state somewhere */
#else
pthread_kill (thread->tid, mono_thread_get_abort_signal ());
#endif
-#endif /* defined (__MINGW32__) || defined (__MSC_VER) */
+#endif /* PLATFORM_WIN32 */
}
void
}
thread->resume_event = CreateEvent (NULL, TRUE, FALSE, NULL);
+ if (thread->resume_event == NULL) {
+ mono_monitor_exit (thread->synch_lock);
+ return(FALSE);
+ }
/* Awake the thread */
SetEvent (thread->suspend_event);
InitializeCriticalSection(&interlocked_mutex);
InitializeCriticalSection(&contexts_mutex);
background_change_event = CreateEvent (NULL, TRUE, FALSE, NULL);
+ g_assert(background_change_event != NULL);
mono_init_static_data_info (&thread_static_info);
mono_init_static_data_info (&context_static_info);
GetCurrentProcess ();
}
+void mono_thread_cleanup (void)
+{
+#if !defined(PLATFORM_WIN32) && !defined(RUN_IN_SUBTHREAD)
+ /* The main thread must abandon any held mutexes (particularly
+ * important for named mutexes as they are shared across
+ * processes, see bug 74680.) This will happen when the
+ * thread exits, but if it's not running in a subthread it
+ * won't exit in time.
+ */
+ /* Using non-w32 API is a nasty kludge, but I couldn't find
+ * anything in the documentation that would let me do this
+ * here yet still be safe to call on windows.
+ */
+ _wapi_thread_signal_self (mono_environment_exitcode_get ());
+#endif
+
+#if 0
+ /* This stuff needs more testing, it seems one of these
+ * critical sections can be locked when mono_thread_cleanup is
+ * called.
+ */
+ DeleteCriticalSection (&threads_mutex);
+ DeleteCriticalSection (&interlocked_mutex);
+ DeleteCriticalSection (&contexts_mutex);
+ CloseHandle (background_change_event);
+#endif
+
+ TlsFree (current_object_key);
+}
+
void
mono_threads_install_cleanup (MonoThreadCleanupFunc func)
{
- mono_thread_cleanup = func;
+ mono_thread_cleanup_fn = func;
}
G_GNUC_UNUSED
MonoThread *thread=(MonoThread *)value;
/* Ignore background threads, we abort them later */
- mono_monitor_enter (thread->synch_lock);
+ /* Do not lock here since it is not needed and the caller holds threads_lock */
if (thread->state & ThreadState_Background) {
- mono_monitor_exit (thread->synch_lock);
+ THREAD_DEBUG (g_message ("%s: ignoring background thread %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
return; /* just leave, ignore */
}
- mono_monitor_exit (thread->synch_lock);
- if (mono_gc_is_finalizer_thread (thread))
+ if (mono_gc_is_finalizer_thread (thread)) {
+ THREAD_DEBUG (g_message ("%s: ignoring finalizer thread %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
return;
+ }
- if (thread == mono_thread_current ())
+ if (thread == mono_thread_current ()) {
+ THREAD_DEBUG (g_message ("%s: ignoring current thread %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
return;
+ }
- if (thread == mono_thread_get_main ())
+ if (thread == mono_thread_get_main ()) {
+ THREAD_DEBUG (g_message ("%s: ignoring main thread %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
return;
+ }
handle = OpenThread (THREAD_ALL_ACCESS, TRUE, thread->tid);
- if (handle == NULL)
+ if (handle == NULL) {
+ THREAD_DEBUG (g_message ("%s: ignoring unopenable thread %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
return;
+ }
wait->handles[wait->num]=handle;
wait->threads[wait->num]=thread;
wait->num++;
+
+ THREAD_DEBUG (g_message ("%s: adding thread %"G_GSIZE_FORMAT, __func__, (gsize)thread->tid));
} else {
/* Just ignore the rest, we can't do anything with
* them yet
wait_for_tids (wait, INFINITE);
}
} while (wait->num > 0);
-
-#if !defined(PLATFORM_WIN32) && !defined(RUN_IN_SUBTHREAD)
- /* The main thread must abandon any held mutexes (particularly
- * important for named mutexes as they are shared across
- * processes, see bug 74680.) This will happen when the
- * thread exits, but if it's not running in a subthread it
- * won't exit in time.
- */
- /* Using non-w32 API is a nasty kludge, but I couldn't find
- * anything in the documentation that would let me do this
- * here yet still be safe to call on windows.
- */
- _wapi_thread_abandon_mutexes (GetCurrentThread ());
-#endif
/*
* give the subthreads a chance to really quit (this is mainly needed
thread->state |= ThreadState_SuspendRequested;
- if (thread->suspended_event == NULL)
+ if (thread->suspended_event == NULL) {
thread->suspended_event = CreateEvent (NULL, TRUE, FALSE, NULL);
+ if (thread->suspended_event == NULL) {
+ /* Forget this one and go on to the next */
+ mono_monitor_exit (thread->synch_lock);
+ continue;
+ }
+ }
events [eventidx++] = thread->suspended_event;
mono_monitor_exit (thread->synch_lock);
MonoDomain *domain = data->domain;
if (mono_thread_has_appdomain_ref (thread, domain)) {
- HANDLE handle = OpenThread (THREAD_ALL_ACCESS, TRUE, thread->tid);
- if (handle == NULL)
- return;
-
/* printf ("ABORTING THREAD %p BECAUSE IT REFERENCES DOMAIN %s.\n", thread->tid, domain->friendly_name); */
ves_icall_System_Threading_Thread_Abort (thread, NULL);
if(data->wait.num<MAXIMUM_WAIT_OBJECTS) {
+ HANDLE handle = OpenThread (THREAD_ALL_ACCESS, TRUE, thread->tid);
+ if (handle == NULL)
+ return;
data->wait.handles [data->wait.num] = handle;
data->wait.threads [data->wait.num] = thread;
data->wait.num++;
}
}
-#ifdef __MINGW32__
-static CALLBACK void dummy_apc (ULONG_PTR param)
+#ifdef PLATFORM_WIN32
+static void CALLBACK dummy_apc (ULONG_PTR param)
{
}
#else
thread->state &= ~ThreadState_SuspendRequested;
thread->state |= ThreadState_Suspended;
thread->suspend_event = CreateEvent (NULL, TRUE, FALSE, NULL);
+ if (thread->suspend_event == NULL) {
+ mono_monitor_exit (thread->synch_lock);
+ return(NULL);
+ }
if (thread->suspended_event)
SetEvent (thread->suspended_event);
mono_monitor_exit (thread->synch_lock);
mono_monitor_exit (thread->synch_lock);
mono_thread_exit ();
return NULL;
+ } else if (thread->thread_interrupt_requested) {
+ mono_monitor_exit (thread->synch_lock);
+ return(mono_get_exception_thread_interrupted ());
}
mono_monitor_exit (thread->synch_lock);
{
return &thread_interruption_requested;
}
-
-#if MONO_DEBUGGER_SUPPORTED
-
-extern void GC_push_all_stack (gpointer b, gpointer t);
-
-static void
-debugger_gc_push_stack (gpointer key, gpointer value, gpointer user)
-{
- MonoThread *thread = (MonoThread*)value;
- gpointer end_stack;
-
- /*
- * The debugger stops all other threads for us in debugger_gc_stop_world() and
- * then sets `thread->end_stack' for each of them.
- */
-
- end_stack = (thread->tid == GetCurrentThreadId ()) ? &key : thread->end_stack;
-
- if (!end_stack || !thread->stack_ptr) {
- g_warning (G_STRLOC ": Cannot push stack of thread %Lx", thread->tid);
- return;
- }
-
- GC_push_all_stack (end_stack, thread->stack_ptr);
-}
-
-/*
- * We're called with the thread lock.
- */
-static void
-debugger_gc_push_all_stacks (void)
-{
- if (threads != NULL)
- mono_g_hash_table_foreach (threads, debugger_gc_push_stack, NULL);
-}
-
-static void
-debugger_gc_stop_world (void)
-{
- /*
- * Acquire the thread lock and tell the debugger to stop all other threads.
- */
- mono_threads_lock ();
- mono_debugger_event (
- MONO_DEBUGGER_EVENT_ACQUIRE_GLOBAL_THREAD_LOCK, 0, 0);
-}
-
-static void
-debugger_gc_start_world (void)
-{
- /*
- * Tell the debugger to resume all other threads and release the lock.
- */
- mono_debugger_event (
- MONO_DEBUGGER_EVENT_RELEASE_GLOBAL_THREAD_LOCK, 0, 0);
- mono_threads_unlock ();
-}
-
-static void
-debugger_gc_init (void)
-{ }
-
-static GCThreadFunctions debugger_thread_vtable = {
- debugger_gc_init,
-
- debugger_gc_stop_world,
- debugger_gc_push_all_stacks,
- debugger_gc_start_world
-};
-
-static GCThreadFunctions *old_gc_thread_vtable = NULL;
-
-/**
- * mono_debugger_init_threads:
- *
- * This is used when running inside the Mono Debugger.
- */
-void
-mono_debugger_init_threads (void)
-{
- old_gc_thread_vtable = gc_thread_vtable;
- gc_thread_vtable = &debugger_thread_vtable;
- debugger_thread_created (mono_thread_current ());
-}
-
-/**
- * mono_debugger_finalize_threads:
- *
- * This is used when running inside the Mono Debugger.
- * Undo the effects of mono_debugger_init_threads(); this is called
- * prior to detaching from a process.
- */
-void
-mono_debugger_finalize_threads (void)
-{
- gc_thread_vtable = old_gc_thread_vtable;
-}
-
-#else /* WITH_INCLUDED_LIBGC */
-
-void
-mono_debugger_init_threads (void)
-{
- g_assert_not_reached ();
-}
-
-void
-mono_debugger_finalize_threads (void)
-{
- g_assert_not_reached ();
-}
-
-#endif