| /* | |
| * Copyright 2025 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkExecutor.h" | |
| #include "src/core/SkSpinlock.h" | |
| #include "src/core/SkTaskGroup.h" | |
| #include "tests/Test.h" | |
| #include <array> | |
| #include <thread> | |
| namespace { | |
| constexpr int kNumWorkLists = 2; | |
| constexpr int kNumThreads = 4; | |
| constexpr int kHighPriority = 0; | |
| constexpr int kLowPriority = 1; | |
| // This utility just collects a set kMaxCount ints in a thread-safe manner. It has a | |
| // predicate to check if the results match expectations. | |
| class Collector { | |
| public: | |
| static constexpr int kMaxCount = 200; | |
| void insert(int i) SK_EXCLUDES(fSpinLock) { | |
| SkAutoSpinlock lock{fSpinLock}; | |
| if (fCount >= kMaxCount) { | |
| return; | |
| } | |
| fData[fCount++] = i; | |
| } | |
| int count() const SK_EXCLUDES(fSpinLock) { | |
| SkAutoSpinlock lock{fSpinLock}; | |
| return fCount; | |
| } | |
| #if defined(SK_DEBUG) | |
| void dump() const SK_EXCLUDES(fSpinLock) { | |
| SkAutoSpinlock lock{fSpinLock}; | |
| for (int i = 0; i < fCount; ++i) { | |
| SkDebugf("%d ", fData[i]); | |
| } | |
| SkDebugf("\n"); | |
| } | |
| #endif | |
| // For this following tests we expect that all the high priority work is completed | |
| // before the low priority work. The length of the two task types is set up so, | |
| // even if low priority work is picked up while the high priority tasks are finishing, | |
| // all the high priority task should still finish before the low priority ones. | |
| bool dataIsInOrder() const SK_EXCLUDES(fSpinLock) { | |
| SkAutoSpinlock lock{fSpinLock}; | |
| for (int i = 0; i < fCount; ++i) { | |
| if (i < 100) { | |
| if (fData[i] >= 100) { return false; } | |
| } else { | |
| if (fData[i] < 100) { return false; } | |
| } | |
| } | |
| return true; | |
| } | |
| private: | |
| mutable SkSpinlock fSpinLock; | |
| int fCount SK_GUARDED_BY(fSpinLock) = 0; | |
| std::array<int, kMaxCount> fData SK_GUARDED_BY(fSpinLock); | |
| }; | |
| // Make sure all high priority work is started before the low priority work is begun | |
| void priority_test(skiatest::Reporter* reporter, std::unique_ptr<SkExecutor> executor) { | |
| SkTaskGroup taskGroup(*executor); | |
| Collector collector; | |
| for (int i = 0; i < 100; ++i) { | |
| taskGroup.add([&collector, i]() { | |
| collector.insert(i); | |
| std::this_thread::sleep_for(std::chrono::microseconds(50)); | |
| }, kHighPriority); | |
| } | |
| for (int i = 100; i < 200; ++i) { | |
| taskGroup.add([&collector, i]() { | |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); | |
| collector.insert(i); | |
| }, kLowPriority); | |
| } | |
| taskGroup.wait(); | |
| REPORTER_ASSERT(reporter, collector.count() == Collector::kMaxCount); | |
| REPORTER_ASSERT(reporter, collector.dataIsInOrder()); | |
| } | |
| std::unique_ptr<SkExecutor> make_2x_FIFO() { | |
| return SkExecutors::MakeMultiListFIFOThreadPool( | |
| kNumWorkLists, kNumThreads, /* allowBorrowing= */ false); | |
| } | |
| std::unique_ptr<SkExecutor> make_2x_LIFO() { | |
| return SkExecutors::MakeMultiListLIFOThreadPool( | |
| kNumWorkLists, kNumThreads, /* allowBorrowing= */ false); | |
| } | |
| std::unique_ptr<SkExecutor> make_1x_FIFO() { | |
| return SkExecutors::MakeFIFOThreadPool(kNumThreads, /* allowBorrowing= */ false); | |
| } | |
| } // anonymous namespace | |
| DEF_TEST(ExecutorTest, reporter) { | |
| // 1x_LIFO is incompatible w/ how this test is structured | |
| for (auto makeExecutor : { make_2x_FIFO, make_2x_LIFO, make_1x_FIFO }) { | |
| priority_test(reporter, makeExecutor()); | |
| } | |
| } |