Merge pull request #2820 from kumpera/license-change-rebased
[mono.git] / mono / metadata / threadpool-ms.c
index 9e960df6e37aa1e37102d53972936951400d5bc2..62fc34aef3f2b11e470b7a8912c819bbf68a6db1 100644 (file)
@@ -5,6 +5,7 @@
  *     Ludovic Henry (ludovic.henry@xamarin.com)
  *
  * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
  */
 
 //
 #define CPU_USAGE_LOW 80
 #define CPU_USAGE_HIGH 95
 
-#define MONITOR_INTERVAL 100 // ms
+#define MONITOR_INTERVAL 500 // ms
 #define MONITOR_MINIMAL_LIFETIME 60 * 1000 // ms
 
+#define WORKER_CREATION_MAX_PER_SEC 10
+
 /* The exponent to apply to the gain. 1.0 means to use linear gain,
  * higher values will enhance large moves and damp small ones.
  * default: 2.0 */
@@ -132,6 +135,10 @@ typedef struct {
        MonoCoopCond parked_threads_cond;
        MonoCoopMutex active_threads_lock; /* protect access to working_threads and parked_threads */
 
+       guint32 worker_creation_current_second;
+       guint32 worker_creation_current_count;
+       MonoCoopMutex worker_creation_lock;
+
        gint32 heuristic_completions;
        guint32 heuristic_sample_start;
        guint32 heuristic_last_dequeue; // ms
@@ -221,11 +228,11 @@ rand_create (void)
 static guint32
 rand_next (gpointer *handle, guint32 min, guint32 max)
 {
+       MonoError error;
        guint32 val;
-       if (!mono_rand_try_get_uint32 (handle, &val, min, max)) {
-               // FIXME handle error
-               g_assert_not_reached ();
-       }
+       mono_rand_try_get_uint32 (handle, &val, min, max, &error);
+       // FIXME handle error
+       mono_error_assert_ok (&error);
        return val;
 }
 
@@ -255,6 +262,9 @@ initialize (void)
        threadpool->working_threads = g_ptr_array_new ();
        mono_coop_mutex_init (&threadpool->active_threads_lock);
 
+       threadpool->worker_creation_current_second = -1;
+       mono_coop_mutex_init (&threadpool->worker_creation_lock);
+
        threadpool->heuristic_adjustment_interval = 10;
        mono_coop_mutex_init (&threadpool->heuristic_lock);
 
@@ -295,7 +305,12 @@ initialize (void)
        threads_count = mono_cpu_count () * threads_per_cpu;
 
        threadpool->limit_worker_min = threadpool->limit_io_min = threads_count;
+
+#if defined (PLATFORM_ANDROID) || defined (HOST_IOS)
+       threadpool->limit_worker_max = threadpool->limit_io_max = CLAMP (threads_count * 100, MIN (threads_count, 200), MAX (threads_count, 200));
+#else
        threadpool->limit_worker_max = threadpool->limit_io_max = threads_count * 100;
+#endif
 
        threadpool->counters._.max_working = threadpool->limit_worker_min;
 
@@ -330,8 +345,8 @@ cleanup (void)
        mono_coop_mutex_unlock (&threadpool->active_threads_lock);
 }
 
