Merge pull request #2805 from alexrp/profiler-sampling-thread2
authormonojenkins <jo.shields+jenkins@xamarin.com>
Fri, 1 Apr 2016 07:00:18 +0000 (08:00 +0100)
committermonojenkins <jo.shields+jenkins@xamarin.com>
Fri, 1 Apr 2016 07:00:18 +0000 (08:00 +0100)
[profiler] Use a background thread to send out sampling signals.

Previously, when using an interval timer, the initial profiling signal could be
delivered to *any* thread in the process. This is not normally a problem, but
if the signaled thread has not even finished its initialization inside libc, it
can happen that it hasn't even set up thread-local storage yet. We then blow up
spectacularly when trying to back up errno in the SIGPROF signal handler, as
it's a TLS variable. Even the SIGSEGV handler blows up immediately after as it
can't access JIT TLS data.

Since there appears to be no reliable and portable way for a library like Mono
to control which exact thread gets the initial timer signal, instead switch to
using a background thread that uses a high-resolution sleep and attempts to
switch itself to real time scheduling if possible. This way, we have full
control over which threads we send SIGPROF to, letting us avoid any threads
which aren't in a 'good' state for profiling.

This commit also gets rid of the multiplexing that was going on in the SIGPROF
signal handler, since we now send out the signals from a background thread. As
this was the only use of the async job API, that API has been removed. This
indirectly fixes the signal storming issue that sometimes popped up, where for
some reason multiple threads would think that they're the initiating thread,
resulting in way too many signals going out.

A nice side-effect of doing the signaling ourselves is that we can now use
real-time signals on systems that have them (e.g. Linux), resulting in a nearly
100% signal delivery rate in all cases. Previously, we would lose a tremendous
amount of signals when an application was under heavy load.

configure.ac
man/mono.1
mono/mini/mini-posix.c
mono/utils/mono-threads-posix-signals.c
mono/utils/mono-threads-posix-signals.h
mono/utils/mono-threads.c
mono/utils/mono-threads.h

index 12ed84fb8431368f42b8760dd8216a919921ccd6..0341987c485a8f92e60172a01459695f365081b6 100644 (file)
@@ -1076,6 +1076,7 @@ if test x$host_win32 = xno; then
 
        dnl hires monotonic clock support
        AC_SEARCH_LIBS(clock_gettime, rt)
+       AC_CHECK_FUNCS(clock_nanosleep)
 
        dnl dynamic loader support
        AC_CHECK_FUNC(dlopen, DL_LIB="",
index 7eb8055fb4f56fdda2452b660572569c7b195c32..f0df69e7e01c56fe9c7f7fcb7f50423e5a608c60 100644 (file)
@@ -1513,26 +1513,7 @@ http://www.mono-project.com/docs/getting-started/application-deployment/
 Provides a search path to the runtime where to look for custom profilers. See the
 section "CUSTOM PROFILERS" above for more information. Custom profilers will be
 searched for in the MONO_PROFILER_LIB_DIR path before the standard library paths.
-
 .TP
-\fBMONO_RTC\fR
-Experimental RTC support in the statistical profiler: if the user has
-the permission, more accurate statistics are gathered.  The MONO_RTC
-value must be restricted to what the Linux rtc allows: power of two
-from 64 to 8192 Hz. To enable higher frequencies like 4096 Hz, run as root:
-.nf
-
-       echo 4096 > /proc/sys/dev/rtc/max-user-freq
-
-.fi
-.Sp
-For example:
-.nf
-
-       MONO_RTC=4096 mono --profiler=default:stat program.exe
-
-.fi
-.TP 
 \fBMONO_SHARED_DIR\fR
 If set its the directory where the ".wapi" handle state is stored.
 This is the directory where the Windows I/O Emulation layer stores its
index 7be84488f5877f979e259857c2383e12c373d542..ddc8cdf3dd2b8132c95ca5c3ef5fcf7b875ecec4 100644 (file)
@@ -27,7 +27,7 @@
 #include <sys/syscall.h>
 #endif
 #include <errno.h>
-
+#include <sched.h>
 
 #include <mono/metadata/assembly.h>
 #include <mono/metadata/loader.h>
@@ -59,6 +59,7 @@
 #include <mono/utils/dtrace.h>
 #include <mono/utils/mono-signal-handler.h>
 #include <mono/utils/mono-threads.h>
+#include <mono/utils/mono-threads-posix-signals.h>
 
 #include "mini.h"
 #include <string.h>
 
 #include "jit-icalls.h"
 
+#ifdef PLATFORM_MACOSX
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <mach/clock.h>
+#endif
+
 #if defined(__native_client__) || defined(HOST_WATCHOS)
 
 void
@@ -235,21 +242,11 @@ MONO_SIG_HANDLER_FUNC (static, sigabrt_signal_handler)
 #define FULL_STAT_PROFILER_BACKTRACE 0
 #endif
 
-#ifdef SIGPROF
-
-static int profiling_signal_in_use;
-
-#if defined(__ia64__) || defined(__sparc__) || defined(sparc)
-
-MONO_SIG_HANDLER_FUNC (static, sigprof_signal_handler)
-{
-       if (mono_chain_signal (MONO_SIG_HANDLER_PARAMS))
-               return;
-
-       NOT_IMPLEMENTED;
-}
+#if (defined (USE_POSIX_BACKEND) && defined (SIGRTMIN)) || defined (SIGPROF)
+#define HAVE_PROFILER_SIGNAL
+#endif
 
-#else
+#ifdef HAVE_PROFILER_SIGNAL
 
 static void
 per_thread_profiler_hit (void *ctx)
@@ -325,7 +322,7 @@ per_thread_profiler_hit (void *ctx)
        }
 }
 
