diff --git a/clients/drcachesim/scheduler/scheduler.cpp b/clients/drcachesim/scheduler/scheduler.cpp index ffbb3004309..2ccd4b82a03 100644 --- a/clients/drcachesim/scheduler/scheduler.cpp +++ b/clients/drcachesim/scheduler/scheduler.cpp @@ -3274,14 +3274,11 @@ scheduler_tmpl_t::pick_next_input(output_ordinal_t outpu } else { auto lock = std::unique_lock(*inputs_[prev_index].lock); - // If we can't go back to the current input, we're EOF or idle. - // TODO i#6959: We should go the EOF/idle route if - // inputs_[prev_index].unscheduled as otherwise we're ignoring its - // unscheduled transition: although if there are no other threads - // at all (not just an empty queue) this turns into the - // eof_or_idle() all-unscheduled scenario. Once we have some kind - // of early exit option we'll add the unscheduled check here. - if (inputs_[prev_index].at_eof) { + // If we can't go back to the current input because it's EOF + // or unscheduled indefinitely (we already checked blocked_time + // above: it's 0 here), this output is either idle or EOF. + if (inputs_[prev_index].at_eof || + inputs_[prev_index].unscheduled) { lock.unlock(); sched_type_t::stream_status_t status = eof_or_idle(output, prev_index); diff --git a/clients/drcachesim/tests/scheduler_unit_tests.cpp b/clients/drcachesim/tests/scheduler_unit_tests.cpp index b151c1ff9b9..f608cb48585 100644 --- a/clients/drcachesim/tests/scheduler_unit_tests.cpp +++ b/clients/drcachesim/tests/scheduler_unit_tests.cpp @@ -5222,6 +5222,62 @@ test_unscheduled_small_timeout() } } +static void +test_unscheduled_no_alternative() +{ + // Test that an unscheduled 0-timeout input is not incorrectly executed if + // there is nothing else to run (i#6959). + std::cerr << "\n----------------\nTesting unscheduled no alternative (i#6959)\n"; + static constexpr int NUM_OUTPUTS = 1; + static constexpr uint64_t REBALANCE_PERIOD_US = 50; + static constexpr memref_tid_t TID_A = 100; + std::vector refs_A = { + make_thread(TID_A), + make_pid(1), + make_version(TRACE_ENTRY_VERSION), + make_timestamp(1001), + make_marker(TRACE_MARKER_TYPE_CPU_ID, 0), + make_instr(/*pc=*/101), + make_timestamp(1002), + make_marker(TRACE_MARKER_TYPE_CPU_ID, 0), + make_marker(TRACE_MARKER_TYPE_SYSCALL, 999), + make_marker(TRACE_MARKER_TYPE_MAYBE_BLOCKING_SYSCALL, 0), + // No timeout means infinite (until the fallback kicks in). + make_marker(TRACE_MARKER_TYPE_SYSCALL_UNSCHEDULE, 0), + make_timestamp(2002), + make_instr(/*pc=*/102), + make_exit(TID_A), + }; + { + std::vector readers; + readers.emplace_back(std::unique_ptr(new mock_reader_t(refs_A)), + std::unique_ptr(new mock_reader_t()), TID_A); + static const char *const CORE0_SCHED_STRING = + "...A......__________________________________________________A."; + + std::vector sched_inputs; + sched_inputs.emplace_back(std::move(readers)); + scheduler_t::scheduler_options_t sched_ops(scheduler_t::MAP_TO_ANY_OUTPUT, + scheduler_t::DEPENDENCY_TIMESTAMPS, + scheduler_t::SCHEDULER_DEFAULTS, + /*verbosity=*/3); + // We use our mock's time==instruction count for a deterministic result. + sched_ops.quantum_unit = scheduler_t::QUANTUM_TIME; + sched_ops.time_units_per_us = 1.; + sched_ops.rebalance_period_us = REBALANCE_PERIOD_US; + scheduler_t scheduler; + if (scheduler.init(sched_inputs, NUM_OUTPUTS, std::move(sched_ops)) != + scheduler_t::STATUS_SUCCESS) + assert(false); + std::vector sched_as_string = + run_lockstep_simulation(scheduler, NUM_OUTPUTS, TID_A, /*send_time=*/true); + for (int i = 0; i < NUM_OUTPUTS; i++) { + std::cerr << "cpu #" << i << " schedule: " << sched_as_string[i] << "\n"; + } + assert(sched_as_string[0] == CORE0_SCHED_STRING); + } +} + static void test_unscheduled() { @@ -5230,6 +5286,7 @@ test_unscheduled() test_unscheduled_initially(); test_unscheduled_initially_roi(); test_unscheduled_small_timeout(); + test_unscheduled_no_alternative(); } static void