-void
-mono_threadpool_ms_enqueue_work_item (MonoDomain *domain, MonoObject *work_item)
+gboolean
+mono_threadpool_ms_enqueue_work_item (MonoDomain *domain, MonoObject *work_item, MonoError *error)
 {
        static MonoClass *threadpool_class = NULL;
        static MonoMethod *unsafe_queue_custom_work_item_method = NULL;
@@ -339,11 +354,11 @@ mono_threadpool_ms_enqueue_work_item (MonoDomain *domain, MonoObject *work_item)
        MonoBoolean f;
        gpointer args [2];
 
+       mono_error_init (error);
        g_assert (work_item);
 
        if (!threadpool_class)
-               threadpool_class = mono_class_from_name (mono_defaults.corlib, "System.Threading", "ThreadPool");
-       g_assert (threadpool_class);
+               threadpool_class = mono_class_load_from_name (mono_defaults.corlib, "System.Threading", "ThreadPool");
 
        if (!unsafe_queue_custom_work_item_method)
                unsafe_queue_custom_work_item_method = mono_class_get_method_from_name (threadpool_class, "UnsafeQueueCustomWorkItem", 2);
@@ -356,15 +371,21 @@ mono_threadpool_ms_enqueue_work_item (MonoDomain *domain, MonoObject *work_item)
 
        current_domain = mono_domain_get ();
        if (current_domain == domain) {
-               mono_runtime_invoke (unsafe_queue_custom_work_item_method, NULL, args, NULL);
+               mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method, NULL, args, error);
+               return_val_if_nok (error, FALSE);
        } else {
                mono_thread_push_appdomain_ref (domain);
                if (mono_domain_set (domain, FALSE)) {
-                       mono_runtime_invoke (unsafe_queue_custom_work_item_method, NULL, args, NULL);
+                       mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method, NULL, args, error);
+                       if (!is_ok (error)) {
+                               mono_thread_pop_appdomain_ref ();
+                               return FALSE;
+                       }
                        mono_domain_set (current_domain, TRUE);
                }
                mono_thread_pop_appdomain_ref ();
        }
+       return TRUE;
 }
 
 /* LOCKING: threadpool->domains_lock must be held */
@@ -508,7 +529,7 @@ worker_park (void)
                if (interrupted)
                        goto done;
 
-               if (mono_coop_cond_timedwait (&threadpool->parked_threads_cond, &threadpool->active_threads_lock, rand_next ((void **)rand_handle, 5 * 1000, 60 * 1000)) != 0)
+               if (mono_coop_cond_timedwait (&threadpool->parked_threads_cond, &threadpool->active_threads_lock, rand_next (&rand_handle, 5 * 1000, 60 * 1000)) != 0)
                        timeout = TRUE;
 
                mono_thread_info_uninstall_interrupt (&interrupted);
@@ -558,6 +579,7 @@ worker_kill (ThreadPoolWorkingThread *thread)
 static void
 worker_thread (gpointer data)
 {
+       MonoError error;
        MonoInternalThread *thread;
        ThreadPoolDomain *tpdomain, *previous_tpdomain;
        ThreadPoolCounter counter;
@@ -629,11 +651,16 @@ worker_thread (gpointer data)
 
                mono_thread_push_appdomain_ref (tpdomain->domain);
                if (mono_domain_set (tpdomain->domain, FALSE)) {
-                       MonoObject *exc = NULL;
-                       MonoObject *res = mono_runtime_invoke (mono_defaults.threadpool_perform_wait_callback_method, NULL, NULL, &exc);
-                       if (exc)
+                       MonoObject *exc = NULL, *res;
+
+                       res = mono_runtime_try_invoke (mono_defaults.threadpool_perform_wait_callback_method, NULL, NULL, &exc, &error);
+                       if (exc || !mono_error_ok(&error)) {
+                               if (exc == NULL)
+                                       exc = (MonoObject *) mono_error_convert_to_exception (&error);
+                               else
+                                       mono_error_cleanup (&error);
                                mono_thread_internal_unhandled_exception (exc);
-                       else if (res && *(MonoBoolean*) mono_object_unbox (res) == FALSE)
+                       else if (res && *(MonoBoolean*) mono_object_unbox (res) == FALSE)
                                retire = TRUE;
 
                        mono_thread_clr_state (thread, (MonoThreadState)~ThreadState_Background);
@@ -680,29 +707,56 @@ worker_try_create (void)
 {
        ThreadPoolCounter counter;
        MonoInternalThread *thread;
+       gint32 now;
+
+       mono_coop_mutex_lock (&threadpool->worker_creation_lock);
 
        mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker", mono_native_thread_id_get ());
 
+       if ((now = mono_100ns_ticks () / 10 / 1000 / 1000) == 0) {
+               g_warning ("failed to get 100ns ticks");
+       } else {
+               if (threadpool->worker_creation_current_second != now) {
+                       threadpool->worker_creation_current_second = now;
+                       threadpool->worker_creation_current_count = 0;
+               } else {
+                       g_assert (threadpool->worker_creation_current_count <= WORKER_CREATION_MAX_PER_SEC);
+                       if (threadpool->worker_creation_current_count == WORKER_CREATION_MAX_PER_SEC) {
+                               mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker, failed: maximum number of worker created per second reached, current count = %d",
+                                       mono_native_thread_id_get (), threadpool->worker_creation_current_count);
+                               mono_coop_mutex_unlock (&threadpool->worker_creation_lock);
+                               return FALSE;
+                       }
+               }
+       }
+
        COUNTER_ATOMIC (counter, {
-               if (counter._.working >= counter._.max_working)
+               if (counter._.working >= counter._.max_working) {
+                       mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker, failed: maximum number of working threads reached",
+                               mono_native_thread_id_get ());
+                       mono_coop_mutex_unlock (&threadpool->worker_creation_lock);
                        return FALSE;
+               }
                counter._.working ++;
                counter._.active ++;
        });
 
        if ((thread = mono_thread_create_internal (mono_get_root_domain (), worker_thread, NULL, TRUE, 0)) != NULL) {
-               mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker, created %p",
-                       mono_native_thread_id_get (), thread->tid);
+               threadpool->worker_creation_current_count += 1;
+
+               mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker, created %p, now = %d count = %d", mono_native_thread_id_get (), thread->tid, now, threadpool->worker_creation_current_count);
+               mono_coop_mutex_unlock (&threadpool->worker_creation_lock);
                return TRUE;
        }
 
-       mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker, failed", mono_native_thread_id_get ());
+       mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] try create worker, failed: could not create thread", mono_native_thread_id_get ());
 
        COUNTER_ATOMIC (counter, {
                counter._.working --;
                counter._.active --;
        });
 
