[threadpool] Fix race on runtime shutdown (#4263)
[mono.git] / mono / metadata / threadpool.c
1 /*
2  * threadpool.c: Microsoft threadpool runtime support
3  *
4  * Author:
5  *      Ludovic Henry (ludovic.henry@xamarin.com)
6  *
7  * Copyright 2015 Xamarin, Inc (http://www.xamarin.com)
8  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9  */
10
11 //
12 // Copyright (c) Microsoft. All rights reserved.
13 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
14 //
15 // Files:
16 //  - src/vm/comthreadpool.cpp
17 //  - src/vm/win32threadpoolcpp
18 //  - src/vm/threadpoolrequest.cpp
19 //  - src/vm/hillclimbing.cpp
20 //
21 // Ported from C++ to C and adjusted to Mono runtime
22
23 #include <stdlib.h>
24 #define _USE_MATH_DEFINES // needed by MSVC to define math constants
25 #include <math.h>
26 #include <config.h>
27 #include <glib.h>
28
29 #include <mono/metadata/class-internals.h>
30 #include <mono/metadata/exception.h>
31 #include <mono/metadata/gc-internals.h>
32 #include <mono/metadata/object.h>
33 #include <mono/metadata/object-internals.h>
34 #include <mono/metadata/threadpool.h>
35 #include <mono/metadata/threadpool-worker.h>
36 #include <mono/metadata/threadpool-io.h>
37 #include <mono/metadata/w32event.h>
38 #include <mono/utils/atomic.h>
39 #include <mono/utils/mono-compiler.h>
40 #include <mono/utils/mono-complex.h>
41 #include <mono/utils/mono-lazy-init.h>
42 #include <mono/utils/mono-logger.h>
43 #include <mono/utils/mono-logger-internals.h>
44 #include <mono/utils/mono-proclib.h>
45 #include <mono/utils/mono-threads.h>
46 #include <mono/utils/mono-time.h>
47 #include <mono/utils/refcount.h>
48 #include <mono/io-layer/io-layer.h>
49
50 typedef struct {
51         MonoDomain *domain;
52         /* Number of outstanding jobs */
53         gint32 outstanding_request;
54         /* Number of currently executing jobs */
55         gint32 threadpool_jobs;
56         /* Signalled when threadpool_jobs + outstanding_request is 0 */
57         /* Protected by threadpool->domains_lock */
58         MonoCoopCond cleanup_cond;
59 } ThreadPoolDomain;
60
61 typedef union {
62         struct {
63                 gint16 starting; /* starting, but not yet in worker_callback */
64                 gint16 working; /* executing worker_callback */
65         } _;
66         gint32 as_gint32;
67 } ThreadPoolCounter;
68
69 typedef struct {
70         MonoRefCount ref;
71
72         GPtrArray *domains; // ThreadPoolDomain* []
73         MonoCoopMutex domains_lock;
74
75         GPtrArray *threads; // MonoInternalThread* []
76         MonoCoopMutex threads_lock;
77         MonoCoopCond threads_exit_cond;
78
79         ThreadPoolCounter counters;
80
81         gint32 limit_io_min;
82         gint32 limit_io_max;
83
84         MonoThreadPoolWorker *worker;
85 } ThreadPool;
86
87 static mono_lazy_init_t status = MONO_LAZY_INIT_STATUS_NOT_INITIALIZED;
88
89 static ThreadPool* threadpool;
90
91 #define COUNTER_ATOMIC(threadpool,var,block) \
92         do { \
93                 ThreadPoolCounter __old; \
94                 do { \
95                         g_assert (threadpool); \
96                         (var) = __old = COUNTER_READ (threadpool); \
97                         { block; } \
98                         if (!(counter._.starting >= 0)) \
99                                 g_error ("%s: counter._.starting = %d, but should be >= 0", __func__, counter._.starting); \
100                         if (!(counter._.working >= 0)) \
101                                 g_error ("%s: counter._.working = %d, but should be >= 0", __func__, counter._.working); \
102                 } while (InterlockedCompareExchange (&threadpool->counters.as_gint32, (var).as_gint32, __old.as_gint32) != __old.as_gint32); \
103         } while (0)
104
105 static inline ThreadPoolCounter
106 COUNTER_READ (ThreadPool *threadpool)
107 {
108         ThreadPoolCounter counter;
109         counter.as_gint32 = InterlockedRead (&threadpool->counters.as_gint32);
110         return counter;
111 }
112
113 static inline void
114 domains_lock (void)
115 {
116         mono_coop_mutex_lock (&threadpool->domains_lock);
117 }
118
119 static inline void
120 domains_unlock (void)
121 {
122         mono_coop_mutex_unlock (&threadpool->domains_lock);
123 }
124
125 static void
126 destroy (gpointer unused)
127 {
128         g_ptr_array_free (threadpool->domains, TRUE);
129         mono_coop_mutex_destroy (&threadpool->domains_lock);
130
131         g_ptr_array_free (threadpool->threads, TRUE);
132         mono_coop_mutex_destroy (&threadpool->threads_lock);
133         mono_coop_cond_destroy (&threadpool->threads_exit_cond);
134
135         /* We cannot free the threadpool, because there is a race
136          * on shutdown where a managed thread may request a new
137          * threadpool thread, but we already destroyed the
138          * threadpool. So to avoid a use-after-free, we simply do
139          * not free the threadpool, as we won't be able to access
140          * the threadpool anyway because the ref count will be 0 */
141         // g_free (threadpool);
142 }
143
144 static void
145 initialize (void)
146 {
147         g_assert (!threadpool);
148         threadpool = g_new0 (ThreadPool, 1);
149         g_assert (threadpool);
150
151         g_assert (sizeof (ThreadPoolCounter) == sizeof (gint32));
152
153         mono_refcount_init (threadpool, destroy);
154
155         threadpool->domains = g_ptr_array_new ();
156         mono_coop_mutex_init (&threadpool->domains_lock);
157
158         threadpool->threads = g_ptr_array_new ();
159         mono_coop_mutex_init (&threadpool->threads_lock);
160         mono_coop_cond_init (&threadpool->threads_exit_cond);
161
162         threadpool->limit_io_min = mono_cpu_count ();
163         threadpool->limit_io_max = CLAMP (threadpool->limit_io_min * 100, MIN (threadpool->limit_io_min, 200), MAX (threadpool->limit_io_min, 200));
164
165         mono_threadpool_worker_init (&threadpool->worker);
166 }
167
168 static void
169 cleanup (void)
170 {
171         guint i;
172         MonoInternalThread *current;
173
174         /* we make the assumption along the code that we are
175          * cleaning up only if the runtime is shutting down */
176         g_assert (mono_runtime_is_shutting_down ());
177
178         current = mono_thread_internal_current ();
179
180         mono_coop_mutex_lock (&threadpool->threads_lock);
181
182         /* stop all threadpool->threads */
183         for (i = 0; i < threadpool->threads->len; ++i) {
184                 MonoInternalThread *thread = (MonoInternalThread*) g_ptr_array_index (threadpool->threads, i);
185                 if (thread != current)
186                         mono_thread_internal_abort (thread);
187         }
188
189         mono_coop_mutex_unlock (&threadpool->threads_lock);
190
191         /* give a chance to the other threads to exit */
192         mono_thread_info_yield ();
193
194         mono_coop_mutex_lock (&threadpool->threads_lock);
195
196         for (;;) {
197                 if (threadpool->threads->len == 0)
198                         break;
199
200                 if (threadpool->threads->len == 1 && g_ptr_array_index (threadpool->threads, 0) == current) {
201                         /* We are waiting on ourselves */
202                         break;
203                 }
204
205                 mono_coop_cond_wait (&threadpool->threads_exit_cond, &threadpool->threads_lock);
206         }
207
208         mono_coop_mutex_unlock (&threadpool->threads_lock);
209
210         mono_threadpool_worker_cleanup (threadpool->worker);
211
212         mono_refcount_dec (threadpool);
213 }
214
215 gboolean
216 mono_threadpool_enqueue_work_item (MonoDomain *domain, MonoObject *work_item, MonoError *error)
217 {
218         static MonoClass *threadpool_class = NULL;
219         static MonoMethod *unsafe_queue_custom_work_item_method = NULL;
220         MonoDomain *current_domain;
221         MonoBoolean f;
222         gpointer args [2];
223
224         mono_error_init (error);
225         g_assert (work_item);
226
227         if (!threadpool_class)
228                 threadpool_class = mono_class_load_from_name (mono_defaults.corlib, "System.Threading", "ThreadPool");
229
230         if (!unsafe_queue_custom_work_item_method)
231                 unsafe_queue_custom_work_item_method = mono_class_get_method_from_name (threadpool_class, "UnsafeQueueCustomWorkItem", 2);
232         g_assert (unsafe_queue_custom_work_item_method);
233
234         f = FALSE;
235
236         args [0] = (gpointer) work_item;
237         args [1] = (gpointer) &f;
238
239         current_domain = mono_domain_get ();
240         if (current_domain == domain) {
241                 mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method, NULL, args, error);
242                 return_val_if_nok (error, FALSE);
243         } else {
244                 mono_thread_push_appdomain_ref (domain);
245                 if (mono_domain_set (domain, FALSE)) {
246                         mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method, NULL, args, error);
247                         if (!is_ok (error)) {
248                                 mono_thread_pop_appdomain_ref ();
249                                 return FALSE;
250                         }
251                         mono_domain_set (current_domain, TRUE);
252                 }
253                 mono_thread_pop_appdomain_ref ();
254         }
255         return TRUE;
256 }
257
258 /* LOCKING: domains_lock must be held */
259 static void
260 tpdomain_add (ThreadPoolDomain *tpdomain)
261 {
262         guint i, len;
263
264         g_assert (tpdomain);
265
266         len = threadpool->domains->len;
267         for (i = 0; i < len; ++i) {
268                 if (g_ptr_array_index (threadpool->domains, i) == tpdomain)
269                         break;
270         }
271
272         if (i == len)
273                 g_ptr_array_add (threadpool->domains, tpdomain);
274 }
275
276 /* LOCKING: domains_lock must be held. */
277 static gboolean
278 tpdomain_remove (ThreadPoolDomain *tpdomain)
279 {
280         g_assert (tpdomain);
281         return g_ptr_array_remove (threadpool->domains, tpdomain);
282 }
283
284 /* LOCKING: domains_lock must be held */
285 static ThreadPoolDomain *
286 tpdomain_get (MonoDomain *domain, gboolean create)
287 {
288         guint i;
289         ThreadPoolDomain *tpdomain;
290
291         g_assert (domain);
292
293         for (i = 0; i < threadpool->domains->len; ++i) {
294                 ThreadPoolDomain *tpdomain;
295
296                 tpdomain = (ThreadPoolDomain *)g_ptr_array_index (threadpool->domains, i);
297                 if (tpdomain->domain == domain)
298                         return tpdomain;
299         }
300
301         if (!create)
302                 return NULL;
303
304         tpdomain = g_new0 (ThreadPoolDomain, 1);
305         tpdomain->domain = domain;
306         mono_coop_cond_init (&tpdomain->cleanup_cond);
307
308         tpdomain_add (tpdomain);
309
310         return tpdomain;
311 }
312
313 static void
314 tpdomain_free (ThreadPoolDomain *tpdomain)
315 {
316         g_free (tpdomain);
317 }
318
319 /* LOCKING: domains_lock must be held */
320 static ThreadPoolDomain *
321 tpdomain_get_next (ThreadPoolDomain *current)
322 {
323         ThreadPoolDomain *tpdomain = NULL;
324         guint len;
325
326         len = threadpool->domains->len;
327         if (len > 0) {
328                 guint i, current_idx = -1;
329                 if (current) {
330                         for (i = 0; i < len; ++i) {
331                                 if (current == g_ptr_array_index (threadpool->domains, i)) {
332                                         current_idx = i;
333                                         break;
334                                 }
335                         }
336                         g_assert (current_idx != (guint)-1);
337                 }
338                 for (i = current_idx + 1; i < len + current_idx + 1; ++i) {
339                         ThreadPoolDomain *tmp = (ThreadPoolDomain *)g_ptr_array_index (threadpool->domains, i % len);
340                         if (tmp->outstanding_request > 0) {
341                                 tpdomain = tmp;
342                                 break;
343                         }
344                 }
345         }
346
347         return tpdomain;
348 }
349
350 static MonoObject*
351 try_invoke_perform_wait_callback (MonoObject** exc, MonoError *error)
352 {
353         HANDLE_FUNCTION_ENTER ();
354         mono_error_init (error);
355         MonoObject *res = mono_runtime_try_invoke (mono_defaults.threadpool_perform_wait_callback_method, NULL, NULL, exc, error);
356         HANDLE_FUNCTION_RETURN_VAL (res);
357 }
358
359 static void
360 worker_callback (gpointer unused)
361 {
362         MonoError error;
363         ThreadPoolDomain *tpdomain, *previous_tpdomain;
364         ThreadPoolCounter counter;
365         MonoInternalThread *thread;
366
367         thread = mono_thread_internal_current ();
368
369         COUNTER_ATOMIC (threadpool, counter, {
370                 if (!(counter._.working < 32767 /* G_MAXINT16 */))
371                         g_error ("%s: counter._.working = %d, but should be < 32767", __func__, counter._.working);
372
373                 counter._.starting --;
374                 counter._.working ++;
375         });
376
377         if (mono_runtime_is_shutting_down ()) {
378                 COUNTER_ATOMIC (threadpool, counter, {
379                         counter._.working --;
380                 });
381
382                 mono_refcount_dec (threadpool);
383                 return;
384         }
385
386         mono_coop_mutex_lock (&threadpool->threads_lock);
387         g_ptr_array_add (threadpool->threads, thread);
388         mono_coop_mutex_unlock (&threadpool->threads_lock);
389
390         /*
391          * This is needed so there is always an lmf frame in the runtime invoke call below,
392          * so ThreadAbortExceptions are caught even if the thread is in native code.
393          */
394         mono_defaults.threadpool_perform_wait_callback_method->save_lmf = TRUE;
395
396         domains_lock ();
397
398         previous_tpdomain = NULL;
399
400         while (!mono_runtime_is_shutting_down ()) {
401                 gboolean retire = FALSE;
402
403                 if ((thread->state & (ThreadState_AbortRequested | ThreadState_SuspendRequested)) != 0) {
404                         domains_unlock ();
405                         mono_thread_interruption_checkpoint ();
406                         domains_lock ();
407                 }
408
409                 tpdomain = tpdomain_get_next (previous_tpdomain);
410                 if (!tpdomain)
411                         break;
412
413                 tpdomain->outstanding_request --;
414                 g_assert (tpdomain->outstanding_request >= 0);
415
416                 mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_THREADPOOL, "[%p] worker running in domain %p (outstanding requests %d)",
417                         mono_native_thread_id_get (), tpdomain->domain, tpdomain->outstanding_request);
418
419                 g_assert (tpdomain->threadpool_jobs >= 0);
420                 tpdomain->threadpool_jobs ++;
421
422                 domains_unlock ();
423
424                 mono_thread_clr_state (thread, (MonoThreadState)~ThreadState_Background);
425                 if (!mono_thread_test_state (thread , ThreadState_Background))
426                         ves_icall_System_Threading_Thread_SetState (thread, ThreadState_Background);
427
428                 mono_thread_push_appdomain_ref (tpdomain->domain);
429                 if (mono_domain_set (tpdomain->domain, FALSE)) {
430                         MonoObject *exc = NULL, *res;
431
432                         res = try_invoke_perform_wait_callback (&exc, &error);
433                         if (exc || !mono_error_ok(&error)) {
434                                 if (exc == NULL)
435                                         exc = (MonoObject *) mono_error_convert_to_exception (&error);
436                                 else
437                                         mono_error_cleanup (&error);
438                                 mono_thread_internal_unhandled_exception (exc);
439                         } else if (res && *(MonoBoolean*) mono_object_unbox (res) == FALSE) {
440                                 retire = TRUE;
441                         }
442
443                         mono_domain_set (mono_get_root_domain (), TRUE);
444                 }
445                 mono_thread_pop_appdomain_ref ();
446
447                 domains_lock ();
448
449                 tpdomain->threadpool_jobs --;
450                 g_assert (tpdomain->threadpool_jobs >= 0);
451
452                 if (tpdomain->outstanding_request + tpdomain->threadpool_jobs == 0 && mono_domain_is_unloading (tpdomain->domain)) {
453                         gboolean removed;
454
455                         removed = tpdomain_remove (tpdomain);
456                         g_assert (removed);
457
458                         mono_coop_cond_signal (&tpdomain->cleanup_cond);
459                         tpdomain = NULL;
460                 }
461
462                 if (retire)
463                         break;
464
465                 previous_tpdomain = tpdomain;
466         }
467
468         domains_unlock ();
469
470         mono_coop_mutex_lock (&threadpool->threads_lock);
471
472         g_ptr_array_remove_fast (threadpool->threads, thread);
473
474         mono_coop_cond_signal (&threadpool->threads_exit_cond);
475
476         mono_coop_mutex_unlock (&threadpool->threads_lock);
477
478         COUNTER_ATOMIC (threadpool, counter, {
479                 counter._.working --;
480         });
481
482         mono_refcount_dec (threadpool);
483 }
484
485 void
486 mono_threadpool_cleanup (void)
487 {
488 #ifndef DISABLE_SOCKETS
489         mono_threadpool_io_cleanup ();
490 #endif
491         mono_lazy_cleanup (&status, cleanup);
492 }
493
494 MonoAsyncResult *
495 mono_threadpool_begin_invoke (MonoDomain *domain, MonoObject *target, MonoMethod *method, gpointer *params, MonoError *error)
496 {
497         static MonoClass *async_call_klass = NULL;
498         MonoMethodMessage *message;
499         MonoAsyncResult *async_result;
500         MonoAsyncCall *async_call;
501         MonoDelegate *async_callback = NULL;
502         MonoObject *state = NULL;
503
504         if (!async_call_klass)
505                 async_call_klass = mono_class_load_from_name (mono_defaults.corlib, "System", "MonoAsyncCall");
506
507         mono_lazy_initialize (&status, initialize);
508
509         mono_error_init (error);
510
511         message = mono_method_call_message_new (method, params, mono_get_delegate_invoke (method->klass), (params != NULL) ? (&async_callback) : NULL, (params != NULL) ? (&state) : NULL, error);
512         return_val_if_nok (error, NULL);
513
514         async_call = (MonoAsyncCall*) mono_object_new_checked (domain, async_call_klass, error);
515         return_val_if_nok (error, NULL);
516
517         MONO_OBJECT_SETREF (async_call, msg, message);
518         MONO_OBJECT_SETREF (async_call, state, state);
519
520         if (async_callback) {
521                 MONO_OBJECT_SETREF (async_call, cb_method, mono_get_delegate_invoke (((MonoObject*) async_callback)->vtable->klass));
522                 MONO_OBJECT_SETREF (async_call, cb_target, async_callback);
523         }
524
525         async_result = mono_async_result_new (domain, NULL, async_call->state, NULL, (MonoObject*) async_call, error);
526         return_val_if_nok (error, NULL);
527         MONO_OBJECT_SETREF (async_result, async_delegate, target);
528
529         mono_threadpool_enqueue_work_item (domain, (MonoObject*) async_result, error);
530         return_val_if_nok (error, NULL);
531
532         return async_result;
533 }
534
535 MonoObject *
536 mono_threadpool_end_invoke (MonoAsyncResult *ares, MonoArray **out_args, MonoObject **exc, MonoError *error)
537 {
538         MonoAsyncCall *ac;
539
540         mono_error_init (error);
541         g_assert (exc);
542         g_assert (out_args);
543
544         *exc = NULL;
545         *out_args = NULL;
546
547         /* check if already finished */
548         mono_monitor_enter ((MonoObject*) ares);
549
550         if (ares->endinvoke_called) {
551                 mono_error_set_invalid_operation(error, "Delegate EndInvoke method called more than once");
552                 mono_monitor_exit ((MonoObject*) ares);
553                 return NULL;
554         }
555
556         ares->endinvoke_called = 1;
557
558         /* wait until we are really finished */
559         if (ares->completed) {
560                 mono_monitor_exit ((MonoObject *) ares);
561         } else {
562                 gpointer wait_event;
563                 if (ares->handle) {
564                         wait_event = mono_wait_handle_get_handle ((MonoWaitHandle*) ares->handle);
565                 } else {
566                         wait_event = mono_w32event_create (TRUE, FALSE);
567                         g_assert(wait_event);
568                         MonoWaitHandle *wait_handle = mono_wait_handle_new (mono_object_domain (ares), wait_event, error);
569                         if (!is_ok (error)) {
570                                 CloseHandle (wait_event);
571                                 return NULL;
572                         }
573                         MONO_OBJECT_SETREF (ares, handle, (MonoObject*) wait_handle);
574                 }
575                 mono_monitor_exit ((MonoObject*) ares);
576                 MONO_ENTER_GC_SAFE;
577 #ifdef HOST_WIN32
578                 WaitForSingleObjectEx (wait_event, INFINITE, TRUE);
579 #else
580                 mono_w32handle_wait_one (wait_event, MONO_INFINITE_WAIT, TRUE);
581 #endif
582                 MONO_EXIT_GC_SAFE;
583         }
584
585         ac = (MonoAsyncCall*) ares->object_data;
586         g_assert (ac);
587
588         *exc = ac->msg->exc; /* FIXME: GC add write barrier */
589         *out_args = ac->out_args;
590         return ac->res;
591 }
592
593 gboolean
594 mono_threadpool_remove_domain_jobs (MonoDomain *domain, int timeout)
595 {
596         gint64 end;
597         ThreadPoolDomain *tpdomain;
598         gboolean ret;
599
600         g_assert (domain);
601         g_assert (timeout >= -1);
602
603         g_assert (mono_domain_is_unloading (domain));
604
605         if (timeout != -1)
606                 end = mono_msec_ticks () + timeout;
607
608 #ifndef DISABLE_SOCKETS
609         mono_threadpool_io_remove_domain_jobs (domain);
610         if (timeout != -1) {
611                 if (mono_msec_ticks () > end)
612                         return FALSE;
613         }
614 #endif
615
616         /*
617          * Wait for all threads which execute jobs in the domain to exit.
618          * The is_unloading () check in worker_request () ensures that
619          * no new jobs are added after we enter the lock below.
620          */
621
622         if (!mono_lazy_is_initialized (&status))
623                 return TRUE;
624
625         mono_refcount_inc (threadpool);
626
627         domains_lock ();
628
629         tpdomain = tpdomain_get (domain, FALSE);
630         if (!tpdomain) {
631                 domains_unlock ();
632                 mono_refcount_dec (threadpool);
633                 return TRUE;
634         }
635
636         ret = TRUE;
637
638         while (tpdomain->outstanding_request + tpdomain->threadpool_jobs > 0) {
639                 if (timeout == -1) {
640                         mono_coop_cond_wait (&tpdomain->cleanup_cond, &threadpool->domains_lock);
641                 } else {
642                         gint64 now;
643                         gint res;
644
645                         now = mono_msec_ticks();
646                         if (now > end) {
647                                 ret = FALSE;
648                                 break;
649                         }
650
651                         res = mono_coop_cond_timedwait (&tpdomain->cleanup_cond, &threadpool->domains_lock, end - now);
652                         if (res != 0) {
653                                 ret = FALSE;
654                                 break;
655                         }
656                 }
657         }
658
659         /* Remove from the list the worker threads look at */
660         tpdomain_remove (tpdomain);
661
662         domains_unlock ();
663
664         mono_coop_cond_destroy (&tpdomain->cleanup_cond);
665         tpdomain_free (tpdomain);
666
667         mono_refcount_dec (threadpool);
668
669         return ret;
670 }
671
672 void
673 mono_threadpool_suspend (void)
674 {
675         if (threadpool)
676                 mono_threadpool_worker_set_suspended (threadpool->worker, TRUE);
677 }
678
679 void
680 mono_threadpool_resume (void)
681 {
682         if (threadpool)
683                 mono_threadpool_worker_set_suspended (threadpool->worker, FALSE);
684 }
685
686 void
687 ves_icall_System_Threading_ThreadPool_GetAvailableThreadsNative (gint32 *worker_threads, gint32 *completion_port_threads)
688 {
689         ThreadPoolCounter counter;
690
691         if (!worker_threads || !completion_port_threads)
692                 return;
693
694         mono_lazy_initialize (&status, initialize);
695
696         counter = COUNTER_READ (threadpool);
697
698         *worker_threads = MAX (0, mono_threadpool_worker_get_max (threadpool->worker) - counter._.working);
699         *completion_port_threads = threadpool->limit_io_max;
700 }
701
702 void
703 ves_icall_System_Threading_ThreadPool_GetMinThreadsNative (gint32 *worker_threads, gint32 *completion_port_threads)
704 {
705         if (!worker_threads || !completion_port_threads)
706                 return;
707
708         mono_lazy_initialize (&status, initialize);
709
710         *worker_threads = mono_threadpool_worker_get_min (threadpool->worker);
711         *completion_port_threads = threadpool->limit_io_min;
712 }
713
714 void
715 ves_icall_System_Threading_ThreadPool_GetMaxThreadsNative (gint32 *worker_threads, gint32 *completion_port_threads)
716 {
717         if (!worker_threads || !completion_port_threads)
718                 return;
719
720         mono_lazy_initialize (&status, initialize);
721
722         *worker_threads = mono_threadpool_worker_get_max (threadpool->worker);
723         *completion_port_threads = threadpool->limit_io_max;
724 }
725
726 MonoBoolean
727 ves_icall_System_Threading_ThreadPool_SetMinThreadsNative (gint32 worker_threads, gint32 completion_port_threads)
728 {
729         mono_lazy_initialize (&status, initialize);
730
731         if (completion_port_threads <= 0 || completion_port_threads > threadpool->limit_io_max)
732                 return FALSE;
733
734         if (!mono_threadpool_worker_set_min (threadpool->worker, worker_threads))
735                 return FALSE;
736
737         threadpool->limit_io_min = completion_port_threads;
738
739         return TRUE;
740 }
741
742 MonoBoolean
743 ves_icall_System_Threading_ThreadPool_SetMaxThreadsNative (gint32 worker_threads, gint32 completion_port_threads)
744 {
745         gint cpu_count = mono_cpu_count ();
746
747         mono_lazy_initialize (&status, initialize);
748
749         if (completion_port_threads < threadpool->limit_io_min || completion_port_threads < cpu_count)
750                 return FALSE;
751
752         if (!mono_threadpool_worker_set_max (threadpool->worker, worker_threads))
753                 return FALSE;
754
755         threadpool->limit_io_max = completion_port_threads;
756
757         return TRUE;
758 }
759
760 void
761 ves_icall_System_Threading_ThreadPool_InitializeVMTp (MonoBoolean *enable_worker_tracking)
762 {
763         if (enable_worker_tracking) {
764                 // TODO implement some kind of switch to have the possibily to use it
765                 *enable_worker_tracking = FALSE;
766         }
767
768         mono_lazy_initialize (&status, initialize);
769 }
770
771 MonoBoolean
772 ves_icall_System_Threading_ThreadPool_NotifyWorkItemComplete (void)
773 {
774         if (mono_domain_is_unloading (mono_domain_get ()) || mono_runtime_is_shutting_down ())
775                 return FALSE;
776
777         return mono_threadpool_worker_notify_completed (threadpool->worker);
778 }
779
780 void
781 ves_icall_System_Threading_ThreadPool_NotifyWorkItemProgressNative (void)
782 {
783         mono_threadpool_worker_notify_completed (threadpool->worker);
784 }
785
786 void
787 ves_icall_System_Threading_ThreadPool_ReportThreadStatus (MonoBoolean is_working)
788 {
789         // TODO
790         MonoError error;
791         mono_error_set_not_implemented (&error, "");
792         mono_error_set_pending_exception (&error);
793 }
794
795 MonoBoolean
796 ves_icall_System_Threading_ThreadPool_RequestWorkerThread (void)
797 {
798         MonoDomain *domain;
799         ThreadPoolDomain *tpdomain;
800         ThreadPoolCounter counter;
801
802         domain = mono_domain_get ();
803         if (mono_domain_is_unloading (domain))
804                 return FALSE;
805
806         if (!mono_refcount_tryinc (threadpool)) {
807                 /* threadpool has been destroyed, we are shutting down */
808                 return FALSE;
809         }
810
811         domains_lock ();
812
813         /* synchronize with mono_threadpool_remove_domain_jobs */
814         if (mono_domain_is_unloading (domain)) {
815                 domains_unlock ();
816                 mono_refcount_dec (threadpool);
817                 return FALSE;
818         }
819
820         tpdomain = tpdomain_get (domain, TRUE);
821         g_assert (tpdomain);
822
823         tpdomain->outstanding_request ++;
824         g_assert (tpdomain->outstanding_request >= 1);
825
826         domains_unlock ();
827
828         COUNTER_ATOMIC (threadpool, counter, {
829                 if (counter._.starting == 16) {
830                         mono_refcount_dec (threadpool);
831                         return TRUE;
832                 }
833
834                 counter._.starting ++;
835         });
836
837         mono_threadpool_worker_enqueue (threadpool->worker, worker_callback, NULL);
838
839         return TRUE;
840 }
841
842 MonoBoolean G_GNUC_UNUSED
843 ves_icall_System_Threading_ThreadPool_PostQueuedCompletionStatus (MonoNativeOverlapped *native_overlapped)
844 {
845         /* This copy the behavior of the current Mono implementation */
846         MonoError error;
847         mono_error_set_not_implemented (&error, "");
848         mono_error_set_pending_exception (&error);
849         return FALSE;
850 }
851
852 MonoBoolean G_GNUC_UNUSED
853 ves_icall_System_Threading_ThreadPool_BindIOCompletionCallbackNative (gpointer file_handle)
854 {
855         /* This copy the behavior of the current Mono implementation */
856         return TRUE;
857 }
858
859 MonoBoolean G_GNUC_UNUSED
860 ves_icall_System_Threading_ThreadPool_IsThreadPoolHosted (void)
861 {
862         return FALSE;
863 }