blob: 9c885ef0e29f7e3700af3c2e2fd045fcb967490a [file] [log] [blame]
/*
* Copyright (C) 2019 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 <gtest/gtest.h>
#include "thread/CommonPool.h"
#include <array>
#include <condition_variable>
#include <set>
#include <thread>
#include "unistd.h"
using namespace android;
using namespace android::uirenderer;
TEST(CommonPool, post) {
std::atomic_bool ran(false);
CommonPool::post([&ran] { ran = true; });
for (int i = 0; !ran && i < 1000; i++) {
usleep(1);
}
EXPECT_TRUE(ran) << "Failed to flip atomic after 1 second";
}
TEST(CommonPool, threadCount) {
std::set<pid_t> threads;
std::array<std::future<pid_t>, 64> futures;
for (int i = 0; i < futures.size(); i++) {
futures[i] = CommonPool::async([] {
usleep(10);
return gettid();
});
}
for (auto& f : futures) {
threads.insert(f.get());
}
EXPECT_EQ(threads.size(), CommonPool::THREAD_COUNT);
EXPECT_EQ(0, threads.count(gettid()));
}
TEST(CommonPool, singleThread) {
std::mutex mutex;
std::condition_variable fence;
bool isProcessing = false;
bool queuedSecond = false;
auto f1 = CommonPool::async([&] {
{
std::unique_lock lock{mutex};
isProcessing = true;
fence.notify_all();
while (!queuedSecond) {
fence.wait(lock);
}
}
return gettid();
});
{
std::unique_lock lock{mutex};
while (!isProcessing) {
fence.wait(lock);
}
}
auto f2 = CommonPool::async([] {
return gettid();
});
{
std::unique_lock lock{mutex};
queuedSecond = true;
fence.notify_all();
}
auto tid1 = f1.get();
auto tid2 = f2.get();
EXPECT_EQ(tid1, tid2);
EXPECT_NE(gettid(), tid1);
}
TEST(CommonPool, fullQueue) {
std::mutex lock;
std::condition_variable fence;
bool signaled = false;
static constexpr auto QUEUE_COUNT = CommonPool::THREAD_COUNT + CommonPool::QUEUE_SIZE + 10;
std::atomic_int queuedCount{0};
std::array<std::future<void>, QUEUE_COUNT> futures;
std::thread queueThread{[&] {
for (int i = 0; i < QUEUE_COUNT; i++) {
futures[i] = CommonPool::async([&] {
std::unique_lock _lock{lock};
while (!signaled) {
fence.wait(_lock);
}
});
queuedCount++;
}
}};
int previous;
do {
previous = queuedCount.load();
usleep(10000);
} while (previous != queuedCount.load());
EXPECT_GT(queuedCount.load(), CommonPool::QUEUE_SIZE);
EXPECT_LT(queuedCount.load(), QUEUE_COUNT);
{
std::unique_lock _lock{lock};
signaled = true;
fence.notify_all();
}
queueThread.join();
EXPECT_EQ(queuedCount.load(), QUEUE_COUNT);
// Ensure all our tasks are finished before return as they have references to the stack
for (auto& f : futures) {
f.get();
}
}
struct DestructorObserver {
DestructorObserver(int* destroyCount) : mDestroyCount(destroyCount) {}
DestructorObserver(const DestructorObserver& other) : mDestroyCount(other.mDestroyCount) {}
DestructorObserver(DestructorObserver&& other) {
mDestroyCount = other.mDestroyCount;
other.mDestroyCount = nullptr;
}
~DestructorObserver() {
if (mDestroyCount) {
(*mDestroyCount)++;
}
}
DestructorObserver& operator=(DestructorObserver&& other) {
mDestroyCount = other.mDestroyCount;
other.mDestroyCount = nullptr;
return *this;
}
int* mDestroyCount;
};
struct CopyObserver {
CopyObserver(int* copyCount) : mCopyCount(copyCount) {}
CopyObserver(CopyObserver&& other) = default;
CopyObserver& operator=(CopyObserver&& other) = default;
CopyObserver(const CopyObserver& other) {
mCopyCount = other.mCopyCount;
if (mCopyCount) {
(*mCopyCount)++;
}
}
CopyObserver& operator=(const CopyObserver& other) {
mCopyCount = other.mCopyCount;
if (mCopyCount) {
(*mCopyCount)++;
}
return *this;
}
int* mCopyCount;
};
TEST(CommonPool, asyncLifecycleCheck) {
std::vector<std::future<void>> mFrameFences;
int destroyCount = 0;
int runCount = 0;
{
DestructorObserver observer{&destroyCount};
auto func = [observer = std::move(observer), count = &runCount]() {
if (observer.mDestroyCount) {
(*count)++;
}
};
mFrameFences.push_back(CommonPool::async(std::move(func)));
}
for (auto& fence : mFrameFences) {
EXPECT_TRUE(fence.valid());
fence.get();
EXPECT_FALSE(fence.valid());
}
mFrameFences.clear();
EXPECT_EQ(1, runCount);
EXPECT_EQ(1, destroyCount);
}
TEST(CommonPool, asyncCopyCheck) {
std::vector<std::future<void>> mFrameFences;
int copyCount = 0;
int runCount = 0;
{
CopyObserver observer{&copyCount};
auto func = [observer = std::move(observer), count = &runCount]() {
if (observer.mCopyCount) {
(*count)++;
}
};
mFrameFences.push_back(CommonPool::async(std::move(func)));
}
for (auto& fence : mFrameFences) {
EXPECT_TRUE(fence.valid());
fence.get();
EXPECT_FALSE(fence.valid());
}
mFrameFences.clear();
EXPECT_EQ(1, runCount);
// We expect std::move all the way
EXPECT_EQ(0, copyCount);
}
TEST(CommonPool, syncLifecycleCheck) {
int destroyCount = 0;
int runCount = 0;
{
DestructorObserver observer{&destroyCount};
auto func = [observer = std::move(observer), count = &runCount]() {
if (observer.mDestroyCount) {
(*count)++;
}
};
CommonPool::runSync(std::move(func));
}
EXPECT_EQ(1, runCount);
EXPECT_EQ(1, destroyCount);
}
TEST(CommonPool, syncCopyCheck) {
int copyCount = 0;
int runCount = 0;
{
CopyObserver observer{&copyCount};
auto func = [observer = std::move(observer), count = &runCount]() {
if (observer.mCopyCount) {
(*count)++;
}
};
CommonPool::runSync(std::move(func));
}
EXPECT_EQ(1, runCount);
// We expect std::move all the way
EXPECT_EQ(0, copyCount);
}