+       mono_coop_mutex_unlock (&threadpool->worker_creation_lock);
        return FALSE;
 }
 
@@ -826,8 +880,8 @@ monitor_thread (void)
        mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] monitor thread, started", mono_native_thread_id_get ());
 
        do {
-               MonoInternalThread *thread;
-               gboolean all_waitsleepjoin = TRUE;
+               ThreadPoolCounter counter;
+               gboolean limit_worker_max_reached;
                gint32 interval_left = MONITOR_INTERVAL;
                gint32 awake = 0; /* number of spurious awakes we tolerate before doing a round of rebalancing */
 
@@ -868,39 +922,38 @@ monitor_thread (void)
                }
                mono_coop_mutex_unlock (&threadpool->domains_lock);
 
+               threadpool->cpu_usage = mono_cpu_usage (threadpool->cpu_usage_state);
+
+               if (!monitor_sufficient_delay_since_last_dequeue ())
+                       continue;
 
-               mono_coop_mutex_lock (&threadpool->active_threads_lock);
-               for (i = 0; i < threadpool->working_threads->len; ++i) {
-                       thread = (MonoInternalThread *)g_ptr_array_index (threadpool->working_threads, i);
-                       if ((thread->state & ThreadState_WaitSleepJoin) == 0) {
-                               all_waitsleepjoin = FALSE;
+               limit_worker_max_reached = FALSE;
+
+               COUNTER_ATOMIC (counter, {
+                       if (counter._.max_working >= threadpool->limit_worker_max) {
+                               limit_worker_max_reached = TRUE;
                                break;
                        }
-               }
-               mono_coop_mutex_unlock (&threadpool->active_threads_lock);
+                       counter._.max_working ++;
+               });
 
-               if (all_waitsleepjoin) {
-                       ThreadPoolCounter counter;
-                       COUNTER_ATOMIC (counter, { counter._.max_working ++; });
-                       hill_climbing_force_change (counter._.max_working, TRANSITION_STARVATION);
-               }
+               if (limit_worker_max_reached)
+                       continue;
 
-               threadpool->cpu_usage = mono_cpu_usage (threadpool->cpu_usage_state);
+               hill_climbing_force_change (counter._.max_working, TRANSITION_STARVATION);
 
-               if (monitor_sufficient_delay_since_last_dequeue ()) {
-                       for (i = 0; i < 5; ++i) {
-                               if (mono_runtime_is_shutting_down ())
-                                       break;
+               for (i = 0; i < 5; ++i) {
+                       if (mono_runtime_is_shutting_down ())
+                               break;
 
-                               if (worker_try_unpark ()) {
-                                       mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] monitor thread, unparked", mono_native_thread_id_get ());
-                                       break;
-                               }
+                       if (worker_try_unpark ()) {
+                               mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] monitor thread, unparked", mono_native_thread_id_get ());
+                               break;
+                       }
 
-                               if (worker_try_create ()) {
-                                       mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] monitor thread, created", mono_native_thread_id_get ());
-                                       break;
-                               }
+                       if (worker_try_create ()) {
+                               mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] monitor thread, created", mono_native_thread_id_get ());
+                               break;
                        }
                }
        } while (monitor_should_keep_running ());
