add Task and TaskQueue
This commit is contained in:
parent
07209204b9
commit
63911d448d
@ -13,6 +13,7 @@ target_sources(nf7_iface
|
||||
common/container.hh
|
||||
common/future.hh
|
||||
common/observer.hh
|
||||
common/task.hh
|
||||
common/value.hh
|
||||
version.hh
|
||||
)
|
||||
@ -25,6 +26,7 @@ target_sources(nf7_iface_test
|
||||
common/future_test.cc
|
||||
common/observer_test.hh
|
||||
common/observer_test.cc
|
||||
common/task_test.cc
|
||||
common/value_test.cc
|
||||
)
|
||||
target_link_libraries(nf7_iface_test
|
||||
|
106
iface/common/task.hh
Normal file
106
iface/common/task.hh
Normal file
@ -0,0 +1,106 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "iface/common/exception.hh"
|
||||
#include "iface/common/future.hh"
|
||||
|
||||
namespace nf7 {
|
||||
|
||||
class Task final {
|
||||
public:
|
||||
Task() = delete;
|
||||
explicit Task(
|
||||
std::function<void()>&& func,
|
||||
std::source_location location = std::source_location::current()) noexcept
|
||||
: func_(std::move(func)),
|
||||
location_(location) {
|
||||
assert(func_);
|
||||
}
|
||||
|
||||
Task(const Task&) = delete;
|
||||
Task(Task&&) = default;
|
||||
Task& operator=(const Task&) = delete;
|
||||
Task& operator=(Task&&) = default;
|
||||
|
||||
void Run() {
|
||||
if (!func_) {
|
||||
throw Exception {"double run is not allowed", location_};
|
||||
}
|
||||
try {
|
||||
auto f = std::move(func_);
|
||||
f();
|
||||
} catch (...) {
|
||||
throw Exception {"task throws an exception", location_};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> func_;
|
||||
|
||||
std::source_location location_;
|
||||
};
|
||||
|
||||
class TaskQueue : public std::enable_shared_from_this<TaskQueue> {
|
||||
public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
TaskQueue(const TaskQueue&) = delete;
|
||||
TaskQueue(TaskQueue&&) = delete;
|
||||
TaskQueue& operator=(const TaskQueue&) = delete;
|
||||
TaskQueue& operator=(TaskQueue&&) = delete;
|
||||
|
||||
// THREAD SAFE
|
||||
// an implementation must handle memory errors well
|
||||
virtual void Push(Task&&) noexcept = 0;
|
||||
|
||||
// THREAD SAFE
|
||||
auto Wrap(
|
||||
auto&& f,
|
||||
std::source_location loc = std::source_location::current()) noexcept {
|
||||
return [self = shared_from_this(), f = std::move(f), loc](auto&&... args) {
|
||||
self->Push(Task {[f = std::move(f),
|
||||
...args = std::forward<decltype(args)>(args)]() {
|
||||
f(std::forward<decltype(args)>(args)...);
|
||||
}, loc});
|
||||
};
|
||||
}
|
||||
|
||||
// THREAD SAFE
|
||||
template <typename R>
|
||||
Future<R> RunAnd(
|
||||
std::function<R()>&& f,
|
||||
std::source_location loc = std::source_location::current()) noexcept {
|
||||
return RunAnd({}, std::move(f));
|
||||
}
|
||||
|
||||
// THREAD SAFE
|
||||
template <typename R>
|
||||
Future<R> RunAnd(
|
||||
Future<R>::Completer&& comp,
|
||||
std::function<R()>&& f,
|
||||
std::source_location loc = std::source_location::current()) noexcept {
|
||||
Future<R> future {comp};
|
||||
Push(Task {
|
||||
[f = std::move(f), comp = std::move(comp)]() { comp.Run(f); }, loc});
|
||||
return future;
|
||||
}
|
||||
|
||||
// THREAD SAFE
|
||||
void Run(
|
||||
std::function<void()>&& f,
|
||||
std::source_location loc = std::source_location::current()) noexcept {
|
||||
Push(Task {std::move(f), loc});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nf7
|
47
iface/common/task_test.cc
Normal file
47
iface/common/task_test.cc
Normal file
@ -0,0 +1,47 @@
|
||||
// No copyright
|
||||
#include "iface/common/task.hh"
|
||||
#include "iface/common/task_test.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "iface/common/future.hh"
|
||||
|
||||
|
||||
TEST(Task, RunAndThrow) {
|
||||
const auto line = __LINE__ + 1;
|
||||
nf7::Task task {[&]() { throw nf7::Exception {"hello"}; }};
|
||||
|
||||
try {
|
||||
task.Run();
|
||||
EXPECT_FALSE("unreachable (exception expected)");
|
||||
} catch (const nf7::Exception& e) {
|
||||
EXPECT_EQ(e.location().line(), line);
|
||||
EXPECT_EQ(e.location().file_name(), __FILE__);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TaskQueue, Wrap) {
|
||||
auto sut = std::make_shared<nf7::test::TaskQueueMock>();
|
||||
auto wrapped = sut->Wrap([](){});
|
||||
|
||||
EXPECT_CALL(*sut, Push(::testing::_)).Times(1);
|
||||
|
||||
wrapped();
|
||||
}
|
||||
|
||||
TEST(TaskQueue, WrapInFutureThen) {
|
||||
auto sut = std::make_shared<nf7::test::TaskQueueMock>();
|
||||
ON_CALL(*sut, Push(::testing::_)).WillByDefault([](auto&& task) {
|
||||
task.Run();
|
||||
});
|
||||
|
||||
nf7::Future<int32_t> fut {int32_t {777}};
|
||||
|
||||
auto called = uint32_t {0};
|
||||
fut.Then(sut->Wrap([&](auto& x) {
|
||||
++called;
|
||||
EXPECT_EQ(x, int32_t {777});
|
||||
}));
|
||||
|
||||
EXPECT_EQ(called, 1);
|
||||
}
|
18
iface/common/task_test.hh
Normal file
18
iface/common/task_test.hh
Normal file
@ -0,0 +1,18 @@
|
||||
// No copyright
|
||||
#pragma once
|
||||
|
||||
#include "iface/common/task.hh"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
|
||||
namespace nf7::test {
|
||||
|
||||
class TaskQueueMock : public nf7::TaskQueue {
|
||||
public:
|
||||
TaskQueueMock() = default;
|
||||
|
||||
MOCK_METHOD(void, Push, (Task&&), (noexcept));
|
||||
};
|
||||
|
||||
} // namespace nf7::test
|
Loading…
x
Reference in New Issue
Block a user