diff --git a/CMakeLists.txt b/CMakeLists.txt index 17bb7605ea2..17621d60694 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2108,6 +2108,7 @@ if(BUILD_TESTS) pipe_wakeup mmap_adjacent mmap_bits + startup starvation_multithreaded starvation_singlethreaded ) diff --git a/src/Scheduler.cc b/src/Scheduler.cc index 2601fc41bca..97a9222a1fe 100644 --- a/src/Scheduler.cc +++ b/src/Scheduler.cc @@ -39,6 +39,12 @@ static double low_priority_probability = 0.1; // many tests are basically main-thread-only static double main_thread_low_priority_probability = 0.3; static double very_short_timeslice_probability = 0.1; +// For low priority tasks, assign some probability of being treated +// as medium priority until their first yield. +// This lets a low priority task run until it unblocks the execution of +// a high-priority task and then never run again during a +// high-priority-only interval. See the `startup` test. +static double postpone_low_priority_until_after_yield = 0.2; static Ticks very_short_timeslice_max_duration = 100; static double short_timeslice_probability = 0.1; static Ticks short_timeslice_max_duration = 10000; @@ -70,8 +76,9 @@ static double priorities_refresh_max_interval = 20; * running time. Then to maximise the probability of triggering the test * failure, we start high-priority-only intervals as often as possible, * i.e. one for D' seconds starting every 5xD' seconds. - * The start time of the first interval is chosen uniformly randomly to be - * between 0 and 4xD'. + * The start time of the first interval is chosen to be between 0 and 4xD'. + * To make sure we capture startup effects, we choose 0 with probability 0.25 + * and uniformly between 0 and 4xD' otherwise. * Then, if we guessed D' and the low-priority thread correctly, the * probability of triggering the test failure is 1 if T >= 4xD', T/4xD' * otherwise, i.e. >= T/8xD. (Higher values of D' than optimal can also trigger @@ -83,6 +90,7 @@ static int high_priority_only_duration_steps = 12; static double high_priority_only_duration_step_factor = 2; // Allow this much of overall runtime to be in the "high priority only" interval static double high_priority_only_fraction = 0.2; +static double start_high_priority_only_immediately_probability = 0.25; Scheduler::Scheduler(RecordSession& session) : reschedule_count(0), @@ -179,10 +187,20 @@ void Scheduler::set_num_cores(int cores) { static double random_frac() { return double(random() % INT32_MAX) / INT32_MAX; } +static const int CHAOS_MODE_HIGH_PRIORITY = 0; +static const int CHAOS_MODE_MEDIUM_PRIORITY_UNTIL_NEXT_YIELD = 1; +static const int CHAOS_MODE_LOW_PRIORITY = 2; + int Scheduler::choose_random_priority(RecordTask* t) { double prob = t->tgid() == t->tid ? main_thread_low_priority_probability : low_priority_probability; - return random_frac() < prob; + if (random_frac() < prob) { + if (random_frac() < postpone_low_priority_until_after_yield) { + return CHAOS_MODE_MEDIUM_PRIORITY_UNTIL_NEXT_YIELD; + } + return CHAOS_MODE_LOW_PRIORITY; + } + return CHAOS_MODE_HIGH_PRIORITY; } static bool treat_syscall_as_nonblocking(int syscallno, SupportedArch arch) { @@ -484,20 +502,30 @@ void Scheduler::maybe_reset_priorities(double now) { } } +void Scheduler::notify_descheduled(RecordTask* t) { + if (!enable_chaos || t->priority != CHAOS_MODE_MEDIUM_PRIORITY_UNTIL_NEXT_YIELD) { + return; + } + LOGM(debug) << "Lowering priority of " << t->tid << " after descheduling"; + update_task_priority_internal(t, CHAOS_MODE_LOW_PRIORITY); +} + void Scheduler::maybe_reset_high_priority_only_intervals(double now) { if (!enable_chaos || high_priority_only_intervals_refresh_time > now) { return; } - int duration_step = random() % high_priority_only_duration_steps; + int duration_step = 11; high_priority_only_intervals_duration = min_high_priority_only_duration * pow(high_priority_only_duration_step_factor, duration_step); high_priority_only_intervals_period = high_priority_only_intervals_duration / high_priority_only_fraction; - high_priority_only_intervals_start = - now + - random_frac() * (high_priority_only_intervals_period - - high_priority_only_intervals_duration); + high_priority_only_intervals_start = now; + if (random_frac() >= start_high_priority_only_immediately_probability) { + high_priority_only_intervals_start += + random_frac() * (high_priority_only_intervals_period - + high_priority_only_intervals_duration); + } high_priority_only_intervals_refresh_time = now + min_high_priority_only_duration * @@ -516,7 +544,7 @@ bool Scheduler::in_high_priority_only_interval(double now) { } bool Scheduler::treat_as_high_priority(RecordTask* t) { - return t->priority == 0; + return t->priority < CHAOS_MODE_LOW_PRIORITY; } void Scheduler::validate_scheduled_task() { @@ -934,11 +962,14 @@ Scheduler::Rescheduled Scheduler::reschedule(Switchable switchable) { must_run_task = next; } - if (current_ && current_ != next && is_logging_enabled(LOG_debug, __FILE__)) { - LOGM(debug) << "Switching from " << current_->tid << "(" << current_->name() - << ") to " << next->tid << "(" << next->name() << ") (priority " - << current_->priority << " to " << next->priority << ") at " - << current_->trace_writer().time(); + if (current_ && current_ != next) { + notify_descheduled(current_); + if (is_logging_enabled(LOG_debug, __FILE__)) { + LOGM(debug) << "Switching from " << current_->tid << "(" << current_->name() + << ") to " << next->tid << "(" << next->name() << ") (priority " + << current_->priority << " to " << next->priority << ") at " + << current_->trace_writer().time(); + } } maybe_reset_high_priority_only_intervals(now); diff --git a/src/Scheduler.h b/src/Scheduler.h index 94e8b16e0b7..b31986dd3f4 100644 --- a/src/Scheduler.h +++ b/src/Scheduler.h @@ -226,6 +226,7 @@ class Scheduler { void maybe_pop_round_robin_task(RecordTask* t); void setup_new_timeslice(); void maybe_reset_priorities(double now); + void notify_descheduled(RecordTask* t); int choose_random_priority(RecordTask* t); void update_task_priority_internal(RecordTask* t, int value); void maybe_reset_high_priority_only_intervals(double now); diff --git a/src/chaos-test/chaos-test.sh b/src/chaos-test/chaos-test.sh index 065bf513a83..51b86682cd5 100755 --- a/src/chaos-test/chaos-test.sh +++ b/src/chaos-test/chaos-test.sh @@ -15,6 +15,7 @@ cd `dirname $0` ./harness.py $1 200 200 pipe_wakeup ./harness.py $1 500 500 mmap_bits 7 ./harness.py $1 500 500 mmap_adjacent 10 +./harness.py $1 200 200 startup ./harness.py $1 100 200 starvation_singlethreaded 200000 202000 2000 1000000 ./harness.py $1 100 200 starvation_singlethreaded 2000000 2400000 500000 5000000 ./harness.py $1 400 800 starvation_multithreaded 200000 202000 2000 1000000 diff --git a/src/chaos-test/startup.c b/src/chaos-test/startup.c new file mode 100644 index 00000000000..19d97144439 --- /dev/null +++ b/src/chaos-test/startup.c @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ + +#include "chaosutil.h" + +static int flag; +static pthread_mutex_t mutex; + +static void* run_thread(__attribute__((unused)) void* p) { + struct timespec ts = { 1, 0 }; + nanosleep(&ts, NULL); + pthread_mutex_lock(&mutex); + flag = 1; + pthread_mutex_unlock(&mutex); + return NULL; +} + +int main(void) { + int i; + pthread_t thread; + + pthread_mutex_init(&mutex, NULL); + pthread_create(&thread, NULL, run_thread, NULL); + pthread_mutex_lock(&mutex); + if (flag > 0) { + caught_test_failure("flag set"); + } + pthread_mutex_unlock(&mutex); + pthread_join(thread, NULL); + + atomic_puts("EXIT-SUCCESS"); + return 0; +}