-MONO_SIG_HANDLER_FUNC (static, sigprof_signal_handler)
+MONO_SIG_HANDLER_FUNC (static, profiler_signal_handler)
 {
        int old_errno = errno;
        int hp_save_index;
@@ -339,18 +336,6 @@ MONO_SIG_HANDLER_FUNC (static, sigprof_signal_handler)
 
        hp_save_index = mono_hazard_pointer_save_for_signal_handler ();
 
-       /* If we can't consume a profiling request it means we're the initiator. */
-       if (!(mono_threads_consume_async_jobs () & MONO_SERVICE_REQUEST_SAMPLE)) {
-               FOREACH_THREAD_SAFE (info) {
-                       if (mono_thread_info_get_tid (info) == mono_native_thread_id_get () ||
-                           !mono_thread_info_is_live (info))
-                               continue;
-
-                       mono_threads_add_async_job (info, MONO_SERVICE_REQUEST_SAMPLE);
-                       mono_threads_pthread_kill (info, profiling_signal_in_use);
-               } FOREACH_THREAD_SAFE_END
-       }
-
        mono_thread_info_set_is_async_context (TRUE);
        per_thread_profiler_hit (ctx);
        mono_thread_info_set_is_async_context (FALSE);
@@ -361,7 +346,6 @@ MONO_SIG_HANDLER_FUNC (static, sigprof_signal_handler)
        mono_chain_signal (MONO_SIG_HANDLER_PARAMS);
 }
 
-#endif
 #endif
 
 MONO_SIG_HANDLER_FUNC (static, sigquit_signal_handler)
@@ -522,123 +506,273 @@ mono_runtime_cleanup_handlers (void)
        free_saved_signal_handlers ();
 }
 
-#ifdef HAVE_LINUX_RTC_H
-#include <linux/rtc.h>
-#include <sys/ioctl.h>
-#include <fcntl.h>
-static int rtc_fd = -1;
+#ifdef HAVE_PROFILER_SIGNAL
+
+#ifdef PLATFORM_MACOSX
+
+static clock_serv_t sampling_clock_service;
 
-static int
-enable_rtc_timer (gboolean enable)
+static void
+clock_init (void)
 {
-       int flags;
-       flags = fcntl (rtc_fd, F_GETFL);
-       if (flags < 0) {
-               perror ("getflags");
-               return 0;
-       }
-       if (enable)
-               flags |= FASYNC;
-       else
-               flags &= ~FASYNC;
-       if (fcntl (rtc_fd, F_SETFL, flags) == -1) {
-               perror ("setflags");
-               return 0;
-       }
-       return 1;
+       kern_return_t ret;
+
+       if ((ret = host_get_clock_service (mach_host_self (), SYSTEM_CLOCK, &sampling_clock_service)) != KERN_SUCCESS)
+               g_error ("%s: host_get_clock_service () returned %d", __func__, ret);
 }
-#endif
 
