| /* |
| * Copyright (C) 2012 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 "thread_pool.h" |
| |
| #include <string> |
| |
| #include "base/atomic.h" |
| #include "common_runtime_test.h" |
| #include "scoped_thread_state_change-inl.h" |
| #include "thread-inl.h" |
| |
| namespace art { |
| |
| class CountTask : public Task { |
| public: |
| explicit CountTask(AtomicInteger* count) : count_(count), verbose_(false) {} |
| |
| void Run(Thread* self) override { |
| if (verbose_) { |
| LOG(INFO) << "Running: " << *self; |
| } |
| // Simulate doing some work. |
| usleep(100); |
| // Increment the counter which keeps track of work completed. |
| ++*count_; |
| } |
| |
| void Finalize() override { |
| if (verbose_) { |
| LOG(INFO) << "Finalizing: " << *Thread::Current(); |
| } |
| delete this; |
| } |
| |
| private: |
| AtomicInteger* const count_; |
| const bool verbose_; |
| }; |
| |
| class ThreadPoolTest : public CommonRuntimeTest { |
| public: |
| static int32_t num_threads; |
| }; |
| |
| int32_t ThreadPoolTest::num_threads = 4; |
| |
| // Check that the thread pool actually runs tasks that you assign it. |
| TEST_F(ThreadPoolTest, CheckRun) { |
| Thread* self = Thread::Current(); |
| ThreadPool thread_pool("Thread pool test thread pool", num_threads); |
| AtomicInteger count(0); |
| static const int32_t num_tasks = num_threads * 4; |
| for (int32_t i = 0; i < num_tasks; ++i) { |
| thread_pool.AddTask(self, new CountTask(&count)); |
| } |
| thread_pool.StartWorkers(self); |
| // Wait for tasks to complete. |
| thread_pool.Wait(self, true, false); |
| // Make sure that we finished all the work. |
| EXPECT_EQ(num_tasks, count.load(std::memory_order_seq_cst)); |
| } |
| |
| TEST_F(ThreadPoolTest, StopStart) { |
| Thread* self = Thread::Current(); |
| ThreadPool thread_pool("Thread pool test thread pool", num_threads); |
| AtomicInteger count(0); |
| static const int32_t num_tasks = num_threads * 4; |
| for (int32_t i = 0; i < num_tasks; ++i) { |
| thread_pool.AddTask(self, new CountTask(&count)); |
| } |
| usleep(200); |
| // Check that no threads started prematurely. |
| EXPECT_EQ(0, count.load(std::memory_order_seq_cst)); |
| // Signal the threads to start processing tasks. |
| thread_pool.StartWorkers(self); |
| usleep(200); |
| thread_pool.StopWorkers(self); |
| AtomicInteger bad_count(0); |
| thread_pool.AddTask(self, new CountTask(&bad_count)); |
| usleep(200); |
| // Ensure that the task added after the workers were stopped doesn't get run. |
| EXPECT_EQ(0, bad_count.load(std::memory_order_seq_cst)); |
| // Allow tasks to finish up and delete themselves. |
| thread_pool.StartWorkers(self); |
| thread_pool.Wait(self, false, false); |
| } |
| |
| TEST_F(ThreadPoolTest, StopWait) { |
| Thread* self = Thread::Current(); |
| ThreadPool thread_pool("Thread pool test thread pool", num_threads); |
| |
| AtomicInteger count(0); |
| static const int32_t num_tasks = num_threads * 100; |
| for (int32_t i = 0; i < num_tasks; ++i) { |
| thread_pool.AddTask(self, new CountTask(&count)); |
| } |
| |
| // Signal the threads to start processing tasks. |
| thread_pool.StartWorkers(self); |
| usleep(200); |
| thread_pool.StopWorkers(self); |
| |
| thread_pool.Wait(self, false, false); // We should not deadlock here. |
| |
| // Drain the task list. Note: we have to restart here, as no tasks will be finished when |
| // the pool is stopped. |
| thread_pool.StartWorkers(self); |
| thread_pool.Wait(self, /* do_work */ true, false); |
| } |
| |
| class TreeTask : public Task { |
| public: |
| TreeTask(ThreadPool* const thread_pool, AtomicInteger* count, int depth) |
| : thread_pool_(thread_pool), |
| count_(count), |
| depth_(depth) {} |
| |
| void Run(Thread* self) override { |
| if (depth_ > 1) { |
| thread_pool_->AddTask(self, new TreeTask(thread_pool_, count_, depth_ - 1)); |
| thread_pool_->AddTask(self, new TreeTask(thread_pool_, count_, depth_ - 1)); |
| } |
| // Increment the counter which keeps track of work completed. |
| ++*count_; |
| } |
| |
| void Finalize() override { |
| delete this; |
| } |
| |
| private: |
| ThreadPool* const thread_pool_; |
| AtomicInteger* const count_; |
| const int depth_; |
| }; |
| |
| // Test that adding new tasks from within a task works. |
| TEST_F(ThreadPoolTest, RecursiveTest) { |
| Thread* self = Thread::Current(); |
| ThreadPool thread_pool("Thread pool test thread pool", num_threads); |
| AtomicInteger count(0); |
| static const int depth = 8; |
| thread_pool.AddTask(self, new TreeTask(&thread_pool, &count, depth)); |
| thread_pool.StartWorkers(self); |
| thread_pool.Wait(self, true, false); |
| EXPECT_EQ((1 << depth) - 1, count.load(std::memory_order_seq_cst)); |
| } |
| |
| class PeerTask : public Task { |
| public: |
| PeerTask() {} |
| |
| void Run(Thread* self) override { |
| ScopedObjectAccess soa(self); |
| CHECK(self->GetPeer() != nullptr); |
| } |
| |
| void Finalize() override { |
| delete this; |
| } |
| }; |
| |
| class NoPeerTask : public Task { |
| public: |
| NoPeerTask() {} |
| |
| void Run(Thread* self) override { |
| ScopedObjectAccess soa(self); |
| CHECK(self->GetPeer() == nullptr); |
| } |
| |
| void Finalize() override { |
| delete this; |
| } |
| }; |
| |
| // Tests for create_peer functionality. |
| TEST_F(ThreadPoolTest, PeerTest) { |
| Thread* self = Thread::Current(); |
| { |
| ThreadPool thread_pool("Thread pool test thread pool", 1); |
| thread_pool.AddTask(self, new NoPeerTask()); |
| thread_pool.StartWorkers(self); |
| thread_pool.Wait(self, false, false); |
| } |
| |
| { |
| // To create peers, the runtime needs to be started. |
| self->TransitionFromSuspendedToRunnable(); |
| bool started = runtime_->Start(); |
| ASSERT_TRUE(started); |
| |
| ThreadPool thread_pool("Thread pool test thread pool", 1, true); |
| thread_pool.AddTask(self, new PeerTask()); |
| thread_pool.StartWorkers(self); |
| thread_pool.Wait(self, false, false); |
| } |
| } |
| |
| } // namespace art |