2 * threadpool.c: Microsoft threadpool runtime support
5 * Ludovic Henry (ludovic.henry@xamarin.com)
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.
12 // Copyright (c) Microsoft. All rights reserved.
13 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
16 // - src/vm/comthreadpool.cpp
17 // - src/vm/win32threadpoolcpp
18 // - src/vm/threadpoolrequest.cpp
19 // - src/vm/hillclimbing.cpp
21 // Ported from C++ to C and adjusted to Mono runtime
24 #define _USE_MATH_DEFINES // needed by MSVC to define math constants
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>
51 /* Number of outstanding jobs */
52 gint32 outstanding_request
;
53 /* Number of currently executing jobs */
54 gint32 threadpool_jobs
;
55 /* Signalled when threadpool_jobs + outstanding_request is 0 */
56 /* Protected by threadpool->domains_lock */
57 MonoCoopCond cleanup_cond
;
62 gint16 starting
; /* starting, but not yet in worker_callback */
63 gint16 working
; /* executing worker_callback */
71 GPtrArray
*domains
; // ThreadPoolDomain* []
72 MonoCoopMutex domains_lock
;
74 GPtrArray
*threads
; // MonoInternalThread* []
75 MonoCoopMutex threads_lock
;
76 MonoCoopCond threads_exit_cond
;
78 ThreadPoolCounter counters
;
83 MonoThreadPoolWorker
*worker
;
86 static mono_lazy_init_t status
= MONO_LAZY_INIT_STATUS_NOT_INITIALIZED
;
88 static ThreadPool
* threadpool
;
90 #define COUNTER_ATOMIC(threadpool,var,block) \
92 ThreadPoolCounter __old; \
94 g_assert (threadpool); \
95 (var) = __old = COUNTER_READ (threadpool); \
97 if (!(counter._.starting >= 0)) \
98 g_error ("%s: counter._.starting = %d, but should be >= 0", __func__, counter._.starting); \
99 if (!(counter._.working >= 0)) \
100 g_error ("%s: counter._.working = %d, but should be >= 0", __func__, counter._.working); \
101 } while (InterlockedCompareExchange (&threadpool->counters.as_gint32, (var).as_gint32, __old.as_gint32) != __old.as_gint32); \
104 static inline ThreadPoolCounter
105 COUNTER_READ (ThreadPool
*threadpool
)
107 ThreadPoolCounter counter
;
108 counter
.as_gint32
= InterlockedRead (&threadpool
->counters
.as_gint32
);
115 mono_coop_mutex_lock (&threadpool
->domains_lock
);
119 domains_unlock (void)
121 mono_coop_mutex_unlock (&threadpool
->domains_lock
);
125 destroy (gpointer unused
)
128 g_ptr_array_free (threadpool
->domains
, TRUE
);
129 mono_coop_mutex_destroy (&threadpool
->domains_lock
);
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
);
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);
148 g_assert (!threadpool
);
149 threadpool
= g_new0 (ThreadPool
, 1);
150 g_assert (threadpool
);
152 g_assert (sizeof (ThreadPoolCounter
) == sizeof (gint32
));
154 mono_refcount_init (threadpool
, destroy
);
156 threadpool
->domains
= g_ptr_array_new ();
157 mono_coop_mutex_init (&threadpool
->domains_lock
);
159 threadpool
->threads
= g_ptr_array_new ();
160 mono_coop_mutex_init (&threadpool
->threads_lock
);
161 mono_coop_cond_init (&threadpool
->threads_exit_cond
);
163 threadpool
->limit_io_min
= mono_cpu_count ();
164 threadpool
->limit_io_max
= CLAMP (threadpool
->limit_io_min
* 100, MIN (threadpool
->limit_io_min
, 200), MAX (threadpool
->limit_io_min
, 200));
166 mono_threadpool_worker_init (&threadpool
->worker
);
173 MonoInternalThread
*current
;
175 /* we make the assumption along the code that we are
176 * cleaning up only if the runtime is shutting down */
177 g_assert (mono_runtime_is_shutting_down ());
179 current
= mono_thread_internal_current ();
181 mono_coop_mutex_lock (&threadpool
->threads_lock
);
183 /* stop all threadpool->threads */
184 for (i
= 0; i
< threadpool
->threads
->len
; ++i
) {
185 MonoInternalThread
*thread
= (MonoInternalThread
*) g_ptr_array_index (threadpool
->threads
, i
);
186 if (thread
!= current
)
187 mono_thread_internal_abort (thread
);
190 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
193 /* give a chance to the other threads to exit */
194 mono_thread_info_yield ();
196 mono_coop_mutex_lock (&threadpool
->threads_lock
);
199 if (threadpool
->threads
->len
== 0)
202 if (threadpool
->threads
->len
== 1 && g_ptr_array_index (threadpool
->threads
, 0) == current
) {
203 /* We are waiting on ourselves */
207 mono_coop_cond_wait (&threadpool
->threads_exit_cond
, &threadpool
->threads_lock
);
210 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
213 mono_threadpool_worker_cleanup (threadpool
->worker
);
215 mono_refcount_dec (threadpool
);
219 mono_threadpool_enqueue_work_item (MonoDomain
*domain
, MonoObject
*work_item
, MonoError
*error
)
221 static MonoClass
*threadpool_class
= NULL
;
222 static MonoMethod
*unsafe_queue_custom_work_item_method
= NULL
;
223 MonoDomain
*current_domain
;
227 mono_error_init (error
);
228 g_assert (work_item
);
230 if (!threadpool_class
)
231 threadpool_class
= mono_class_load_from_name (mono_defaults
.corlib
, "System.Threading", "ThreadPool");
233 if (!unsafe_queue_custom_work_item_method
)
234 unsafe_queue_custom_work_item_method
= mono_class_get_method_from_name (threadpool_class
, "UnsafeQueueCustomWorkItem", 2);
235 g_assert (unsafe_queue_custom_work_item_method
);
239 args
[0] = (gpointer
) work_item
;
240 args
[1] = (gpointer
) &f
;
242 current_domain
= mono_domain_get ();
243 if (current_domain
== domain
) {
244 mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method
, NULL
, args
, error
);
245 return_val_if_nok (error
, FALSE
);
247 mono_thread_push_appdomain_ref (domain
);
248 if (mono_domain_set (domain
, FALSE
)) {
249 mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method
, NULL
, args
, error
);
250 if (!is_ok (error
)) {
251 mono_thread_pop_appdomain_ref ();
254 mono_domain_set (current_domain
, TRUE
);
256 mono_thread_pop_appdomain_ref ();
261 /* LOCKING: domains_lock must be held */
263 tpdomain_add (ThreadPoolDomain
*tpdomain
)
269 len
= threadpool
->domains
->len
;
270 for (i
= 0; i
< len
; ++i
) {
271 if (g_ptr_array_index (threadpool
->domains
, i
) == tpdomain
)
276 g_ptr_array_add (threadpool
->domains
, tpdomain
);
279 /* LOCKING: domains_lock must be held. */
281 tpdomain_remove (ThreadPoolDomain
*tpdomain
)
284 return g_ptr_array_remove (threadpool
->domains
, tpdomain
);
287 /* LOCKING: domains_lock must be held */
288 static ThreadPoolDomain
*
289 tpdomain_get (MonoDomain
*domain
, gboolean create
)
292 ThreadPoolDomain
*tpdomain
;
296 for (i
= 0; i
< threadpool
->domains
->len
; ++i
) {
297 ThreadPoolDomain
*tpdomain
;
299 tpdomain
= (ThreadPoolDomain
*)g_ptr_array_index (threadpool
->domains
, i
);
300 if (tpdomain
->domain
== domain
)
307 tpdomain
= g_new0 (ThreadPoolDomain
, 1);
308 tpdomain
->domain
= domain
;
309 mono_coop_cond_init (&tpdomain
->cleanup_cond
);
311 tpdomain_add (tpdomain
);
317 tpdomain_free (ThreadPoolDomain
*tpdomain
)
322 /* LOCKING: domains_lock must be held */
323 static ThreadPoolDomain
*
324 tpdomain_get_next (ThreadPoolDomain
*current
)
326 ThreadPoolDomain
*tpdomain
= NULL
;
329 len
= threadpool
->domains
->len
;
331 guint i
, current_idx
= -1;
333 for (i
= 0; i
< len
; ++i
) {
334 if (current
== g_ptr_array_index (threadpool
->domains
, i
)) {
339 g_assert (current_idx
!= (guint
)-1);
341 for (i
= current_idx
+ 1; i
< len
+ current_idx
+ 1; ++i
) {
342 ThreadPoolDomain
*tmp
= (ThreadPoolDomain
*)g_ptr_array_index (threadpool
->domains
, i
% len
);
343 if (tmp
->outstanding_request
> 0) {
354 try_invoke_perform_wait_callback (MonoObject
** exc
, MonoError
*error
)
356 HANDLE_FUNCTION_ENTER ();
357 mono_error_init (error
);
358 MonoObject
*res
= mono_runtime_try_invoke (mono_defaults
.threadpool_perform_wait_callback_method
, NULL
, NULL
, exc
, error
);
359 HANDLE_FUNCTION_RETURN_VAL (res
);
363 worker_callback (gpointer unused
)
366 ThreadPoolDomain
*tpdomain
, *previous_tpdomain
;
367 ThreadPoolCounter counter
;
368 MonoInternalThread
*thread
;
370 thread
= mono_thread_internal_current ();
372 COUNTER_ATOMIC (threadpool
, counter
, {
373 if (!(counter
._
.working
< 32767 /* G_MAXINT16 */))
374 g_error ("%s: counter._.working = %d, but should be < 32767", __func__
, counter
._
.working
);
376 counter
._
.starting
--;
377 counter
._
.working
++;
380 if (mono_runtime_is_shutting_down ()) {
381 COUNTER_ATOMIC (threadpool
, counter
, {
382 counter
._
.working
--;
385 mono_refcount_dec (threadpool
);
389 mono_coop_mutex_lock (&threadpool
->threads_lock
);
390 g_ptr_array_add (threadpool
->threads
, thread
);
391 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
394 * This is needed so there is always an lmf frame in the runtime invoke call below,
395 * so ThreadAbortExceptions are caught even if the thread is in native code.
397 mono_defaults
.threadpool_perform_wait_callback_method
->save_lmf
= TRUE
;
401 previous_tpdomain
= NULL
;
403 while (!mono_runtime_is_shutting_down ()) {
404 gboolean retire
= FALSE
;
406 if ((thread
->state
& (ThreadState_AbortRequested
| ThreadState_SuspendRequested
)) != 0) {
408 mono_thread_interruption_checkpoint ();
412 tpdomain
= tpdomain_get_next (previous_tpdomain
);
416 tpdomain
->outstanding_request
--;
417 g_assert (tpdomain
->outstanding_request
>= 0);
419 mono_trace (G_LOG_LEVEL_DEBUG
, MONO_TRACE_THREADPOOL
, "[%p] worker running in domain %p (outstanding requests %d)",
420 mono_native_thread_id_get (), tpdomain
->domain
, tpdomain
->outstanding_request
);
422 g_assert (tpdomain
->threadpool_jobs
>= 0);
423 tpdomain
->threadpool_jobs
++;
427 mono_thread_set_name_internal (thread
, mono_string_new (mono_get_root_domain (), "Threadpool worker"), FALSE
, TRUE
, &error
);
428 mono_error_assert_ok (&error
);
430 mono_thread_clr_state (thread
, (MonoThreadState
)~ThreadState_Background
);
431 if (!mono_thread_test_state (thread
, ThreadState_Background
))
432 ves_icall_System_Threading_Thread_SetState (thread
, ThreadState_Background
);
434 mono_thread_push_appdomain_ref (tpdomain
->domain
);
435 if (mono_domain_set (tpdomain
->domain
, FALSE
)) {
436 MonoObject
*exc
= NULL
, *res
;
438 res
= try_invoke_perform_wait_callback (&exc
, &error
);
439 if (exc
|| !mono_error_ok(&error
)) {
441 exc
= (MonoObject
*) mono_error_convert_to_exception (&error
);
443 mono_error_cleanup (&error
);
444 mono_thread_internal_unhandled_exception (exc
);
445 } else if (res
&& *(MonoBoolean
*) mono_object_unbox (res
) == FALSE
) {
449 mono_domain_set (mono_get_root_domain (), TRUE
);
451 mono_thread_pop_appdomain_ref ();
455 tpdomain
->threadpool_jobs
--;
456 g_assert (tpdomain
->threadpool_jobs
>= 0);
458 if (tpdomain
->outstanding_request
+ tpdomain
->threadpool_jobs
== 0 && mono_domain_is_unloading (tpdomain
->domain
)) {
461 removed
= tpdomain_remove (tpdomain
);
464 mono_coop_cond_signal (&tpdomain
->cleanup_cond
);
471 previous_tpdomain
= tpdomain
;
476 mono_coop_mutex_lock (&threadpool
->threads_lock
);
478 g_ptr_array_remove_fast (threadpool
->threads
, thread
);
480 mono_coop_cond_signal (&threadpool
->threads_exit_cond
);
482 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
484 COUNTER_ATOMIC (threadpool
, counter
, {
485 counter
._
.working
--;
488 mono_refcount_dec (threadpool
);
492 mono_threadpool_cleanup (void)
494 #ifndef DISABLE_SOCKETS
495 mono_threadpool_io_cleanup ();
497 mono_lazy_cleanup (&status
, cleanup
);
501 mono_threadpool_begin_invoke (MonoDomain
*domain
, MonoObject
*target
, MonoMethod
*method
, gpointer
*params
, MonoError
*error
)
503 static MonoClass
*async_call_klass
= NULL
;
504 MonoMethodMessage
*message
;
505 MonoAsyncResult
*async_result
;
506 MonoAsyncCall
*async_call
;
507 MonoDelegate
*async_callback
= NULL
;
508 MonoObject
*state
= NULL
;
510 if (!async_call_klass
)
511 async_call_klass
= mono_class_load_from_name (mono_defaults
.corlib
, "System", "MonoAsyncCall");
513 mono_lazy_initialize (&status
, initialize
);
515 mono_error_init (error
);
517 message
= mono_method_call_message_new (method
, params
, mono_get_delegate_invoke (method
->klass
), (params
!= NULL
) ? (&async_callback
) : NULL
, (params
!= NULL
) ? (&state
) : NULL
, error
);
518 return_val_if_nok (error
, NULL
);
520 async_call
= (MonoAsyncCall
*) mono_object_new_checked (domain
, async_call_klass
, error
);
521 return_val_if_nok (error
, NULL
);
523 MONO_OBJECT_SETREF (async_call
, msg
, message
);
524 MONO_OBJECT_SETREF (async_call
, state
, state
);
526 if (async_callback
) {
527 MONO_OBJECT_SETREF (async_call
, cb_method
, mono_get_delegate_invoke (((MonoObject
*) async_callback
)->vtable
->klass
));
528 MONO_OBJECT_SETREF (async_call
, cb_target
, async_callback
);
531 async_result
= mono_async_result_new (domain
, NULL
, async_call
->state
, NULL
, (MonoObject
*) async_call
, error
);
532 return_val_if_nok (error
, NULL
);
533 MONO_OBJECT_SETREF (async_result
, async_delegate
, target
);
535 mono_threadpool_enqueue_work_item (domain
, (MonoObject
*) async_result
, error
);
536 return_val_if_nok (error
, NULL
);
542 mono_threadpool_end_invoke (MonoAsyncResult
*ares
, MonoArray
**out_args
, MonoObject
**exc
, MonoError
*error
)
546 mono_error_init (error
);
553 /* check if already finished */
554 mono_monitor_enter ((MonoObject
*) ares
);
556 if (ares
->endinvoke_called
) {
557 mono_error_set_invalid_operation(error
, "Delegate EndInvoke method called more than once");
558 mono_monitor_exit ((MonoObject
*) ares
);
562 ares
->endinvoke_called
= 1;
564 /* wait until we are really finished */
565 if (ares
->completed
) {
566 mono_monitor_exit ((MonoObject
*) ares
);
570 wait_event
= mono_wait_handle_get_handle ((MonoWaitHandle
*) ares
->handle
);
572 wait_event
= mono_w32event_create (TRUE
, FALSE
);
573 g_assert(wait_event
);
574 MonoWaitHandle
*wait_handle
= mono_wait_handle_new (mono_object_domain (ares
), wait_event
, error
);
575 if (!is_ok (error
)) {
576 mono_w32event_close (wait_event
);
579 MONO_OBJECT_SETREF (ares
, handle
, (MonoObject
*) wait_handle
);
581 mono_monitor_exit ((MonoObject
*) ares
);
584 WaitForSingleObjectEx (wait_event
, INFINITE
, TRUE
);
586 mono_w32handle_wait_one (wait_event
, MONO_INFINITE_WAIT
, TRUE
);
591 ac
= (MonoAsyncCall
*) ares
->object_data
;
594 *exc
= ac
->msg
->exc
; /* FIXME: GC add write barrier */
595 *out_args
= ac
->out_args
;
600 mono_threadpool_remove_domain_jobs (MonoDomain
*domain
, int timeout
)
603 ThreadPoolDomain
*tpdomain
;
607 g_assert (timeout
>= -1);
609 g_assert (mono_domain_is_unloading (domain
));
612 end
= mono_msec_ticks () + timeout
;
614 #ifndef DISABLE_SOCKETS
615 mono_threadpool_io_remove_domain_jobs (domain
);
617 if (mono_msec_ticks () > end
)
623 * Wait for all threads which execute jobs in the domain to exit.
624 * The is_unloading () check in worker_request () ensures that
625 * no new jobs are added after we enter the lock below.
628 if (!mono_lazy_is_initialized (&status
))
631 mono_refcount_inc (threadpool
);
635 tpdomain
= tpdomain_get (domain
, FALSE
);
638 mono_refcount_dec (threadpool
);
644 while (tpdomain
->outstanding_request
+ tpdomain
->threadpool_jobs
> 0) {
646 mono_coop_cond_wait (&tpdomain
->cleanup_cond
, &threadpool
->domains_lock
);
651 now
= mono_msec_ticks();
657 res
= mono_coop_cond_timedwait (&tpdomain
->cleanup_cond
, &threadpool
->domains_lock
, end
- now
);
665 /* Remove from the list the worker threads look at */
666 tpdomain_remove (tpdomain
);
670 mono_coop_cond_destroy (&tpdomain
->cleanup_cond
);
671 tpdomain_free (tpdomain
);
673 mono_refcount_dec (threadpool
);
679 mono_threadpool_suspend (void)
682 mono_threadpool_worker_set_suspended (threadpool
->worker
, TRUE
);
686 mono_threadpool_resume (void)
689 mono_threadpool_worker_set_suspended (threadpool
->worker
, FALSE
);
693 ves_icall_System_Threading_ThreadPool_GetAvailableThreadsNative (gint32
*worker_threads
, gint32
*completion_port_threads
)
695 ThreadPoolCounter counter
;
697 if (!worker_threads
|| !completion_port_threads
)
700 mono_lazy_initialize (&status
, initialize
);
702 counter
= COUNTER_READ (threadpool
);
704 *worker_threads
= MAX (0, mono_threadpool_worker_get_max (threadpool
->worker
) - counter
._
.working
);
705 *completion_port_threads
= threadpool
->limit_io_max
;
709 ves_icall_System_Threading_ThreadPool_GetMinThreadsNative (gint32
*worker_threads
, gint32
*completion_port_threads
)
711 if (!worker_threads
|| !completion_port_threads
)
714 mono_lazy_initialize (&status
, initialize
);
716 *worker_threads
= mono_threadpool_worker_get_min (threadpool
->worker
);
717 *completion_port_threads
= threadpool
->limit_io_min
;
721 ves_icall_System_Threading_ThreadPool_GetMaxThreadsNative (gint32
*worker_threads
, gint32
*completion_port_threads
)
723 if (!worker_threads
|| !completion_port_threads
)
726 mono_lazy_initialize (&status
, initialize
);
728 *worker_threads
= mono_threadpool_worker_get_max (threadpool
->worker
);
729 *completion_port_threads
= threadpool
->limit_io_max
;
733 ves_icall_System_Threading_ThreadPool_SetMinThreadsNative (gint32 worker_threads
, gint32 completion_port_threads
)
735 mono_lazy_initialize (&status
, initialize
);
737 if (completion_port_threads
<= 0 || completion_port_threads
> threadpool
->limit_io_max
)
740 if (!mono_threadpool_worker_set_min (threadpool
->worker
, worker_threads
))
743 threadpool
->limit_io_min
= completion_port_threads
;
749 ves_icall_System_Threading_ThreadPool_SetMaxThreadsNative (gint32 worker_threads
, gint32 completion_port_threads
)
751 gint cpu_count
= mono_cpu_count ();
753 mono_lazy_initialize (&status
, initialize
);
755 if (completion_port_threads
< threadpool
->limit_io_min
|| completion_port_threads
< cpu_count
)
758 if (!mono_threadpool_worker_set_max (threadpool
->worker
, worker_threads
))
761 threadpool
->limit_io_max
= completion_port_threads
;
767 ves_icall_System_Threading_ThreadPool_InitializeVMTp (MonoBoolean
*enable_worker_tracking
)
769 if (enable_worker_tracking
) {
770 // TODO implement some kind of switch to have the possibily to use it
771 *enable_worker_tracking
= FALSE
;
774 mono_lazy_initialize (&status
, initialize
);
778 ves_icall_System_Threading_ThreadPool_NotifyWorkItemComplete (void)
780 if (mono_domain_is_unloading (mono_domain_get ()) || mono_runtime_is_shutting_down ())
783 return mono_threadpool_worker_notify_completed (threadpool
->worker
);
787 ves_icall_System_Threading_ThreadPool_NotifyWorkItemProgressNative (void)
789 mono_threadpool_worker_notify_completed (threadpool
->worker
);
793 ves_icall_System_Threading_ThreadPool_ReportThreadStatus (MonoBoolean is_working
)
797 mono_error_set_not_implemented (&error
, "");
798 mono_error_set_pending_exception (&error
);
802 ves_icall_System_Threading_ThreadPool_RequestWorkerThread (void)
805 ThreadPoolDomain
*tpdomain
;
806 ThreadPoolCounter counter
;
808 domain
= mono_domain_get ();
809 if (mono_domain_is_unloading (domain
))
812 if (!mono_refcount_tryinc (threadpool
)) {
813 /* threadpool has been destroyed, we are shutting down */
819 /* synchronize with mono_threadpool_remove_domain_jobs */
820 if (mono_domain_is_unloading (domain
)) {
822 mono_refcount_dec (threadpool
);
826 tpdomain
= tpdomain_get (domain
, TRUE
);
829 tpdomain
->outstanding_request
++;
830 g_assert (tpdomain
->outstanding_request
>= 1);
834 COUNTER_ATOMIC (threadpool
, counter
, {
835 if (counter
._
.starting
== 16) {
836 mono_refcount_dec (threadpool
);
840 counter
._
.starting
++;
843 mono_threadpool_worker_enqueue (threadpool
->worker
, worker_callback
, NULL
);
848 MonoBoolean G_GNUC_UNUSED
849 ves_icall_System_Threading_ThreadPool_PostQueuedCompletionStatus (MonoNativeOverlapped
*native_overlapped
)
851 /* This copy the behavior of the current Mono implementation */
853 mono_error_set_not_implemented (&error
, "");
854 mono_error_set_pending_exception (&error
);
858 MonoBoolean G_GNUC_UNUSED
859 ves_icall_System_Threading_ThreadPool_BindIOCompletionCallbackNative (gpointer file_handle
)
861 /* This copy the behavior of the current Mono implementation */
865 MonoBoolean G_GNUC_UNUSED
866 ves_icall_System_Threading_ThreadPool_IsThreadPoolHosted (void)