-void
-mono_runtime_shutdown_stat_profiler (void)
+static void
+clock_cleanup (void)
 {
-#ifdef HAVE_LINUX_RTC_H
-       if (rtc_fd >= 0)
-               enable_rtc_timer (FALSE);
-#endif
+       kern_return_t ret;
+
+       if ((ret = mach_port_deallocate (mach_task_self (), sampling_clock_service)) != KERN_SUCCESS)
+               g_error ("%s: mach_port_deallocate () returned %d", __func__, ret);
 }
 
-#ifdef ITIMER_PROF
-static int
-get_itimer_mode (void)
+static guint64
+clock_get_time_ns (void)
 {
-       switch (mono_profiler_get_sampling_mode ()) {
-       case MONO_PROFILER_STAT_MODE_PROCESS: return ITIMER_PROF;
-       case MONO_PROFILER_STAT_MODE_REAL: return ITIMER_REAL;
-       }
-       g_assert_not_reached ();
-       return 0;
+       kern_return_t ret;
+       mach_timespec_t mach_ts;
+
+       if ((ret = clock_get_time (sampling_clock_service, &mach_ts)) != KERN_SUCCESS)
+               g_error ("%s: clock_get_time () returned %d", __func__, ret);
+
+       return ((guint64) mach_ts.tv_sec * 1000000000) + (guint64) mach_ts.tv_nsec;
 }
 
-static int
-get_itimer_signal (void)
+static void
+clock_sleep_ns_abs (guint64 ns_abs)
+{
+       kern_return_t ret;
+       mach_timespec_t then, remain_unused;
+
+       then.tv_sec = ns_abs / 1000000000;
+       then.tv_nsec = ns_abs % 1000000000;
+
+       do {
+               ret = clock_sleep (sampling_clock_service, TIME_ABSOLUTE, then, &remain_unused);
+
+               if (ret != KERN_SUCCESS && ret != KERN_ABORTED)
+                       g_error ("%s: clock_sleep () returned %d", __func__, ret);
+       } while (ret == KERN_ABORTED);
+}
+
+#else
+
+clockid_t sampling_posix_clock;
+
+static void
+clock_init (void)
 {
        switch (mono_profiler_get_sampling_mode ()) {
-       case MONO_PROFILER_STAT_MODE_PROCESS: return SIGPROF;
-       case MONO_PROFILER_STAT_MODE_REAL: return SIGALRM;
+       case MONO_PROFILER_STAT_MODE_PROCESS:
+#ifdef HAVE_CLOCK_NANOSLEEP
+               /*
+                * If we don't have clock_nanosleep (), measuring the process time
+                * makes very little sense as we can only use nanosleep () to sleep on
+                * real time.
+                */
+               sampling_posix_clock = CLOCK_PROCESS_CPUTIME_ID;
+               break;
+#endif
+       case MONO_PROFILER_STAT_MODE_REAL: sampling_posix_clock = CLOCK_MONOTONIC; break;
+       default: g_assert_not_reached (); break;
        }
-       g_assert_not_reached ();
-       return 0;
 }
+
+static void
+clock_cleanup (void)
+{
+}
+
+static guint64
+clock_get_time_ns (void)
+{
+       struct timespec ts;
+
+       if (clock_gettime (sampling_posix_clock, &ts) == -1)
+               g_error ("%s: clock_gettime () returned -1, errno = %d", __func__, errno);
+
+       return ((guint64) ts.tv_sec * 1000000000) + (guint64) ts.tv_nsec;
+}
+
+static void
+clock_sleep_ns_abs (guint64 ns_abs)
+{
+#ifdef HAVE_CLOCK_NANOSLEEP
+       int ret;
+       struct timespec then;
+
+       then.tv_sec = ns_abs / 1000000000;
+       then.tv_nsec = ns_abs % 1000000000;
+
+       do {
+               ret = clock_nanosleep (sampling_posix_clock, TIMER_ABSTIME, &then, NULL);
+
+               if (ret != 0 && ret != EINTR)
+                       g_error ("%s: clock_nanosleep () returned %d", __func__, ret);
+       } while (ret == EINTR);
+#else
+       int ret;
+       gint64 diff;
+       struct timespec req;
+
+       /*
+        * What follows is a crude attempt at emulating clock_nanosleep () on OSs
+        * which don't provide it (e.g. FreeBSD).
+        *
+        * The problem with nanosleep () is that if it is interrupted by a signal,
+        * time will drift as a result of having to restart the call after the
+        * signal handler has finished. For this reason, we avoid using the rem
+        * argument of nanosleep (). Instead, before every nanosleep () call, we
+        * check if enough time has passed to satisfy the sleep request. If yes, we
+        * simply return. If not, we calculate the difference and do another sleep.
+        *
+        * This should reduce the amount of drift that happens because we account
+        * for the time spent executing the signal handler, which nanosleep () is
+        * not guaranteed to do for the rem argument.
+        *
+        * The downside to this approach is that it is slightly expensive: We have
+        * to make an extra system call to retrieve the current time whenever we're
+        * going to restart a nanosleep () call. This is unlikely to be a problem
+        * in practice since the sampling thread won't be receiving many signals in
+        * the first place (it's a tools thread, so no STW), and because typical
+        * sleep periods for the thread are many orders of magnitude bigger than
+        * the time it takes to actually perform that system call (just a few
+        * nanoseconds).
+        */
+       do {
+               diff = (gint64) ns_abs - (gint64) clock_get_time_ns (mode);
+
+               if (diff <= 0)
+                       break;
+
+               req.tv_sec = diff / 1000000000;
+               req.tv_nsec = diff % 1000000000;
+
+               if ((ret = nanosleep (&req, NULL)) == -1 && errno != EINTR)
+                       g_error ("%s: nanosleep () returned -1, errno = %d", __func__, errno);
+       } while (ret == -1);
+#endif
+}
+
 #endif
 
