@@ -160,6 +160,14 @@ static MonoGHashTable *threads_starting_up = NULL;
160160static GHashTable * joinable_threads ;
161161static gint32 joinable_thread_count ;
162162
163+ static GHashTable * pending_joinable_threads ;
164+ static gint32 pending_joinable_thread_count ;
165+
166+ static mono_cond_t zero_pending_joinable_thread_event ;
167+
168+ static void threads_add_pending_joinable_runtime_thread (MonoThreadInfo * mono_thread_info );
169+ static gboolean threads_wait_pending_joinable_threads (uint32_t timeout );
170+
163171#define SET_CURRENT_OBJECT (x ) mono_tls_set_thread (x)
164172#define GET_CURRENT_OBJECT () (MonoInternalThread*) mono_tls_get_thread ()
165173
@@ -763,6 +771,17 @@ mono_thread_detach_internal (MonoInternalThread *thread)
763771
764772 MONO_PROFILER_RAISE (thread_stopping , (thread -> tid ));
765773
774+ /*
775+ * Prevent race condition between thread shutdown and runtime shutdown.
776+ * Including all runtime threads in the pending joinable count will make
777+ * sure shutdown will wait for it to get onto the joinable thread list before
778+ * critical resources have been cleanup (like GC memory). Threads getting onto
779+ * the joinable thread list should just about to exit and not blocking a potential
780+ * join call. Owner of threads attached to the runtime but not identified as runtime
781+ * threads needs to make sure thread detach calls won't race with runtime shutdown.
782+ */
783+ threads_add_pending_joinable_runtime_thread (info );
784+
766785#ifndef HOST_WIN32
767786 mono_w32mutex_abandon ();
768787#endif
@@ -775,17 +794,6 @@ mono_thread_detach_internal (MonoInternalThread *thread)
775794 thread -> abort_exc = NULL ;
776795 thread -> current_appcontext = NULL ;
777796
778- /*
779- * Prevent race condition between execution of this method and runtime shutdown.
780- * Adding runtime thread to the joinable threads list will make sure runtime shutdown
781- * won't complete until added runtime thread have exited. Owner of threads attached to the
782- * runtime but not identified as runtime threads needs to make sure thread detach calls won't
783- * race with runtime shutdown.
784- */
785- #ifdef HOST_WIN32
786- mono_threads_add_joinable_runtime_thread (info );
787- #endif
788-
789797 /*
790798 * thread->synch_cs can be NULL if this was called after
791799 * ves_icall_System_Threading_InternalThread_Thread_free_internal.
@@ -3031,6 +3039,8 @@ void mono_thread_init (MonoThreadStartCB start_cb,
30313039
30323040 mono_os_event_init (& background_change_event , FALSE);
30333041
3042+ mono_os_cond_init (& zero_pending_joinable_thread_event );
3043+
30343044 mono_init_static_data_info (& thread_static_info );
30353045 mono_init_static_data_info (& context_static_info );
30363046
@@ -3122,6 +3132,13 @@ mono_thread_callbacks_init (void)
31223132void
31233133mono_thread_cleanup (void )
31243134{
3135+ /* Wait for pending threads to park on joinable threads list */
3136+ /* NOTE, waiting on this should be extremely rare and will only happen */
3137+ /* under certain specific conditions. */
3138+ gboolean wait_result = threads_wait_pending_joinable_threads (2000 );
3139+ if (!wait_result )
3140+ g_warning ("Waiting on threads to park on joinable thread list timed out." );
3141+
31253142 mono_threads_join_threads ();
31263143
31273144#if !defined(RUN_IN_SUBTHREAD ) && !defined(HOST_WIN32 )
@@ -3143,6 +3160,7 @@ mono_thread_cleanup (void)
31433160 mono_os_mutex_destroy (& interlocked_mutex );
31443161 mono_os_mutex_destroy (& delayed_free_table_mutex );
31453162 mono_os_mutex_destroy (& small_id_mutex );
3163+ mono_os_cond_destroy (& zero_pending_joinable_runtime_thread_event );
31463164 mono_os_event_destroy (& background_change_event );
31473165#endif
31483166}
@@ -3367,6 +3385,7 @@ mono_thread_manage (void)
33673385 mono_threads_unlock ();
33683386 return ;
33693387 }
3388+
33703389 mono_threads_unlock ();
33713390
33723391 do {
@@ -5221,15 +5240,105 @@ threads_add_joinable_thread_nolock (gpointer tid)
52215240}
52225241#endif
52235242
5243+ static void
5244+ threads_add_pending_joinable_thread (gpointer tid )
5245+ {
5246+ joinable_threads_lock ();
5247+
5248+ if (!pending_joinable_threads )
5249+ pending_joinable_threads = g_hash_table_new (NULL , NULL );
5250+
5251+ gpointer orig_key ;
5252+ gpointer value ;
5253+
5254+ if (!g_hash_table_lookup_extended (pending_joinable_threads , tid , & orig_key , & value )) {
5255+ g_hash_table_insert (pending_joinable_threads , tid , tid );
5256+ UnlockedIncrement (& pending_joinable_thread_count );
5257+ }
5258+
5259+ joinable_threads_unlock ();
5260+ }
5261+
5262+ static void
5263+ threads_add_pending_joinable_runtime_thread (MonoThreadInfo * mono_thread_info )
5264+ {
5265+ g_assert (mono_thread_info );
5266+
5267+ if (mono_thread_info -> runtime_thread ) {
5268+ threads_add_pending_joinable_thread ((gpointer )(MONO_UINT_TO_NATIVE_THREAD_ID (mono_thread_info_get_tid (mono_thread_info ))));
5269+ }
5270+ }
5271+
5272+ static void
5273+ threads_remove_pending_joinable_thread_nolock (gpointer tid )
5274+ {
5275+ gpointer orig_key ;
5276+ gpointer value ;
5277+
5278+ if (pending_joinable_threads && g_hash_table_lookup_extended (pending_joinable_threads , tid , & orig_key , & value )) {
5279+ g_hash_table_remove (pending_joinable_threads , tid );
5280+ if (UnlockedDecrement (& pending_joinable_thread_count ) == 0 )
5281+ mono_os_cond_broadcast (& zero_pending_joinable_thread_event );
5282+ }
5283+ }
5284+
5285+ static gboolean
5286+ threads_wait_pending_joinable_threads (uint32_t timeout )
5287+ {
5288+ if (UnlockedRead (& pending_joinable_thread_count ) > 0 ) {
5289+ joinable_threads_lock ();
5290+ if (timeout == MONO_INFINITE_WAIT ) {
5291+ while (UnlockedRead (& pending_joinable_thread_count ) > 0 )
5292+ mono_os_cond_wait (& zero_pending_joinable_thread_event , & joinable_threads_mutex );
5293+ } else {
5294+ gint64 start = mono_msec_ticks ();
5295+ gint64 elapsed = 0 ;
5296+ while (UnlockedRead (& pending_joinable_thread_count ) > 0 && elapsed < timeout ) {
5297+ mono_os_cond_timedwait (& zero_pending_joinable_thread_event , & joinable_threads_mutex , timeout - (uint32_t )elapsed );
5298+ elapsed = mono_msec_ticks () - start ;
5299+ }
5300+ }
5301+ joinable_threads_unlock ();
5302+ }
5303+
5304+ return UnlockedRead (& pending_joinable_thread_count ) == 0 ;
5305+ }
5306+
5307+ static void
5308+ threads_add_unique_joinable_thread_nolock (gpointer tid )
5309+ {
5310+ if (!joinable_threads )
5311+ joinable_threads = g_hash_table_new (NULL , NULL );
5312+
5313+ gpointer orig_key ;
5314+ gpointer value ;
5315+
5316+ if (!g_hash_table_lookup_extended (joinable_threads , tid , & orig_key , & value )) {
5317+ threads_add_joinable_thread_nolock (tid );
5318+ UnlockedIncrement (& joinable_thread_count );
5319+ }
5320+ }
5321+
52245322void
52255323mono_threads_add_joinable_runtime_thread (gpointer thread_info )
52265324{
52275325 g_assert (thread_info );
52285326 MonoThreadInfo * mono_thread_info = (MonoThreadInfo * )thread_info ;
52295327
52305328 if (mono_thread_info -> runtime_thread ) {
5231- if (mono_atomic_cas_i32 (& mono_thread_info -> thread_pending_native_join , TRUE, FALSE) == FALSE)
5232- mono_threads_add_joinable_thread ((gpointer )(MONO_UINT_TO_NATIVE_THREAD_ID (mono_thread_info_get_tid (mono_thread_info ))));
5329+ gpointer tid = (gpointer )(MONO_UINT_TO_NATIVE_THREAD_ID (mono_thread_info_get_tid (mono_thread_info )));
5330+
5331+ joinable_threads_lock ();
5332+
5333+ // Add to joinable thread list, if not already included.
5334+ threads_add_unique_joinable_thread_nolock (tid );
5335+
5336+ // Remove thread from pending joinable list, if present.
5337+ threads_remove_pending_joinable_thread_nolock (tid );
5338+
5339+ joinable_threads_unlock ();
5340+
5341+ mono_gc_finalize_notify ();
52335342 }
52345343}
52355344
@@ -5248,15 +5357,7 @@ mono_threads_add_joinable_thread (gpointer tid)
52485357 * we have time (in the finalizer thread).
52495358 */
52505359 joinable_threads_lock ();
5251- if (!joinable_threads )
5252- joinable_threads = g_hash_table_new (NULL , NULL );
5253-
5254- gpointer orig_key ;
5255- gpointer value ;
5256- if (!g_hash_table_lookup_extended (joinable_threads , tid , & orig_key , & value )) {
5257- threads_add_joinable_thread_nolock (tid );
5258- UnlockedIncrement (& joinable_thread_count );
5259- }
5360+ threads_add_unique_joinable_thread_nolock (tid );
52605361 joinable_threads_unlock ();
52615362
52625363 mono_gc_finalize_notify ();
0 commit comments