| /* | |
| * Copyright (C) 2014 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "task_processor.h" | |
| #include "base/time_utils.h" | |
| #include "common_runtime_test.h" | |
| #include "thread-current-inl.h" | |
| #include "thread_pool.h" | |
| namespace art HIDDEN { | |
| namespace gc { | |
| class TaskProcessorTest : public CommonRuntimeTest { | |
| public: | |
| }; | |
| class RecursiveTask : public HeapTask { | |
| public: | |
| RecursiveTask(TaskProcessor* task_processor, Atomic<size_t>* counter, size_t max_recursion) | |
| : HeapTask(NanoTime() + MsToNs(10)), task_processor_(task_processor), counter_(counter), | |
| max_recursion_(max_recursion) { | |
| } | |
| void Run(Thread* self) override { | |
| if (max_recursion_ > 0) { | |
| task_processor_->AddTask(self, | |
| new RecursiveTask(task_processor_, counter_, max_recursion_ - 1)); | |
| counter_->fetch_add(1U, std::memory_order_seq_cst); | |
| } | |
| } | |
| private: | |
| TaskProcessor* const task_processor_; | |
| Atomic<size_t>* const counter_; | |
| const size_t max_recursion_; | |
| }; | |
| class WorkUntilDoneTask : public SelfDeletingTask { | |
| public: | |
| WorkUntilDoneTask(TaskProcessor* task_processor, Atomic<bool>* done_running) | |
| : task_processor_(task_processor), done_running_(done_running) { | |
| } | |
| void Run(Thread* self) override { | |
| task_processor_->RunAllTasks(self); | |
| done_running_->store(true, std::memory_order_seq_cst); | |
| } | |
| private: | |
| TaskProcessor* const task_processor_; | |
| Atomic<bool>* done_running_; | |
| }; | |
| TEST_F(TaskProcessorTest, Interrupt) { | |
| std::unique_ptr<ThreadPool> thread_pool(ThreadPool::Create("task processor test", 1U)); | |
| Thread* const self = Thread::Current(); | |
| TaskProcessor task_processor; | |
| static constexpr size_t kRecursion = 10; | |
| Atomic<bool> done_running(false); | |
| Atomic<size_t> counter(0); | |
| task_processor.AddTask(self, new RecursiveTask(&task_processor, &counter, kRecursion)); | |
| task_processor.Start(self); | |
| // Add a task which will wait until interrupted to the thread pool. | |
| thread_pool->AddTask(self, new WorkUntilDoneTask(&task_processor, &done_running)); | |
| thread_pool->StartWorkers(self); | |
| ASSERT_FALSE(done_running); | |
| // Wait until all the tasks are done, but since we didn't interrupt, done_running should be 0. | |
| while (counter.load(std::memory_order_seq_cst) != kRecursion) { | |
| usleep(10); | |
| } | |
| ASSERT_FALSE(done_running); | |
| task_processor.Stop(self); | |
| thread_pool->Wait(self, true, false); | |
| // After the interrupt and wait, the WorkUntilInterruptedTasktask should have terminated and | |
| // set done_running_ to true. | |
| ASSERT_TRUE(done_running.load(std::memory_order_seq_cst)); | |
| // Test that we finish remaining tasks before returning from RunTasksUntilInterrupted. | |
| counter.store(0, std::memory_order_seq_cst); | |
| done_running.store(false, std::memory_order_seq_cst); | |
| // Self interrupt before any of the other tasks run, but since we added them we should keep on | |
| // working until all the tasks are completed. | |
| task_processor.Stop(self); | |
| task_processor.AddTask(self, new RecursiveTask(&task_processor, &counter, kRecursion)); | |
| thread_pool->AddTask(self, new WorkUntilDoneTask(&task_processor, &done_running)); | |
| thread_pool->StartWorkers(self); | |
| thread_pool->Wait(self, true, false); | |
| ASSERT_TRUE(done_running.load(std::memory_order_seq_cst)); | |
| ASSERT_EQ(counter.load(std::memory_order_seq_cst), kRecursion); | |
| } | |
| class TestOrderTask : public HeapTask { | |
| public: | |
| TestOrderTask(uint64_t expected_time, size_t expected_counter, size_t* counter) | |
| : HeapTask(expected_time), expected_counter_(expected_counter), counter_(counter) { | |
| } | |
| void Run([[maybe_unused]] Thread* thread) override { | |
| ASSERT_EQ(*counter_, expected_counter_); | |
| ++*counter_; | |
| } | |
| private: | |
| const size_t expected_counter_; | |
| size_t* const counter_; | |
| }; | |
| TEST_F(TaskProcessorTest, Ordering) { | |
| static const size_t kNumTasks = 25; | |
| const uint64_t current_time = NanoTime(); | |
| Thread* const self = Thread::Current(); | |
| TaskProcessor task_processor; | |
| task_processor.Stop(self); | |
| size_t counter = 0; | |
| std::vector<std::pair<uint64_t, size_t>> orderings; | |
| for (size_t i = 0; i < kNumTasks; ++i) { | |
| orderings.push_back(std::make_pair(current_time + MsToNs(10U * i), i)); | |
| } | |
| for (size_t i = 0; i < kNumTasks; ++i) { | |
| std::swap(orderings[i], orderings[(i * 87654231 + 12345) % orderings.size()]); | |
| } | |
| for (const auto& pair : orderings) { | |
| auto* task = new TestOrderTask(pair.first, pair.second, &counter); | |
| task_processor.AddTask(self, task); | |
| } | |
| std::unique_ptr<ThreadPool> thread_pool(ThreadPool::Create("task processor test", 1U)); | |
| Atomic<bool> done_running(false); | |
| // Add a task which will wait until interrupted to the thread pool. | |
| thread_pool->AddTask(self, new WorkUntilDoneTask(&task_processor, &done_running)); | |
| ASSERT_FALSE(done_running.load(std::memory_order_seq_cst)); | |
| thread_pool->StartWorkers(self); | |
| thread_pool->Wait(self, true, false); | |
| ASSERT_TRUE(done_running.load(std::memory_order_seq_cst)); | |
| ASSERT_EQ(counter, kNumTasks); | |
| } | |
| } // namespace gc | |
| } // namespace art |