+static int profiler_signal;
+static MonoNativeThreadId sampling_thread;
+static volatile gint32 sampling_thread_running;
+
+static mono_native_thread_return_t
+sampling_thread_func (void *data)
+{
+       mono_threads_attach_tools_thread ();
+
+       gint64 rate = 1000000000 / mono_profiler_get_sampling_rate ();
+
+       int old_policy;
+       struct sched_param old_sched;
+       pthread_getschedparam (pthread_self (), &old_policy, &old_sched);
+
+       /*
+        * Attempt to switch the thread to real time scheduling. This will not
+        * necessarily work on all OSs; for example, most Linux systems will give
+        * us EPERM here unless configured to allow this.
+        *
+        * TODO: This does not work on Mac (and maybe some other OSs). On Mac, we
+        * have to use the Mach thread policy routines to switch to real-time
+        * scheduling. This is quite tricky as we need to specify how often we'll
+        * be doing work (easy), the normal processing time needed (also easy),
+        * and the maximum amount of processing time needed (hard). This is
+        * further complicated by the fact that if we misbehave and take too long
+        * to do our work, the kernel may knock us back down to the normal thread
+        * scheduling policy without telling us.
+        */
+       struct sched_param sched = { .sched_priority = sched_get_priority_max (SCHED_FIFO) };
+       pthread_setschedparam (pthread_self (), SCHED_FIFO, &sched);
+
+       clock_init ();
+
+       guint64 sleep = clock_get_time_ns ();
+
+       while (InterlockedRead (&sampling_thread_running)) {
+               sleep += rate;
+
+               FOREACH_THREAD_SAFE (info) {
+                       /* info should never be this thread as we're a tools thread. */
+                       g_assert (mono_thread_info_get_tid (info) != mono_native_thread_id_get ());
+
+                       mono_threads_pthread_kill (info, profiler_signal);
+               } FOREACH_THREAD_SAFE_END
+
+               clock_sleep_ns_abs (sleep);
+       }
+
+       clock_cleanup ();
+
+       pthread_setschedparam (pthread_self (), old_policy, &old_sched);
+
+       mono_thread_info_detach ();
+
+       return NULL;
+}
+
+void
+mono_runtime_shutdown_stat_profiler (void)
+{
+       InterlockedWrite (&sampling_thread_running, 0);
+       pthread_join (sampling_thread, NULL);
+
+       /*
+        * We can't safely remove the signal handler because we have no guarantee
+        * that all pending signals have been delivered at this point. This should
+        * not really be a problem anyway.
+        */
+       //remove_signal_handler (profiler_signal);
+}
+
 void
 mono_runtime_setup_stat_profiler (void)
 {
-#ifdef ITIMER_PROF
-       struct itimerval itval;
-       static int inited = 0;
-#ifdef HAVE_LINUX_RTC_H
-       const char *rtc_freq;
-       if (!inited && (rtc_freq = g_getenv ("MONO_RTC"))) {
-               int freq = 0;
-               inited = 1;
-               if (*rtc_freq)
-                       freq = atoi (rtc_freq);
-               if (!freq)
-                       freq = 1024;
-               rtc_fd = open ("/dev/rtc", O_RDONLY);
-               if (rtc_fd == -1) {
-                       perror ("open /dev/rtc");
-                       return;
-               }
-               profiling_signal_in_use = SIGPROF;
-               add_signal_handler (profiling_signal_in_use, sigprof_signal_handler, SA_RESTART);
-               if (ioctl (rtc_fd, RTC_IRQP_SET, freq) == -1) {
-                       perror ("set rtc freq");
-                       return;
-               }
-               if (ioctl (rtc_fd, RTC_PIE_ON, 0) == -1) {
-                       perror ("start rtc");
-                       return;
-               }
-               if (fcntl (rtc_fd, F_SETSIG, SIGPROF) == -1) {
-                       perror ("setsig");
-                       return;
-               }
-               if (fcntl (rtc_fd, F_SETOWN, getpid ()) == -1) {
-                       perror ("setown");
-                       return;
-               }
-               enable_rtc_timer (TRUE);
-               return;
-       }
-       if (rtc_fd >= 0)
-               return;
+       /*
+        * Use a real-time signal when possible. This gives us roughly a 99% signal
+        * delivery rate in all cases. On the other hand, using a regular signal
+        * tends to result in awful delivery rates when the application is heavily
+        * loaded.
+        *
+        * TODO: On Mac, we should explore using the Mach thread suspend/resume
+        * functions and doing the stack walk from the sampling thread. This would
+        * get us a 100% sampling rate. However, this may interfere with the GC's
+        * STW logic. Could perhaps be solved by taking the suspend lock.
+        */
+#if defined (USE_POSIX_BACKEND) && defined (SIGRTMIN)
+       /* Just take the first real-time signal we can get. */
+       profiler_signal = mono_threads_posix_signal_search_alternative (-1);
+#else
+       profiler_signal = SIGPROF;
 #endif
 
-       itval.it_interval.tv_usec = (1000000 / mono_profiler_get_sampling_rate ()) - 1;
-       itval.it_interval.tv_sec = 0;
-       itval.it_value = itval.it_interval;
-       if (inited)
-               return;
-       inited = 1;
-       profiling_signal_in_use = get_itimer_signal ();
-       add_signal_handler (profiling_signal_in_use, sigprof_signal_handler, SA_RESTART);
-       setitimer (get_itimer_mode (), &itval, NULL);
-#endif
+       add_signal_handler (profiler_signal, profiler_signal_handler, SA_RESTART);
+
+       InterlockedWrite (&sampling_thread_running, 1);
+       mono_native_thread_create (&sampling_thread, sampling_thread_func, NULL);
+}
+
+#else
+
+void
+mono_runtime_shutdown_stat_profiler (void)
+{
+}
+
+void
+mono_runtime_setup_stat_profiler (void)
+{
 }
 
