// Copyright 2018 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(joshuseaton): Once std lands in Zircon, simplify everything below. #include #include #include #include #include #include #include namespace async { namespace { // Determinisitically updates |m| to point to a pseudo-random number. void Randomize(uint32_t* m) { rand32_t r = { .n = *m }; *m = rand32(&r); } // Generates a random seed if the environment variable TEST_LOOP_RANDOM_SEED // is unset; else returns the value of the latter. uint32_t GetRandomSeed() {; uint32_t random_seed; const char* preset = getenv("TEST_LOOP_RANDOM_SEED"); if (preset) { size_t preset_length = strlen(preset); char* endptr = nullptr; long unsigned preset_seed = strtoul(preset, &endptr, 10); ZX_ASSERT_MSG(preset_seed > 0 && endptr == preset + preset_length, "ERROR: \"%s\" does not give a valid random seed\n", preset); random_seed = static_cast(preset_seed); } else { zx_cprng_draw(&random_seed, sizeof(uint32_t)); } printf("\nTEST_LOOP_RANDOM_SEED=\"%u\"\n", random_seed); return random_seed; } } // namespace class TestLoop::TestLoopTimeKeeper : public TimeKeeper { public: TestLoopTimeKeeper() = default; ~TestLoopTimeKeeper() = default; zx::time Now() const override { return current_time_; } void RegisterTimer(zx::time deadline, TimerDispatcher* dispatcher) override { if (deadline <= current_time_) { dispatcher->FireTimer(); return; } for (auto& entry : table_) { if (entry.dispatcher == dispatcher) { entry.deadlines.push_back(deadline); return; } } DeadlinesByDispatcher entry{}; entry.dispatcher = dispatcher; entry.deadlines.push_back(deadline); table_.push_back(fbl::move(entry)); } void CancelTimers(TimerDispatcher* dispatcher) override { size_t ind = 0; for (; ind < table_.size(); ++ind){ if (table_[ind].dispatcher == dispatcher){ table_.erase(ind); return; } } } void AdvanceTimeTo(zx::time time) { if (time < current_time_) { return; } current_time_ = time; for (auto& entry : table_) { if (entry.deadlines.size() == 0) { continue; } zx::time min_deadline = *fbl::min_element(entry.deadlines.begin(), entry.deadlines.end()); if (min_deadline > time){ continue; } entry.dispatcher->FireTimer(); size_t end = entry.deadlines.size() - 1; for (size_t i = 0; i <= entry.deadlines.size(); ++i){ zx::time back = entry.deadlines[end]; entry.deadlines.pop_back(); if (back > time) { entry.deadlines.insert(0, back); continue; } --end; } } } private: struct DeadlinesByDispatcher { TimerDispatcher* dispatcher; fbl::Vector deadlines; }; zx::time current_time_; fbl::Vector table_; }; class TestLoop::TestLoopInterface : public LoopInterface { public: TestLoopInterface(TestLoop* loop, TestLoopDispatcher* dispatcher) : loop_(loop), dispatcher_(dispatcher) {} ~TestLoopInterface() override { auto& dispatchers = loop_->dispatchers_; for (size_t index = 0; index < dispatchers.size(); ++index) { if (dispatchers[index].get() == dispatcher_) { dispatchers.erase(index); break; } } dispatcher_ = nullptr; } async_dispatcher_t* dispatcher() override { return dispatcher_; } private: TestLoop* const loop_; TestLoopDispatcher* dispatcher_; }; TestLoop::TestLoop() : time_keeper_(new TestLoopTimeKeeper()), initial_state_(GetRandomSeed()), state_(initial_state_) { dispatchers_.push_back(fbl::make_unique(time_keeper_.get())); async_set_default_dispatcher(dispatchers_[0].get()); } TestLoop::~TestLoop() { async_set_default_dispatcher(nullptr); } async_dispatcher_t* TestLoop::dispatcher() { return dispatchers_[0].get(); } fbl::unique_ptr TestLoop::StartNewLoop() { dispatchers_.push_back(fbl::make_unique(time_keeper_.get())); auto* new_dispatcher = dispatchers_[dispatchers_.size() - 1].get(); return fbl::make_unique(this, new_dispatcher); } zx::time TestLoop::Now() const { return time_keeper_->Now(); } void TestLoop::Quit() { has_quit_ = true; } void TestLoop::AdvanceTimeByEpsilon() { time_keeper_->AdvanceTimeTo(Now() + zx::duration(1)); } bool TestLoop::RunUntil(zx::time deadline) { ZX_ASSERT(!is_running_); is_running_ = true; bool did_work = false; while (!has_quit_) { if (!HasPendingWork()) { zx::time next_due_time = GetNextTaskDueTime(); if (next_due_time > deadline) { time_keeper_->AdvanceTimeTo(deadline); break; } time_keeper_->AdvanceTimeTo(next_due_time); } Randomize(&state_); size_t current_index = state_ % dispatchers_.size(); auto& current_dispatcher = dispatchers_[current_index]; async_set_default_dispatcher(current_dispatcher.get()); did_work |= current_dispatcher->DispatchNextDueMessage(); async_set_default_dispatcher(dispatchers_[0].get()); } is_running_ = false; has_quit_ = false; return did_work; } bool TestLoop::RunFor(zx::duration duration) { return RunUntil(Now() + duration); } bool TestLoop::RunUntilIdle() { return RunUntil(Now()); } bool TestLoop::HasPendingWork() { for (auto& dispatcher : dispatchers_) { if (dispatcher->HasPendingWork()) { return true; } } return false; } zx::time TestLoop::GetNextTaskDueTime() const { zx::time next_due_time = zx::time::infinite(); for (auto& dispatcher : dispatchers_) { next_due_time = fbl::min(next_due_time, dispatcher->GetNextTaskDueTime()); } return next_due_time; } } // namespace async