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
)
127 g_ptr_array_free (threadpool
->domains
, TRUE
);
128 mono_coop_mutex_destroy (&threadpool
->domains_lock
);
130 g_ptr_array_free (threadpool
->threads
, TRUE
);
131 mono_coop_mutex_destroy (&threadpool
->threads_lock
);
132 mono_coop_cond_destroy (&threadpool
->threads_exit_cond
);
134 /* We cannot free the threadpool, because there is a race
135 * on shutdown where a managed thread may request a new
136 * threadpool thread, but we already destroyed the
137 * threadpool. So to avoid a use-after-free, we simply do
138 * not free the threadpool, as we won't be able to access
139 * the threadpool anyway because the ref count will be 0 */
140 // g_free (threadpool);
146 g_assert (!threadpool
);
147 threadpool
= g_new0 (ThreadPool
, 1);
148 g_assert (threadpool
);
150 g_assert (sizeof (ThreadPoolCounter
) == sizeof (gint32
));
152 mono_refcount_init (threadpool
, destroy
);
154 threadpool
->domains
= g_ptr_array_new ();
155 mono_coop_mutex_init (&threadpool
->domains_lock
);
157 threadpool
->threads
= g_ptr_array_new ();
158 mono_coop_mutex_init (&threadpool
->threads_lock
);
159 mono_coop_cond_init (&threadpool
->threads_exit_cond
);
161 threadpool
->limit_io_min
= mono_cpu_count ();
162 threadpool
->limit_io_max
= CLAMP (threadpool
->limit_io_min
* 100, MIN (threadpool
->limit_io_min
, 200), MAX (threadpool
->limit_io_min
, 200));
164 mono_threadpool_worker_init (&threadpool
->worker
);
171 MonoInternalThread
*current
;
173 /* we make the assumption along the code that we are
174 * cleaning up only if the runtime is shutting down */
175 g_assert (mono_runtime_is_shutting_down ());
177 current
= mono_thread_internal_current ();
179 mono_coop_mutex_lock (&threadpool
->threads_lock
);
181 /* stop all threadpool->threads */
182 for (i
= 0; i
< threadpool
->threads
->len
; ++i
) {
183 MonoInternalThread
*thread
= (MonoInternalThread
*) g_ptr_array_index (threadpool
->threads
, i
);
184 if (thread
!= current
)
185 mono_thread_internal_abort (thread
);
188 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
191 /* give a chance to the other threads to exit */
192 mono_thread_info_yield ();
194 mono_coop_mutex_lock (&threadpool
->threads_lock
);
197 if (threadpool
->threads
->len
== 0)
200 if (threadpool
->threads
->len
== 1 && g_ptr_array_index (threadpool
->threads
, 0) == current
) {
201 /* We are waiting on ourselves */
205 mono_coop_cond_wait (&threadpool
->threads_exit_cond
, &threadpool
->threads_lock
);
208 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
211 mono_threadpool_worker_cleanup (threadpool
->worker
);
213 mono_refcount_dec (threadpool
);
217 mono_threadpool_enqueue_work_item (MonoDomain
*domain
, MonoObject
*work_item
, MonoError
*error
)
219 static MonoClass
*threadpool_class
= NULL
;
220 static MonoMethod
*unsafe_queue_custom_work_item_method
= NULL
;
221 MonoDomain
*current_domain
;
225 mono_error_init (error
);
226 g_assert (work_item
);
228 if (!threadpool_class
)
229 threadpool_class
= mono_class_load_from_name (mono_defaults
.corlib
, "System.Threading", "ThreadPool");
231 if (!unsafe_queue_custom_work_item_method
)
232 unsafe_queue_custom_work_item_method
= mono_class_get_method_from_name (threadpool_class
, "UnsafeQueueCustomWorkItem", 2);
233 g_assert (unsafe_queue_custom_work_item_method
);
237 args
[0] = (gpointer
) work_item
;
238 args
[1] = (gpointer
) &f
;
240 current_domain
= mono_domain_get ();
241 if (current_domain
== domain
) {
242 mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method
, NULL
, args
, error
);
243 return_val_if_nok (error
, FALSE
);
245 mono_thread_push_appdomain_ref (domain
);
246 if (mono_domain_set (domain
, FALSE
)) {
247 mono_runtime_invoke_checked (unsafe_queue_custom_work_item_method
, NULL
, args
, error
);
248 if (!is_ok (error
)) {
249 mono_thread_pop_appdomain_ref ();
252 mono_domain_set (current_domain
, TRUE
);
254 mono_thread_pop_appdomain_ref ();
259 /* LOCKING: domains_lock must be held */
261 tpdomain_add (ThreadPoolDomain
*tpdomain
)
267 len
= threadpool
->domains
->len
;
268 for (i
= 0; i
< len
; ++i
) {
269 if (g_ptr_array_index (threadpool
->domains
, i
) == tpdomain
)
274 g_ptr_array_add (threadpool
->domains
, tpdomain
);
277 /* LOCKING: domains_lock must be held. */
279 tpdomain_remove (ThreadPoolDomain
*tpdomain
)
282 return g_ptr_array_remove (threadpool
->domains
, tpdomain
);
285 /* LOCKING: domains_lock must be held */
286 static ThreadPoolDomain
*
287 tpdomain_get (MonoDomain
*domain
, gboolean create
)
290 ThreadPoolDomain
*tpdomain
;
294 for (i
= 0; i
< threadpool
->domains
->len
; ++i
) {
295 ThreadPoolDomain
*tpdomain
;
297 tpdomain
= (ThreadPoolDomain
*)g_ptr_array_index (threadpool
->domains
, i
);
298 if (tpdomain
->domain
== domain
)
305 tpdomain
= g_new0 (ThreadPoolDomain
, 1);
306 tpdomain
->domain
= domain
;
307 mono_coop_cond_init (&tpdomain
->cleanup_cond
);
309 tpdomain_add (tpdomain
);
315 tpdomain_free (ThreadPoolDomain
*tpdomain
)
320 /* LOCKING: domains_lock must be held */
321 static ThreadPoolDomain
*
322 tpdomain_get_next (ThreadPoolDomain
*current
)
324 ThreadPoolDomain
*tpdomain
= NULL
;
327 len
= threadpool
->domains
->len
;
329 guint i
, current_idx
= -1;
331 for (i
= 0; i
< len
; ++i
) {
332 if (current
== g_ptr_array_index (threadpool
->domains
, i
)) {
337 g_assert (current_idx
!= (guint
)-1);
339 for (i
= current_idx
+ 1; i
< len
+ current_idx
+ 1; ++i
) {
340 ThreadPoolDomain
*tmp
= (ThreadPoolDomain
*)g_ptr_array_index (threadpool
->domains
, i
% len
);
341 if (tmp
->outstanding_request
> 0) {
352 try_invoke_perform_wait_callback (MonoObject
** exc
, MonoError
*error
)
354 HANDLE_FUNCTION_ENTER ();
355 mono_error_init (error
);
356 MonoObject
*res
= mono_runtime_try_invoke (mono_defaults
.threadpool_perform_wait_callback_method
, NULL
, NULL
, exc
, error
);
357 HANDLE_FUNCTION_RETURN_VAL (res
);
361 worker_callback (gpointer unused
)
364 ThreadPoolDomain
*tpdomain
, *previous_tpdomain
;
365 ThreadPoolCounter counter
;
366 MonoInternalThread
*thread
;
368 thread
= mono_thread_internal_current ();
370 COUNTER_ATOMIC (threadpool
, counter
, {
371 if (!(counter
._
.working
< 32767 /* G_MAXINT16 */))
372 g_error ("%s: counter._.working = %d, but should be < 32767", __func__
, counter
._
.working
);
374 counter
._
.starting
--;
375 counter
._
.working
++;
378 if (mono_runtime_is_shutting_down ()) {
379 COUNTER_ATOMIC (threadpool
, counter
, {
380 counter
._
.working
--;
383 mono_refcount_dec (threadpool
);
387 mono_coop_mutex_lock (&threadpool
->threads_lock
);
388 g_ptr_array_add (threadpool
->threads
, thread
);
389 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
392 * This is needed so there is always an lmf frame in the runtime invoke call below,
393 * so ThreadAbortExceptions are caught even if the thread is in native code.
395 mono_defaults
.threadpool_perform_wait_callback_method
->save_lmf
= TRUE
;
399 previous_tpdomain
= NULL
;
401 while (!mono_runtime_is_shutting_down ()) {
402 gboolean retire
= FALSE
;
404 if ((thread
->state
& (ThreadState_AbortRequested
| ThreadState_SuspendRequested
)) != 0) {
406 mono_thread_interruption_checkpoint ();
410 tpdomain
= tpdomain_get_next (previous_tpdomain
);
414 tpdomain
->outstanding_request
--;
415 g_assert (tpdomain
->outstanding_request
>= 0);
417 mono_trace (G_LOG_LEVEL_DEBUG
, MONO_TRACE_THREADPOOL
, "[%p] worker running in domain %p (outstanding requests %d)",
418 mono_native_thread_id_get (), tpdomain
->domain
, tpdomain
->outstanding_request
);
420 g_assert (tpdomain
->threadpool_jobs
>= 0);
421 tpdomain
->threadpool_jobs
++;
425 mono_thread_clr_state (thread
, (MonoThreadState
)~ThreadState_Background
);
426 if (!mono_thread_test_state (thread
, ThreadState_Background
))
427 ves_icall_System_Threading_Thread_SetState (thread
, ThreadState_Background
);
429 mono_thread_push_appdomain_ref (tpdomain
->domain
);
430 if (mono_domain_set (tpdomain
->domain
, FALSE
)) {
431 MonoObject
*exc
= NULL
, *res
;
433 res
= try_invoke_perform_wait_callback (&exc
, &error
);
434 if (exc
|| !mono_error_ok(&error
)) {
436 exc
= (MonoObject
*) mono_error_convert_to_exception (&error
);
438 mono_error_cleanup (&error
);
439 mono_thread_internal_unhandled_exception (exc
);
440 } else if (res
&& *(MonoBoolean
*) mono_object_unbox (res
) == FALSE
) {
444 mono_domain_set (mono_get_root_domain (), TRUE
);
446 mono_thread_pop_appdomain_ref ();
450 tpdomain
->threadpool_jobs
--;
451 g_assert (tpdomain
->threadpool_jobs
>= 0);
453 if (tpdomain
->outstanding_request
+ tpdomain
->threadpool_jobs
== 0 && mono_domain_is_unloading (tpdomain
->domain
)) {
456 removed
= tpdomain_remove (tpdomain
);
459 mono_coop_cond_signal (&tpdomain
->cleanup_cond
);
466 previous_tpdomain
= tpdomain
;
471 mono_coop_mutex_lock (&threadpool
->threads_lock
);
473 g_ptr_array_remove_fast (threadpool
->threads
, thread
);
475 mono_coop_cond_signal (&threadpool
->threads_exit_cond
);
477 mono_coop_mutex_unlock (&threadpool
->threads_lock
);
479 COUNTER_ATOMIC (threadpool
, counter
, {
480 counter
._
.working
--;
483 mono_refcount_dec (threadpool
);
487 mono_threadpool_cleanup (void)
489 #ifndef DISABLE_SOCKETS
490 mono_threadpool_io_cleanup ();
492 mono_lazy_cleanup (&status
, cleanup
);
496 mono_threadpool_begin_invoke (MonoDomain
*domain
, MonoObject
*target
, MonoMethod
*method
, gpointer
*params
, MonoError
*error
)
498 static MonoClass
*async_call_klass
= NULL
;
499 MonoMethodMessage
*message
;
500 MonoAsyncResult
*async_result
;
501 MonoAsyncCall
*async_call
;
502 MonoDelegate
*async_callback
= NULL
;
503 MonoObject
*state
= NULL
;
505 if (!async_call_klass
)
506 async_call_klass
= mono_class_load_from_name (mono_defaults
.corlib
, "System", "MonoAsyncCall");
508 mono_lazy_initialize (&status
, initialize
);
510 mono_error_init (error
);
512 message
= mono_method_call_message_new (method
, params
, mono_get_delegate_invoke (method
->klass
), (params
!= NULL
) ? (&async_callback
) : NULL
, (params
!= NULL
) ? (&state
) : NULL
, error
);
513 return_val_if_nok (error
, NULL
);
515 async_call
= (MonoAsyncCall
*) mono_object_new_checked (domain
, async_call_klass
, error
);
516 return_val_if_nok (error
, NULL
);
518 MONO_OBJECT_SETREF (async_call
, msg
, message
);
519 MONO_OBJECT_SETREF (async_call
, state
, state
);
521 if (async_callback
) {
522 MONO_OBJECT_SETREF (async_call
, cb_method
, mono_get_delegate_invoke (((MonoObject
*) async_callback
)->vtable
->klass
));
523 MONO_OBJECT_SETREF (async_call
, cb_target
, async_callback
);
526 async_result
= mono_async_result_new (domain
, NULL
, async_call
->state
, NULL
, (MonoObject
*) async_call
, error
);
527 return_val_if_nok (error
, NULL
);
528 MONO_OBJECT_SETREF (async_result
, async_delegate
, target
);
530 mono_threadpool_enqueue_work_item (domain
, (MonoObject
*) async_result
, error
);
531 return_val_if_nok (error
, NULL
);
537 mono_threadpool_end_invoke (MonoAsyncResult
*ares
, MonoArray
**out_args
, MonoObject
**exc
, MonoError
*error
)
541 mono_error_init (error
);
548 /* check if already finished */
549 mono_monitor_enter ((MonoObject
*) ares
);
551 if (ares
->endinvoke_called
) {
552 mono_error_set_invalid_operation(error
, "Delegate EndInvoke method called more than once");
553 mono_monitor_exit ((MonoObject
*) ares
);
557 ares
->endinvoke_called
= 1;
559 /* wait until we are really finished */
560 if (ares
->completed
) {
561 mono_monitor_exit ((MonoObject
*) ares
);
565 wait_event
= mono_wait_handle_get_handle ((MonoWaitHandle
*) ares
->handle
);
567 wait_event
= mono_w32event_create (TRUE
, FALSE
);
568 g_assert(wait_event
);
569 MonoWaitHandle
*wait_handle
= mono_wait_handle_new (mono_object_domain (ares
), wait_event
, error
);
570 if (!is_ok (error
)) {
571 mono_w32event_close (wait_event
);
574 MONO_OBJECT_SETREF (ares
, handle
, (MonoObject
*) wait_handle
);
576 mono_monitor_exit ((MonoObject
*) ares
);
579 WaitForSingleObjectEx (wait_event
, INFINITE
, TRUE
);
581 mono_w32handle_wait_one (wait_event
, MONO_INFINITE_WAIT
, TRUE
);
586 ac
= (MonoAsyncCall
*) ares
->object_data
;
589 *exc
= ac
->msg
->exc
; /* FIXME: GC add write barrier */
590 *out_args
= ac
->out_args
;
595 mono_threadpool_remove_domain_jobs (MonoDomain
*domain
, int timeout
)
598 ThreadPoolDomain
*tpdomain
;
602 g_assert (timeout
>= -1);
604 g_assert (mono_domain_is_unloading (domain
));
607 end
= mono_msec_ticks () + timeout
;
609 #ifndef DISABLE_SOCKETS
610 mono_threadpool_io_remove_domain_jobs (domain
);
612 if (mono_msec_ticks () > end
)
618 * Wait for all threads which execute jobs in the domain to exit.
619 * The is_unloading () check in worker_request () ensures that
620 * no new jobs are added after we enter the lock below.
623 if (!mono_lazy_is_initialized (&status
))
626 mono_refcount_inc (threadpool
);
630 tpdomain
= tpdomain_get (domain
, FALSE
);
633 mono_refcount_dec (threadpool
);
639 while (tpdomain
->outstanding_request
+ tpdomain
->threadpool_jobs
> 0) {
641 mono_coop_cond_wait (&tpdomain
->cleanup_cond
, &threadpool
->domains_lock
);
646 now
= mono_msec_ticks();
652 res
= mono_coop_cond_timedwait (&tpdomain
->cleanup_cond
, &threadpool
->domains_lock
, end
- now
);
660 /* Remove from the list the worker threads look at */
661 tpdomain_remove (tpdomain
);
665 mono_coop_cond_destroy (&tpdomain
->cleanup_cond
);
666 tpdomain_free (tpdomain
);
668 mono_refcount_dec (threadpool
);
674 mono_threadpool_suspend (void)
677 mono_threadpool_worker_set_suspended (threadpool
->worker
, TRUE
);
681 mono_threadpool_resume (void)
684 mono_threadpool_worker_set_suspended (threadpool
->worker
, FALSE
);
688 ves_icall_System_Threading_ThreadPool_GetAvailableThreadsNative (gint32
*worker_threads
, gint32
*completion_port_threads
)
690 ThreadPoolCounter counter
;
692 if (!worker_threads
|| !completion_port_threads
)
695 mono_lazy_initialize (&status
, initialize
);
697 counter
= COUNTER_READ (threadpool
);
699 *worker_threads
= MAX (0, mono_threadpool_worker_get_max (threadpool
->worker
) - counter
._
.working
);
700 *completion_port_threads
= threadpool
->limit_io_max
;
704 ves_icall_System_Threading_ThreadPool_GetMinThreadsNative (gint32
*worker_threads
, gint32
*completion_port_threads
)
706 if (!worker_threads
|| !completion_port_threads
)
709 mono_lazy_initialize (&status
, initialize
);
711 *worker_threads
= mono_threadpool_worker_get_min (threadpool
->worker
);
712 *completion_port_threads
= threadpool
->limit_io_min
;
716 ves_icall_System_Threading_ThreadPool_GetMaxThreadsNative (gint32
*worker_threads
, gint32
*completion_port_threads
)
718 if (!worker_threads
|| !completion_port_threads
)
721 mono_lazy_initialize (&status
, initialize
);
723 *worker_threads
= mono_threadpool_worker_get_max (threadpool
->worker
);
724 *completion_port_threads
= threadpool
->limit_io_max
;
728 ves_icall_System_Threading_ThreadPool_SetMinThreadsNative (gint32 worker_threads
, gint32 completion_port_threads
)
730 mono_lazy_initialize (&status
, initialize
);
732 if (completion_port_threads
<= 0 || completion_port_threads
> threadpool
->limit_io_max
)
735 if (!mono_threadpool_worker_set_min (threadpool
->worker
, worker_threads
))
738 threadpool
->limit_io_min
= completion_port_threads
;
744 ves_icall_System_Threading_ThreadPool_SetMaxThreadsNative (gint32 worker_threads
, gint32 completion_port_threads
)
746 gint cpu_count
= mono_cpu_count ();
748 mono_lazy_initialize (&status
, initialize
);
750 if (completion_port_threads
< threadpool
->limit_io_min
|| completion_port_threads
< cpu_count
)
753 if (!mono_threadpool_worker_set_max (threadpool
->worker
, worker_threads
))
756 threadpool
->limit_io_max
= completion_port_threads
;
762 ves_icall_System_Threading_ThreadPool_InitializeVMTp (MonoBoolean
*enable_worker_tracking
)
764 if (enable_worker_tracking
) {
765 // TODO implement some kind of switch to have the possibily to use it
766 *enable_worker_tracking
= FALSE
;
769 mono_lazy_initialize (&status
, initialize
);
773 ves_icall_System_Threading_ThreadPool_NotifyWorkItemComplete (void)
775 if (mono_domain_is_unloading (mono_domain_get ()) || mono_runtime_is_shutting_down ())
778 return mono_threadpool_worker_notify_completed (threadpool
->worker
);
782 ves_icall_System_Threading_ThreadPool_NotifyWorkItemProgressNative (void)
784 mono_threadpool_worker_notify_completed (threadpool
->worker
);
788 ves_icall_System_Threading_ThreadPool_ReportThreadStatus (MonoBoolean is_working
)
792 mono_error_set_not_implemented (&error
, "");
793 mono_error_set_pending_exception (&error
);
797 ves_icall_System_Threading_ThreadPool_RequestWorkerThread (void)
800 ThreadPoolDomain
*tpdomain
;
801 ThreadPoolCounter counter
;
803 domain
= mono_domain_get ();
804 if (mono_domain_is_unloading (domain
))
807 if (!mono_refcount_tryinc (threadpool
)) {
808 /* threadpool has been destroyed, we are shutting down */
814 /* synchronize with mono_threadpool_remove_domain_jobs */
815 if (mono_domain_is_unloading (domain
)) {
817 mono_refcount_dec (threadpool
);
821 tpdomain
= tpdomain_get (domain
, TRUE
);
824 tpdomain
->outstanding_request
++;
825 g_assert (tpdomain
->outstanding_request
>= 1);
829 COUNTER_ATOMIC (threadpool
, counter
, {
830 if (counter
._
.starting
== 16) {
831 mono_refcount_dec (threadpool
);
835 counter
._
.starting
++;
838 mono_threadpool_worker_enqueue (threadpool
->worker
, worker_callback
, NULL
);
843 MonoBoolean G_GNUC_UNUSED
844 ves_icall_System_Threading_ThreadPool_PostQueuedCompletionStatus (MonoNativeOverlapped
*native_overlapped
)
846 /* This copy the behavior of the current Mono implementation */
848 mono_error_set_not_implemented (&error
, "");
849 mono_error_set_pending_exception (&error
);
853 MonoBoolean G_GNUC_UNUSED
854 ves_icall_System_Threading_ThreadPool_BindIOCompletionCallbackNative (gpointer file_handle
)
856 /* This copy the behavior of the current Mono implementation */
860 MonoBoolean G_GNUC_UNUSED
861 ves_icall_System_Threading_ThreadPool_IsThreadPoolHosted (void)