+#endif
+
 #if !defined(PLATFORM_MACOSX)
 pid_t
 mono_runtime_syscall_fork ()
index 39caeb54d65b68c918bdc04f991d2a53c4163e13..8200b101913cc05c9e612c69a2077737a5a5ac85 100644 (file)
@@ -36,8 +36,8 @@ static sigset_t suspend_ack_signal_mask;
 //Can't avoid the circular dep on this. Will be gone pretty soon
 extern int mono_gc_get_suspend_signal (void);
 
-static int
-signal_search_alternative (int min_signal)
+int
+mono_threads_posix_signal_search_alternative (int min_signal)
 {
 #if !defined (SIGRTMIN)
        g_error ("signal search only works with RTMIN");
@@ -88,7 +88,7 @@ suspend_signal_get (void)
 #else
        static int suspend_signum = -1;
        if (suspend_signum == -1)
-               suspend_signum = signal_search_alternative (-1);
+               suspend_signum = mono_threads_posix_signal_search_alternative (-1);
        return suspend_signum;
 #endif /* SIGRTMIN */
 }
@@ -107,7 +107,7 @@ restart_signal_get (void)
 #else
        static int resume_signum = -1;
        if (resume_signum == -1)
-               resume_signum = signal_search_alternative (suspend_signal_get () + 1);
+               resume_signum = mono_threads_posix_signal_search_alternative (suspend_signal_get () + 1);
        return resume_signum;
 #endif /* SIGRTMIN */
 }
@@ -127,7 +127,7 @@ abort_signal_get (void)
 #else
        static int abort_signum = -1;
        if (abort_signum == -1)