@@ -1266,7 +1319,7 @@ mono_threadpool_ms_cleanup (void)
 }
 
 MonoAsyncResult *
-mono_threadpool_ms_begin_invoke (MonoDomain *domain, MonoObject *target, MonoMethod *method, gpointer *params)
+mono_threadpool_ms_begin_invoke (MonoDomain *domain, MonoObject *target, MonoMethod *method, gpointer *params, MonoError *error)
 {
        static MonoClass *async_call_klass = NULL;
        MonoMethodMessage *message;
@@ -1276,14 +1329,17 @@ mono_threadpool_ms_begin_invoke (MonoDomain *domain, MonoObject *target, MonoMet
        MonoObject *state = NULL;
 
        if (!async_call_klass)
-               async_call_klass = mono_class_from_name (mono_defaults.corlib, "System", "MonoAsyncCall");
-       g_assert (async_call_klass);
+               async_call_klass = mono_class_load_from_name (mono_defaults.corlib, "System", "MonoAsyncCall");
 
        mono_lazy_initialize (&status, initialize);
 
+       mono_error_init (error);
+
        message = mono_method_call_message_new (method, params, mono_get_delegate_invoke (method->klass), (params != NULL) ? (&async_callback) : NULL, (params != NULL) ? (&state) : NULL);
 
-       async_call = (MonoAsyncCall*) mono_object_new (domain, async_call_klass);
+       async_call = (MonoAsyncCall*) mono_object_new_checked (domain, async_call_klass, error);
+       return_val_if_nok (error, NULL);
+
        MONO_OBJECT_SETREF (async_call, msg, message);
        MONO_OBJECT_SETREF (async_call, state, state);
 
@@ -1295,7 +1351,8 @@ mono_threadpool_ms_begin_invoke (MonoDomain *domain, MonoObject *target, MonoMet
        async_result = mono_async_result_new (domain, NULL, async_call->state, NULL, (MonoObject*) async_call);
        MONO_OBJECT_SETREF (async_result, async_delegate, target);
 
-       mono_threadpool_ms_enqueue_work_item (domain, (MonoObject*) async_result);
+       mono_threadpool_ms_enqueue_work_item (domain, (MonoObject*) async_result, error);
+       return_val_if_nok (error, NULL);
 
        return async_result;
 }
@@ -1533,7 +1590,9 @@ void
 ves_icall_System_Threading_ThreadPool_ReportThreadStatus (MonoBoolean is_working)
 {
        // TODO
-       mono_raise_exception (mono_get_exception_not_implemented (NULL));
+       MonoError error;
+       mono_error_set_not_implemented (&error, "");
+       mono_error_set_pending_exception (&error);
 }
 
 MonoBoolean
@@ -1546,7 +1605,9 @@ MonoBoolean G_GNUC_UNUSED
 ves_icall_System_Threading_ThreadPool_PostQueuedCompletionStatus (MonoNativeOverlapped *native_overlapped)
 {
        /* This copy the behavior of the current Mono implementation */
-       mono_raise_exception (mono_get_exception_not_implemented (NULL));
+       MonoError error;
+       mono_error_set_not_implemented (&error, "");
+       mono_error_set_pending_exception (&error);
        return FALSE;
 }