| //===--- Actor.cpp - Unit tests for the actor API -------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/ABI/Actor.h" |
| #include "swift/Runtime/Concurrency.h" |
| #include "swift/Runtime/Metadata.h" |
| #include "swift/Basic/STLExtras.h" |
| #include "llvm/ADT/Optional.h" |
| #include "gtest/gtest.h" |
| #include <vector> |
| #include <tuple> |
| |
| using namespace swift; |
| |
| /// The current location. |
| static unsigned progressIndex = 0; |
| #define EXPECT_PROGRESS(NUMBER) \ |
| EXPECT_EQ((unsigned) (NUMBER), progressIndex++) |
| |
| enum { |
| FinishedIndex = 100000 |
| }; |
| static void finishTest() { |
| progressIndex = FinishedIndex; |
| } |
| |
| static std::vector<Job*> globalQueue; |
| SWIFT_CC(swift) |
| static void enqueueGlobal(Job *job) { |
| assert(job); |
| |
| // Check that the job isn't already on the queue. |
| for (auto oldJob: globalQueue) { |
| EXPECT_NE(job, oldJob); |
| } |
| |
| // The queue will actually be executed starting from the back. |
| // Add the job after (i.e. before in execution order) all jobs |
| // with lower priority. |
| for (auto i = globalQueue.begin(), e = globalQueue.end(); i != e; ++i) { |
| if (job->getPriority() <= (*i)->getPriority()) { |
| globalQueue.insert(i, job); |
| return; |
| } |
| } |
| |
| // If the job's priority is higher than everything in the existing |
| // queue, set it as the new front of the queue. |
| globalQueue.push_back(job); |
| } |
| |
| static void run(llvm::function_ref<void()> fn) { |
| swift_task_enqueueGlobal_hook = &enqueueGlobal; |
| |
| progressIndex = 0; |
| |
| // Run the setup function. |
| fn(); |
| |
| // The setup function needs to add something to the queue. |
| EXPECT_FALSE(globalQueue.empty()); |
| |
| // Insertion does a priority sort, so we can just process in-order. |
| // But that order starts from the back. |
| while (!globalQueue.empty()) { |
| auto job = globalQueue.back(); |
| globalQueue.pop_back(); |
| |
| job->run(ExecutorRef::generic()); |
| } |
| |
| EXPECT_EQ(FinishedIndex, progressIndex); |
| |
| swift_task_enqueueGlobal_hook = nullptr; |
| } |
| |
| namespace { |
| |
| /// A simple actor class. |
| class TestActor : public DefaultActor { |
| public: |
| TestActor(); |
| ~TestActor() { |
| swift_defaultActor_destroy(this); |
| } |
| bool HasBeenDestructed = false; |
| }; |
| static SWIFT_CC(swift) |
| void destroyTestActor(SWIFT_CONTEXT HeapObject *_object) { |
| delete static_cast<TestActor*>(_object); |
| } |
| static const FullMetadata<ClassMetadata> TestActorMetadata = { |
| { { &destroyTestActor }, { &VALUE_WITNESS_SYM(Bo) } }, |
| { { nullptr }, ClassFlags::UsesSwiftRefcounting, 0, 0, 0, 0, 0, 0 } |
| }; |
| TestActor::TestActor() : DefaultActor(&TestActorMetadata) { |
| swift_defaultActor_initialize(this); |
| } |
| |
| static TestActor *createActor() { |
| return new TestActor(); |
| } |
| |
| /// A very silly template that stores the latest instance of a particular |
| /// lambda in global storage and then returns a function pointer that |
| /// matches an async task continuation function signature. |
| template <class Fn, class Context> |
| class TaskContinuationFromLambda { |
| static llvm::Optional<Fn> lambdaStorage; |
| |
| SWIFT_CC(swiftasync) |
| static void invoke(AsyncTask *task, ExecutorRef executor, |
| AsyncContext *context) { |
| (*lambdaStorage)(task, executor, static_cast<Context*>(context)); |
| } |
| |
| public: |
| static TaskContinuationFunction *get(Fn &&fn) { |
| lambdaStorage.emplace(std::move(fn)); |
| return &invoke; |
| } |
| }; |
| |
| template <class Fn, class Context> |
| llvm::Optional<Fn> TaskContinuationFromLambda<Fn, Context>::lambdaStorage; |
| |
| } // end anonymous namespace |
| |
| template <class Context, class Fn> |
| static std::pair<AsyncTask*, Context*> |
| createTaskWithContext(JobPriority priority, Fn &&fn) { |
| auto invoke = |
| TaskContinuationFromLambda<Fn, Context>::get(std::move(fn)); |
| |
| auto pair = swift_task_create_f(JobFlags(JobKind::Task, priority), |
| /*parent*/ nullptr, |
| invoke, |
| sizeof(Context)); |
| return std::make_pair(pair.Task, |
| static_cast<Context*>(pair.InitialContext)); |
| } |
| |
| template <class Fn> |
| static AsyncTask *createTask(JobPriority priority, Fn &&fn) { |
| return createTaskWithContext<AsyncContext, Fn>(priority, std::move(fn)) |
| .first; |
| } |
| |
| template <class Context, class Fn> |
| static void parkTask(AsyncTask *task, Context *context, Fn &&fn) { |
| auto invoke = |
| TaskContinuationFromLambda<Fn, Context>::get(std::move(fn)); |
| task->ResumeTask = invoke; |
| task->ResumeContext = context; |
| } |
| |
| namespace { |
| template <class... ValueTypes> |
| class TupleContext : public AsyncContext { |
| public: |
| using TupleType = std::tuple<ValueTypes...>; |
| TupleType values; |
| |
| template <unsigned N> auto get() { return std::get<N>(values); } |
| }; |
| |
| /// This extremely silly template repeatedly rotates an argument list |
| /// until the last argument is first, then returns a pair of that and |
| /// a tuple of the remaining arguments. |
| template <unsigned N> struct Decomposer { |
| template <class FirstTy, class... OtherTys> |
| static auto decompose(FirstTy &&first, OtherTys &&...others) { |
| return Decomposer<N-1>::decompose(std::forward<OtherTys>(others)..., |
| std::forward<FirstTy>(first)); |
| } |
| }; |
| template <> struct Decomposer<0> { |
| template <class FirstTy, class... OtherTys> |
| static auto decompose(FirstTy &&first, OtherTys &&...others) { |
| return std::make_pair(std::move(first), |
| std::make_tuple(std::forward<OtherTys>(others)...)); |
| } |
| }; |
| |
| /// This moderately silly template forwards a template argument pack. |
| template <class T> struct TupleContextTypeFor; |
| template <class... EltTys> struct TupleContextTypeFor<std::tuple<EltTys...>> { |
| using type = TupleContext<EltTys...>; |
| }; |
| } // end anonymous namespace |
| |
| template <class... ArgTypes> |
| static AsyncTask *createTaskStoring(JobPriority priority, |
| ArgTypes... args) { |
| auto fnAndTuple = Decomposer<sizeof...(args) - 1>::decompose(args...); |
| |
| using TupleType = decltype(fnAndTuple.second); |
| using ContextType = typename TupleContextTypeFor<TupleType>::type; |
| |
| auto taskAndContext = |
| createTaskWithContext<ContextType>(priority, std::move(fnAndTuple.first)); |
| auto ptr = &taskAndContext.second->values; |
| new(ptr) TupleType(std::move(fnAndTuple.second)); |
| return taskAndContext.first; |
| } |
| |
| TEST(ActorTest, validateTestHarness) { |
| run([] { |
| auto task0 = createTask(JobPriority::Background, |
| [](AsyncTask *task, ExecutorRef executor, AsyncContext *context) { |
| EXPECT_PROGRESS(5); |
| EXPECT_PROGRESS(6); |
| finishTest(); |
| return context->resumeParent(task, executor); |
| }); |
| auto task1 = createTask(JobPriority::Default, |
| [](AsyncTask *task, ExecutorRef executor, AsyncContext *context) { |
| EXPECT_PROGRESS(1); |
| EXPECT_PROGRESS(2); |
| return context->resumeParent(task, executor); |
| }); |
| auto task2 = createTask(JobPriority::Default, |
| [](AsyncTask *task, ExecutorRef executor, AsyncContext *context) { |
| EXPECT_PROGRESS(3); |
| EXPECT_PROGRESS(4); |
| return context->resumeParent(task, executor); |
| }); |
| |
| swift_task_enqueueGlobal(task0); |
| swift_task_enqueueGlobal(task1); |
| swift_task_enqueueGlobal(task2); |
| EXPECT_PROGRESS(0); |
| }); |
| } |
| |
| |
| TEST(ActorTest, actorSwitch) { |
| run([] { |
| using Context = TupleContext<AsyncTask*, TestActor*>; |
| |
| auto actor = createActor(); |
| auto task0 = createTaskStoring(JobPriority::Default, |
| (AsyncTask*) nullptr, actor, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(1); |
| EXPECT_TRUE(executor.isGeneric()); |
| EXPECT_EQ(nullptr, context->get<0>()); |
| std::get<0>(context->values) = task; |
| |
| parkTask(task, context, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(2); |
| EXPECT_FALSE(executor.isGeneric()); |
| EXPECT_EQ(ExecutorRef::forDefaultActor(context->get<1>()), |
| executor); |
| EXPECT_EQ(task, context->get<0>()); |
| parkTask(task, context, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(3); |
| EXPECT_TRUE(executor.isGeneric()); |
| EXPECT_EQ(task, context->get<0>()); |
| finishTest(); |
| return context->resumeParent(task, executor); |
| }); |
| return swift_task_switch(task, executor, ExecutorRef::generic()); |
| }); |
| return swift_task_switch(task, executor, |
| ExecutorRef::forDefaultActor(context->get<1>())); |
| }); |
| swift_task_enqueueGlobal(task0); |
| EXPECT_PROGRESS(0); |
| }); |
| } |
| |
| TEST(ActorTest, actorContention) { |
| run([] { |
| using Context = TupleContext<AsyncTask*, TestActor*>; |
| auto actor = createActor(); |
| |
| // This test only really works because actors are FIFO. |
| |
| auto task0 = createTaskStoring(JobPriority::Default, |
| (AsyncTask*) nullptr, actor, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(1); |
| EXPECT_TRUE(executor.isGeneric()); |
| EXPECT_EQ(nullptr, context->get<0>()); |
| std::get<0>(context->values) = task; |
| |
| parkTask(task, context, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(3); |
| EXPECT_FALSE(executor.isGeneric()); |
| EXPECT_EQ(ExecutorRef::forDefaultActor(context->get<1>()), |
| executor); |
| EXPECT_EQ(task, context->get<0>()); |
| parkTask(task, context, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(4); |
| EXPECT_TRUE(executor.isGeneric()); |
| EXPECT_EQ(task, context->get<0>()); |
| return context->resumeParent(task, executor); |
| }); |
| swift_task_enqueue(task, ExecutorRef::generic()); |
| }); |
| |
| swift_task_enqueue(task, ExecutorRef::forDefaultActor(context->get<1>())); |
| }); |
| swift_task_enqueueGlobal(task0); |
| |
| auto task1 = createTaskStoring(JobPriority::Background, |
| (AsyncTask*) nullptr, actor, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(2); |
| EXPECT_FALSE(executor.isGeneric()); |
| EXPECT_EQ(ExecutorRef::forDefaultActor(context->get<1>()), |
| executor); |
| EXPECT_EQ(nullptr, context->get<0>()); |
| std::get<0>(context->values) = task; |
| |
| parkTask(task, context, |
| [](AsyncTask *task, ExecutorRef executor, Context *context) { |
| EXPECT_PROGRESS(5); |
| EXPECT_TRUE(executor.isGeneric()); |
| EXPECT_EQ(task, context->get<0>()); |
| finishTest(); |
| return context->resumeParent(task, executor); |
| }); |
| |
| swift_task_enqueue(task, ExecutorRef::generic()); |
| }); |
| swift_task_enqueue(task1, ExecutorRef::forDefaultActor(actor)); |
| |
| EXPECT_PROGRESS(0); |
| }); |
| } |