blob: 2a89024f04f93591b9ad264f9ee6fef25a2ce71e [file] [log] [blame]
//===--- 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);
});
}