diff --git a/lib/master.pike.in b/lib/master.pike.in index 243ee40807ebe31607272d0b909532b095edb53f..61fec6ea3dc918dd72aafb6011c2f124fb03c759 100644 --- a/lib/master.pike.in +++ b/lib/master.pike.in @@ -4656,6 +4656,14 @@ array get_backtrace (object|array err) return bt; } +void thread_quanta_exceeded(object thread, int ns) +{ + werror("Thread quanta exceeded for %O (%d ns):\n" + "%s\n", + thread, ns, + describe_backtrace(thread->backtrace())); +} + #ifdef ENCODE_DEBUG # define ENC_MSG(X...) do werror (X); while (0) diff --git a/src/threads.c b/src/threads.c index 415af8ac847272a2e5d97c6ff93bfc1c130080a6..d3554c8e034dc7b35a4a5929781c1197678ee0bf 100644 --- a/src/threads.c +++ b/src/threads.c @@ -59,6 +59,8 @@ */ static struct Pike_interpreter_struct static_pike_interpreter; +static cpu_time_t thread_quanta = 0; + PMOD_EXPORT struct Pike_interpreter_struct * #if defined(__GNUC__) && __GNUC__ >= 3 __restrict @@ -572,6 +574,7 @@ PMOD_EXPORT void pike_init_thread_state (struct thread_state *ts) ts->id = th_self(); ts->status = THREAD_RUNNING; ts->swapped = 0; + ts->interval_start = get_real_time(); #ifdef PIKE_DEBUG ts->debug_flags = 0; thread_swaps++; @@ -711,6 +714,34 @@ PMOD_EXPORT void pike_debug_check_thread (DLOC_DECL) #endif /* PIKE_DEBUG */ +/*! @class MasterObject + */ + +/*! @decl void thread_quanta_exceeded(Thread.Thread thread, int ns) + *! + *! Function called when a thread has exceeded the thread quanta. + *! + *! @param thread + *! Thread that exceeded the thread quanta. + *! + *! @param ns + *! Number of nanoseconds that the thread executed before allowing + *! other threads to run. + *! + *! The default master prints a diagnostic and the thread backtrace + *! to @[Stdio.stderr]. + *! + *! @note + *! This function runs in a signal handler context, and should thus + *! avoid handling of mutexes, etc. + *! + *! @seealso + *! @[get_thread_quanta()], @[set_thread_quanta()] + */ + +/*! @endclass + */ + PMOD_EXPORT void pike_threads_allow (struct thread_state *ts COMMA_DLOC_DECL) { #ifdef DO_PIKE_CLEANUP @@ -718,6 +749,23 @@ PMOD_EXPORT void pike_threads_allow (struct thread_state *ts COMMA_DLOC_DECL) if (!ts) return; #endif + if (UNLIKELY(thread_quanta > 0)) { + cpu_time_t now = get_real_time(); + + if (UNLIKELY((now - ts->interval_start) > thread_quanta) && + LIKELY(ts->thread_obj)) { + ref_push_object(ts->thread_obj); + push_int64(now - ts->interval_start); + ts->interval_start = now; +#ifndef LONG_CPU_TIME + push_int(1000000000 / CPU_TIME_TICKS); + o_multiply(); +#endif + SAFE_APPLY_MASTER("thread_quanta_exceeded", 2); + pop_stack(); + } + } + #ifdef PIKE_DEBUG pike_debug_check_thread (DLOC_ARGS_OPT); if (Pike_in_gc > 50 && Pike_in_gc < 300) @@ -758,6 +806,10 @@ PMOD_EXPORT void pike_threads_disallow (struct thread_state *ts COMMA_DLOC_DECL) pike_swap_in_thread (ts COMMA_DLOC_ARGS_OPT); } + if (UNLIKELY(thread_quanta)) { + ts->interval_start = get_real_time(); + } + #ifdef PIKE_DEBUG ts->debug_flags &= ~THREAD_DEBUG_LOOSE; pike_debug_check_thread (DLOC_ARGS_OPT); @@ -772,6 +824,23 @@ PMOD_EXPORT void pike_threads_allow_ext (struct thread_state *ts if (!ts) return; #endif + if (UNLIKELY(thread_quanta > 0)) { + cpu_time_t now = get_real_time(); + + if (UNLIKELY((now - ts->interval_start) > thread_quanta) && + LIKELY(ts->thread_obj)) { + ref_push_object(ts->thread_obj); + push_int64(now - ts->interval_start); + ts->interval_start = now; +#ifndef LONG_CPU_TIME + push_int(1000000000 / CPU_TIME_TICKS); + o_multiply(); +#endif + SAFE_APPLY_MASTER("thread_quanta_exceeded", 2); + pop_stack(); + } + } + #ifdef PIKE_DEBUG pike_debug_check_thread (DLOC_ARGS_OPT); if (Pike_in_gc > 50 && Pike_in_gc < 300) @@ -821,6 +890,10 @@ PMOD_EXPORT void pike_threads_disallow_ext (struct thread_state *ts pike_swap_in_thread (ts COMMA_DLOC_ARGS_OPT); } + if (UNLIKELY(thread_quanta)) { + ts->interval_start = get_real_time(); + } + #ifdef PIKE_DEBUG ts->debug_flags &= ~THREAD_DEBUG_LOOSE; pike_debug_check_thread (DLOC_ARGS_OPT); @@ -2025,6 +2098,76 @@ PMOD_EXPORT void f_this_thread(INT32 args) } } +/*! @decl int(0..) get_thread_quanta() + *! + *! @returns + *! Returns the current thread quanta in nanoseconds. + *! + *! @seealso + *! @[set_thread_quanta()], @[gethrtime()] + */ +static void f_get_thread_quanta(INT32 args) +{ + pop_n_elems(args); + push_int64(thread_quanta); +#ifndef LONG_CPU_TIME_T + /* Convert from ticks. */ + push_int(1000000000 / CPU_TIME_TICKS); + o_multiply(); +#endif +} + +/*! @decl int(0..) set_thread_quanta(int(0..) ns) + *! + *! Set the thread quanta. + *! + *! @param ns + *! New thread quanta in nanoseconds. + *! A value of zero (default) disables the thread quanta checks. + *! + *! When enabled @[MasterObject.thread_quanta_exceeded()] will + *! be called when a thread has spent more time than the quanta + *! without allowing another thread to run. + *! + *! @note + *! Setting a non-zero value that is too small to allow for + *! @[MasterObject.thread_quanta_exceeded()] to run is NOT a + *! good idea. + *! + *! @returns + *! Returns the previous thread quanta in nanoseconds. + *! + *! @seealso + *! @[set_thread_quanta()], @[gethrtime()] + */ +static void f_set_thread_quanta(INT32 args) +{ + LONGEST ns = 0; + +#ifndef LONG_CPU_TIME_T + /* Convert to ticks. */ + push_int(1000000000 / CPU_TIME_TICKS); + o_divide(); +#endif + get_all_args("set_thread_quanta", args, "%l", &ns); + pop_n_elems(args); + + push_int64(thread_quanta); +#ifndef LONG_CPU_TIME_T + /* Convert from ticks. */ + push_int(1000000000 / CPU_TIME_TICKS); + o_multiply(); +#endif + + if (ns <= 0) ns = 0; + + thread_quanta = ns; + + if (Pike_interpreter.thread_state) { + Pike_interpreter.thread_state->interval_start = get_real_time(); + } +} + #define THIS_MUTEX ((struct mutex_storage *)(CURRENT_STORAGE)) @@ -3393,6 +3536,14 @@ void th_init(void) tFunc(tNone,tArr(tObjIs_THREAD_ID)), OPT_EXTERNAL_DEPEND); + ADD_EFUN("get_thread_quanta", f_get_thread_quanta, + tFunc(tNone, tInt), + OPT_EXTERNAL_DEPEND); + + ADD_EFUN("set_thread_quanta", f_set_thread_quanta, + tFunc(tInt, tInt), + OPT_EXTERNAL_DEPEND); + /* Some constants... */ add_integer_constant("THREAD_NOT_STARTED", THREAD_NOT_STARTED, 0); add_integer_constant("THREAD_RUNNING", THREAD_RUNNING, 0); diff --git a/src/threads.h b/src/threads.h index 4e9f8b615a5ae5af1cdb6bb77ced1bc36e785d2b..70be968bd58c8d49ec1f74d588ca6f619a9e9003 100644 --- a/src/threads.h +++ b/src/threads.h @@ -44,6 +44,7 @@ struct thread_state { struct svalue result; COND_T status_change; THREAD_T id; + cpu_time_t interval_start; /* real_time at THREADS_DISALLOW(). */ #ifdef CPU_TIME_MIGHT_BE_THREAD_LOCAL cpu_time_t auto_gc_time; #endif