-               abort_signum = signal_search_alternative (restart_signal_get () + 1);
+               abort_signum = mono_threads_posix_signal_search_alternative (restart_signal_get () + 1);
        return abort_signum;
 #endif /* SIGRTMIN */
 }
index 2bdcf8e789ac29e055359ce26c8a4dfb5c406bba..59a38c1ceb21fcf922d109ffdff71091415978a5 100644 (file)
@@ -14,6 +14,9 @@ typedef enum {
        MONO_THREADS_POSIX_INIT_SIGNALS_ABORT,
 } MonoThreadPosixInitSignals;
 
+int
+mono_threads_posix_signal_search_alternative (int min_signal);
+
 void
 mono_threads_posix_init_signals (MonoThreadPosixInitSignals signals);
 
@@ -28,4 +31,4 @@ mono_threads_posix_get_abort_signal (void);
 
 #endif /* defined(USE_POSIX_BACKEND) || defined(USE_POSIX_SYSCALL_ABORT) */
 
-#endif /* __MONO_THREADS_POSIX_SIGNALS_H__ */
\ No newline at end of file
+#endif /* __MONO_THREADS_POSIX_SIGNALS_H__ */
index 0b142ee1c3f0024fedbce186aa41c65df83e484e..b6e70329c96dc4c6f5660c3d071c3b37f8cde78a 100644 (file)
@@ -1519,27 +1519,3 @@ mono_thread_info_describe_interrupt_token (MonoThreadInfo *info, GString *text)
        else
                g_string_append_printf (text, "waiting");
 }
-
-/* info must be self or be held in a hazard pointer. */
-gboolean
-mono_threads_add_async_job (MonoThreadInfo *info, MonoAsyncJob job)
-{
-       MonoAsyncJob old_job;
-       do {
-               old_job = (MonoAsyncJob) info->service_requests;
-               if (old_job & job)
-                       return FALSE;
-       } while (InterlockedCompareExchange (&info->service_requests, old_job | job, old_job) != old_job);
-       return TRUE;
-}
-
-MonoAsyncJob
-mono_threads_consume_async_jobs (void)
-{
-       MonoThreadInfo *info = (MonoThreadInfo*)mono_native_tls_get_value (thread_info_key);
-
-       if (!info)
-               return (MonoAsyncJob) 0;
-
-       return (MonoAsyncJob) InterlockedExchange (&info->service_requests, 0);
-}
index 1019dda1884e4c078e4f9de583e74a339c223bfc..7e7fc371c6a4c9f5df4c0526e5ec94340d8c765a 100644 (file)
@@ -164,13 +164,6 @@ enum {
        ASYNC_SUSPEND_STATE_INDEX = 1,
 };
 
-/*
- * This enum tells which async thread service corresponds to which bit.
- */
-typedef enum {
-       MONO_SERVICE_REQUEST_SAMPLE = 1,
-} MonoAsyncJob;
-
 typedef struct _MonoThreadInfoInterruptToken MonoThreadInfoInterruptToken;
 
 typedef struct {
@@ -241,12 +234,6 @@ typedef struct {
        /* Set when the thread is started, or in _wapi_thread_duplicate () */
        HANDLE handle;
 
-       /* Asynchronous service request. This flag is meant to be consumed by the multiplexing signal handlers to discover what sort of work they need to do.
-        * Use the mono_threads_add_async_job and mono_threads_consume_async_jobs APIs to modify this flag.
-        * In the future the signaling should be part of the API, but for now, it's only for massaging the bits.
-        */
-       volatile gint32 service_requests;
-
        void *jit_data;
 
        MonoThreadInfoInterruptToken *interrupt_token;
@@ -466,17 +453,6 @@ mono_threads_get_max_stack_size (void);
 HANDLE
 mono_threads_open_thread_handle (HANDLE handle, MonoNativeThreadId tid);
 
-/*
-This is the async job submission/consumption API.
-XXX: This is a PROVISIONAL API only meant to be used by the statistical profiler.
-If you want to use/extend it anywhere else, understand that you'll have to do some API design work to better fit this puppy.
-*/
-gboolean
-mono_threads_add_async_job (THREAD_INFO_TYPE *info, MonoAsyncJob job);
-
-MonoAsyncJob
-mono_threads_consume_async_jobs (void);
-
 MONO_API void
 mono_threads_attach_tools_thread (void);