initial
This commit is contained in:
57
td/tdactor/CMakeLists.txt
Normal file
57
td/tdactor/CMakeLists.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
|
||||
message(FATAL_ERROR "CMake >= 3.0.2 is required")
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED CMAKE_INSTALL_LIBDIR)
|
||||
set(CMAKE_INSTALL_LIBDIR "lib")
|
||||
endif()
|
||||
|
||||
set(TDACTOR_SOURCE
|
||||
td/actor/ConcurrentScheduler.cpp
|
||||
td/actor/impl/Scheduler.cpp
|
||||
td/actor/MultiPromise.cpp
|
||||
td/actor/MultiTimeout.cpp
|
||||
|
||||
td/actor/actor.h
|
||||
td/actor/ConcurrentScheduler.h
|
||||
td/actor/impl/Actor-decl.h
|
||||
td/actor/impl/Actor.h
|
||||
td/actor/impl/ActorId-decl.h
|
||||
td/actor/impl/ActorId.h
|
||||
td/actor/impl/ActorInfo-decl.h
|
||||
td/actor/impl/ActorInfo.h
|
||||
td/actor/impl/Event.h
|
||||
td/actor/impl/EventFull-decl.h
|
||||
td/actor/impl/EventFull.h
|
||||
td/actor/impl/Scheduler-decl.h
|
||||
td/actor/impl/Scheduler.h
|
||||
td/actor/MultiPromise.h
|
||||
td/actor/MultiTimeout.h
|
||||
td/actor/PromiseFuture.h
|
||||
td/actor/SchedulerLocalStorage.h
|
||||
td/actor/SignalSlot.h
|
||||
td/actor/SleepActor.h
|
||||
td/actor/Timeout.h
|
||||
)
|
||||
|
||||
set(TDACTOR_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_simple.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_workers.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test/actors_bugs.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
add_library(tdactor STATIC ${TDACTOR_SOURCE})
|
||||
target_include_directories(tdactor PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(tdactor PUBLIC tdutils)
|
||||
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
add_executable(example example/example.cpp)
|
||||
target_link_libraries(example PRIVATE tdactor)
|
||||
endif()
|
||||
|
||||
install(TARGETS tdactor EXPORT TdStaticTargets
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
)
|
||||
49
td/tdactor/example/example.cpp
Normal file
49
td/tdactor/example/example.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
class Worker final : public td::Actor {
|
||||
public:
|
||||
void ping(int x) {
|
||||
LOG(ERROR) << "Receive ping " << x;
|
||||
}
|
||||
};
|
||||
|
||||
class MainActor final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
LOG(ERROR) << "Start up";
|
||||
set_timeout_in(10);
|
||||
worker_ = td::create_actor_on_scheduler<Worker>("Worker", 1);
|
||||
send_closure(worker_, &Worker::ping, 123);
|
||||
}
|
||||
|
||||
void timeout_expired() final {
|
||||
LOG(ERROR) << "Timeout expired";
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorOwn<Worker> worker_;
|
||||
};
|
||||
|
||||
int main() {
|
||||
td::ConcurrentScheduler scheduler(4 /*thread_count*/, 0);
|
||||
scheduler.start();
|
||||
{
|
||||
auto guard = scheduler.get_main_guard();
|
||||
td::create_actor_on_scheduler<MainActor>("Main actor", 0).release();
|
||||
}
|
||||
while (!scheduler.is_finished()) {
|
||||
scheduler.run_main(td::Timestamp::in(10));
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
205
td/tdactor/td/actor/ConcurrentScheduler.cpp
Normal file
205
td/tdactor/td/actor/ConcurrentScheduler.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
|
||||
#include "td/utils/ExitGuard.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
#include "td/utils/port/thread_local.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace td {
|
||||
|
||||
ConcurrentScheduler::ConcurrentScheduler(int32 additional_thread_count, uint64 thread_affinity_mask) {
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
additional_thread_count = 0;
|
||||
#endif
|
||||
additional_thread_count++;
|
||||
std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound(additional_thread_count);
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
for (int32 i = 0; i < additional_thread_count; i++) {
|
||||
auto queue = std::make_shared<MpscPollableQueue<EventFull>>();
|
||||
queue->init();
|
||||
outbound[i] = queue;
|
||||
}
|
||||
thread_affinity_mask_ = thread_affinity_mask;
|
||||
#endif
|
||||
|
||||
// +1 for extra scheduler for IOCP and send_closure from unrelated threads
|
||||
// It will know about other schedulers
|
||||
// Other schedulers will have no idea about its existence
|
||||
extra_scheduler_ = 1;
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
extra_scheduler_ = 0;
|
||||
#endif
|
||||
|
||||
schedulers_.resize(additional_thread_count + extra_scheduler_);
|
||||
for (int32 i = 0; i < additional_thread_count + extra_scheduler_; i++) {
|
||||
auto &sched = schedulers_[i];
|
||||
sched = make_unique<Scheduler>();
|
||||
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
if (i >= additional_thread_count) {
|
||||
auto queue = std::make_shared<MpscPollableQueue<EventFull>>();
|
||||
queue->init();
|
||||
outbound.push_back(std::move(queue));
|
||||
}
|
||||
#endif
|
||||
|
||||
sched->init(i, outbound, static_cast<Scheduler::Callback *>(this));
|
||||
}
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
iocp_ = make_unique<detail::Iocp>();
|
||||
iocp_->init();
|
||||
#endif
|
||||
|
||||
state_ = State::Start;
|
||||
}
|
||||
|
||||
void ConcurrentScheduler::test_one_thread_run() {
|
||||
do {
|
||||
for (auto &sched : schedulers_) {
|
||||
sched->run(Timestamp::now_cached());
|
||||
}
|
||||
} while (!is_finished_.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
#if !TD_THREAD_UNSUPPORTED
|
||||
thread::id ConcurrentScheduler::get_scheduler_thread_id(int32 sched_id) {
|
||||
auto thread_pos = static_cast<size_t>(sched_id - 1);
|
||||
CHECK(thread_pos < threads_.size());
|
||||
return threads_[thread_pos].get_id();
|
||||
}
|
||||
#endif
|
||||
|
||||
void ConcurrentScheduler::start() {
|
||||
CHECK(state_ == State::Start);
|
||||
is_finished_.store(false, std::memory_order_relaxed);
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
for (size_t i = 1; i + extra_scheduler_ < schedulers_.size(); i++) {
|
||||
auto &sched = schedulers_[i];
|
||||
threads_.push_back(td::thread([&, thread_affinity_mask = thread_affinity_mask_] {
|
||||
#if TD_PORT_WINDOWS
|
||||
detail::Iocp::Guard iocp_guard(iocp_.get());
|
||||
#endif
|
||||
#if TD_HAVE_THREAD_AFFINITY
|
||||
if (thread_affinity_mask != 0) {
|
||||
thread::set_affinity_mask(this_thread::get_id(), thread_affinity_mask).ignore();
|
||||
}
|
||||
#else
|
||||
(void)thread_affinity_mask;
|
||||
#endif
|
||||
while (!is_finished()) {
|
||||
sched->run(Timestamp::in(10));
|
||||
}
|
||||
}));
|
||||
}
|
||||
#if TD_PORT_WINDOWS
|
||||
iocp_thread_ = td::thread([this] {
|
||||
auto guard = this->get_send_guard();
|
||||
this->iocp_->loop();
|
||||
});
|
||||
#endif
|
||||
#endif
|
||||
|
||||
state_ = State::Run;
|
||||
}
|
||||
|
||||
static TD_THREAD_LOCAL double emscripten_timeout;
|
||||
|
||||
bool ConcurrentScheduler::run_main(Timestamp timeout) {
|
||||
CHECK(state_ == State::Run);
|
||||
// run main scheduler in same thread
|
||||
auto &main_sched = schedulers_[0];
|
||||
if (!is_finished()) {
|
||||
#if TD_PORT_WINDOWS
|
||||
detail::Iocp::Guard iocp_guard(iocp_.get());
|
||||
#endif
|
||||
main_sched->run(timeout);
|
||||
}
|
||||
|
||||
// hack for emscripten
|
||||
emscripten_timeout = get_main_timeout().at();
|
||||
|
||||
return !is_finished();
|
||||
}
|
||||
|
||||
Timestamp ConcurrentScheduler::get_main_timeout() {
|
||||
CHECK(state_ == State::Run);
|
||||
return schedulers_[0]->get_timeout();
|
||||
}
|
||||
|
||||
double ConcurrentScheduler::emscripten_get_main_timeout() {
|
||||
return Timestamp::at(emscripten_timeout).in();
|
||||
}
|
||||
void ConcurrentScheduler::emscripten_clear_main_timeout() {
|
||||
emscripten_timeout = 0;
|
||||
}
|
||||
|
||||
void ConcurrentScheduler::finish() {
|
||||
CHECK(state_ == State::Run);
|
||||
if (!is_finished()) {
|
||||
on_finish();
|
||||
}
|
||||
#if TD_PORT_WINDOWS
|
||||
SCOPE_EXIT {
|
||||
iocp_->clear();
|
||||
};
|
||||
detail::Iocp::Guard iocp_guard(iocp_.get());
|
||||
#endif
|
||||
|
||||
if (ExitGuard::is_exited()) {
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
// prevent closing of schedulers from already killed by OS threads
|
||||
for (auto &thread : threads_) {
|
||||
thread.detach();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
iocp_->interrupt_loop();
|
||||
iocp_thread_.detach();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
for (auto &thread : threads_) {
|
||||
thread.join();
|
||||
}
|
||||
threads_.clear();
|
||||
#endif
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
iocp_->interrupt_loop();
|
||||
iocp_thread_.join();
|
||||
#endif
|
||||
|
||||
schedulers_.clear();
|
||||
for (auto &f : at_finish_) {
|
||||
f();
|
||||
}
|
||||
at_finish_.clear();
|
||||
|
||||
state_ = State::Start;
|
||||
}
|
||||
|
||||
void ConcurrentScheduler::on_finish() {
|
||||
is_finished_.store(true, std::memory_order_relaxed);
|
||||
for (auto &it : schedulers_) {
|
||||
it->wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
void ConcurrentScheduler::register_at_finish(std::function<void()> f) {
|
||||
std::lock_guard<std::mutex> lock(at_finish_mutex_);
|
||||
at_finish_.push_back(std::move(f));
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
116
td/tdactor/td/actor/ConcurrentScheduler.h
Normal file
116
td/tdactor/td/actor/ConcurrentScheduler.h
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#if TD_PORT_WINDOWS
|
||||
#include "td/utils/port/detail/Iocp.h"
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
class ConcurrentScheduler final : private Scheduler::Callback {
|
||||
public:
|
||||
explicit ConcurrentScheduler(int32 additional_thread_count, uint64 thread_affinity_mask = 0);
|
||||
|
||||
void finish_async() {
|
||||
schedulers_[0]->finish();
|
||||
}
|
||||
|
||||
void wakeup() {
|
||||
schedulers_[0]->wakeup();
|
||||
}
|
||||
|
||||
SchedulerGuard get_main_guard() {
|
||||
return schedulers_[0]->get_guard();
|
||||
}
|
||||
|
||||
SchedulerGuard get_send_guard() {
|
||||
return schedulers_.back()->get_const_guard();
|
||||
}
|
||||
|
||||
void test_one_thread_run();
|
||||
|
||||
bool is_finished() const {
|
||||
return is_finished_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
#if TD_THREAD_UNSUPPORTED
|
||||
int get_scheduler_thread_id(int32 sched_id) {
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
thread::id get_scheduler_thread_id(int32 sched_id);
|
||||
#endif
|
||||
|
||||
void start();
|
||||
|
||||
bool run_main(double timeout) {
|
||||
return run_main(Timestamp::in(timeout));
|
||||
}
|
||||
bool run_main(Timestamp timeout);
|
||||
|
||||
Timestamp get_main_timeout();
|
||||
static double emscripten_get_main_timeout();
|
||||
static void emscripten_clear_main_timeout();
|
||||
|
||||
void finish();
|
||||
|
||||
template <class ActorT, class... Args>
|
||||
ActorOwn<ActorT> create_actor_unsafe(int32 sched_id, Slice name, Args &&...args) {
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
sched_id = 0;
|
||||
#endif
|
||||
CHECK(0 <= sched_id && sched_id < static_cast<int32>(schedulers_.size()));
|
||||
auto guard = schedulers_[sched_id]->get_guard();
|
||||
return schedulers_[sched_id]->create_actor<ActorT>(name, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> register_actor_unsafe(int32 sched_id, Slice name, ActorT *actor) {
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
sched_id = 0;
|
||||
#endif
|
||||
CHECK(0 <= sched_id && sched_id < static_cast<int32>(schedulers_.size()));
|
||||
auto guard = schedulers_[sched_id]->get_guard();
|
||||
return schedulers_[sched_id]->register_actor<ActorT>(name, actor);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State { Start, Run };
|
||||
State state_ = State::Start;
|
||||
std::mutex at_finish_mutex_;
|
||||
vector<std::function<void()>> at_finish_; // can be used during destruction by Scheduler destructors
|
||||
vector<unique_ptr<Scheduler>> schedulers_;
|
||||
std::atomic<bool> is_finished_{false};
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
vector<td::thread> threads_;
|
||||
uint64 thread_affinity_mask_ = 0;
|
||||
#endif
|
||||
#if TD_PORT_WINDOWS
|
||||
unique_ptr<detail::Iocp> iocp_;
|
||||
td::thread iocp_thread_;
|
||||
#endif
|
||||
int32 extra_scheduler_ = 0;
|
||||
|
||||
void on_finish() final;
|
||||
|
||||
void register_at_finish(std::function<void()> f) final;
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
105
td/tdactor/td/actor/MultiPromise.cpp
Normal file
105
td/tdactor/td/actor/MultiPromise.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/MultiPromise.h"
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
void MultiPromiseActor::add_promise(Promise<Unit> &&promise) {
|
||||
promises_.emplace_back(std::move(promise));
|
||||
LOG(DEBUG) << "Add promise #" << promises_.size() << " to " << name_;
|
||||
}
|
||||
|
||||
Promise<Unit> MultiPromiseActor::get_promise() {
|
||||
if (empty()) {
|
||||
register_actor(name_, this).release();
|
||||
}
|
||||
CHECK(!promises_.empty());
|
||||
|
||||
PromiseActor<Unit> promise;
|
||||
FutureActor<Unit> future;
|
||||
init_promise_future(&promise, &future);
|
||||
|
||||
future.set_event(EventCreator::raw(actor_id(), nullptr));
|
||||
futures_.emplace_back(std::move(future));
|
||||
LOG(DEBUG) << "Get promise #" << futures_.size() << " for " << name_;
|
||||
return create_promise_from_promise_actor(std::move(promise));
|
||||
}
|
||||
|
||||
void MultiPromiseActor::raw_event(const Event::Raw &event) {
|
||||
received_results_++;
|
||||
LOG(DEBUG) << "Receive result #" << received_results_ << " out of " << futures_.size() << " for " << name_;
|
||||
if (received_results_ == futures_.size()) {
|
||||
if (!ignore_errors_) {
|
||||
for (auto &future : futures_) {
|
||||
auto result = future.move_as_result();
|
||||
if (result.is_error()) {
|
||||
return set_result(result.move_as_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
return set_result(Unit());
|
||||
}
|
||||
}
|
||||
|
||||
void MultiPromiseActor::set_ignore_errors(bool ignore_errors) {
|
||||
ignore_errors_ = ignore_errors;
|
||||
}
|
||||
|
||||
void MultiPromiseActor::set_result(Result<Unit> &&result) {
|
||||
result_ = std::move(result);
|
||||
stop();
|
||||
}
|
||||
|
||||
void MultiPromiseActor::tear_down() {
|
||||
LOG(DEBUG) << "Set result for " << promises_.size() << " promises in " << name_;
|
||||
|
||||
// MultiPromiseActor should be cleared before it begins to send out result
|
||||
auto promises_copy = std::move(promises_);
|
||||
promises_.clear();
|
||||
auto futures_copy = std::move(futures_);
|
||||
futures_.clear();
|
||||
received_results_ = 0;
|
||||
auto result = std::move(result_);
|
||||
result_ = Unit();
|
||||
|
||||
if (!promises_copy.empty()) {
|
||||
for (size_t i = 0; i + 1 < promises_copy.size(); i++) {
|
||||
promises_copy[i].set_result(result.clone());
|
||||
}
|
||||
promises_copy.back().set_result(std::move(result));
|
||||
}
|
||||
}
|
||||
|
||||
size_t MultiPromiseActor::promise_count() const {
|
||||
return promises_.size();
|
||||
}
|
||||
|
||||
void MultiPromiseActorSafe::add_promise(Promise<Unit> &&promise) {
|
||||
multi_promise_->add_promise(std::move(promise));
|
||||
}
|
||||
|
||||
Promise<Unit> MultiPromiseActorSafe::get_promise() {
|
||||
return multi_promise_->get_promise();
|
||||
}
|
||||
|
||||
void MultiPromiseActorSafe::set_ignore_errors(bool ignore_errors) {
|
||||
multi_promise_->set_ignore_errors(ignore_errors);
|
||||
}
|
||||
|
||||
size_t MultiPromiseActorSafe::promise_count() const {
|
||||
return multi_promise_->promise_count();
|
||||
}
|
||||
|
||||
MultiPromiseActorSafe::~MultiPromiseActorSafe() {
|
||||
if (!multi_promise_->empty()) {
|
||||
register_existing_actor(std::move(multi_promise_)).release();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
120
td/tdactor/td/actor/MultiPromise.h
Normal file
120
td/tdactor/td/actor/MultiPromise.h
Normal file
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
class MultiPromiseInterface {
|
||||
public:
|
||||
virtual void add_promise(Promise<> &&promise) = 0;
|
||||
virtual Promise<> get_promise() = 0;
|
||||
|
||||
virtual size_t promise_count() const = 0;
|
||||
virtual void set_ignore_errors(bool ignore_errors) = 0;
|
||||
|
||||
MultiPromiseInterface() = default;
|
||||
MultiPromiseInterface(const MultiPromiseInterface &) = delete;
|
||||
MultiPromiseInterface &operator=(const MultiPromiseInterface &) = delete;
|
||||
MultiPromiseInterface(MultiPromiseInterface &&) = default;
|
||||
MultiPromiseInterface &operator=(MultiPromiseInterface &&) = default;
|
||||
virtual ~MultiPromiseInterface() = default;
|
||||
};
|
||||
|
||||
class MultiPromise final : public MultiPromiseInterface {
|
||||
public:
|
||||
void add_promise(Promise<> &&promise) final {
|
||||
impl_->add_promise(std::move(promise));
|
||||
}
|
||||
Promise<> get_promise() final {
|
||||
return impl_->get_promise();
|
||||
}
|
||||
|
||||
size_t promise_count() const final {
|
||||
return impl_->promise_count();
|
||||
}
|
||||
void set_ignore_errors(bool ignore_errors) final {
|
||||
impl_->set_ignore_errors(ignore_errors);
|
||||
}
|
||||
|
||||
MultiPromise() = default;
|
||||
explicit MultiPromise(unique_ptr<MultiPromiseInterface> impl) : impl_(std::move(impl)) {
|
||||
}
|
||||
|
||||
private:
|
||||
unique_ptr<MultiPromiseInterface> impl_;
|
||||
};
|
||||
|
||||
class MultiPromiseActor final
|
||||
: public Actor
|
||||
, public MultiPromiseInterface {
|
||||
public:
|
||||
explicit MultiPromiseActor(string name) : name_(std::move(name)) {
|
||||
}
|
||||
|
||||
void add_promise(Promise<Unit> &&promise) final;
|
||||
|
||||
Promise<Unit> get_promise() final;
|
||||
|
||||
void set_ignore_errors(bool ignore_errors) final;
|
||||
|
||||
size_t promise_count() const final;
|
||||
|
||||
private:
|
||||
void set_result(Result<Unit> &&result);
|
||||
|
||||
string name_;
|
||||
vector<Promise<Unit>> promises_; // promises waiting for result
|
||||
vector<FutureActor<Unit>> futures_; // futures waiting for result of the queries
|
||||
size_t received_results_ = 0;
|
||||
bool ignore_errors_ = false;
|
||||
Result<Unit> result_;
|
||||
|
||||
void raw_event(const Event::Raw &event) final;
|
||||
|
||||
void tear_down() final;
|
||||
|
||||
void on_start_migrate(int32) final {
|
||||
UNREACHABLE();
|
||||
}
|
||||
void on_finish_migrate() final {
|
||||
UNREACHABLE();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class ActorTraits<MultiPromiseActor> {
|
||||
public:
|
||||
static constexpr bool need_context = false;
|
||||
static constexpr bool need_start_up = true;
|
||||
};
|
||||
|
||||
class MultiPromiseActorSafe final : public MultiPromiseInterface {
|
||||
public:
|
||||
void add_promise(Promise<Unit> &&promise) final;
|
||||
Promise<Unit> get_promise() final;
|
||||
void set_ignore_errors(bool ignore_errors) final;
|
||||
size_t promise_count() const final;
|
||||
explicit MultiPromiseActorSafe(string name) : multi_promise_(td::make_unique<MultiPromiseActor>(std::move(name))) {
|
||||
}
|
||||
MultiPromiseActorSafe(const MultiPromiseActorSafe &) = delete;
|
||||
MultiPromiseActorSafe &operator=(const MultiPromiseActorSafe &) = delete;
|
||||
MultiPromiseActorSafe(MultiPromiseActorSafe &&) = delete;
|
||||
MultiPromiseActorSafe &operator=(MultiPromiseActorSafe &&) = delete;
|
||||
~MultiPromiseActorSafe() final;
|
||||
|
||||
private:
|
||||
unique_ptr<MultiPromiseActor> multi_promise_;
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
119
td/tdactor/td/actor/MultiTimeout.cpp
Normal file
119
td/tdactor/td/actor/MultiTimeout.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/MultiTimeout.h"
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
bool MultiTimeout::has_timeout(int64 key) const {
|
||||
return items_.count(Item(key)) > 0;
|
||||
}
|
||||
|
||||
void MultiTimeout::set_timeout_at(int64 key, double timeout) {
|
||||
LOG(DEBUG) << "Set " << get_name() << " for " << key << " in " << timeout - Time::now();
|
||||
auto item = items_.emplace(key);
|
||||
auto heap_node = static_cast<HeapNode *>(const_cast<Item *>(&*item.first));
|
||||
if (heap_node->in_heap()) {
|
||||
CHECK(!item.second);
|
||||
bool need_update_timeout = heap_node->is_top();
|
||||
timeout_queue_.fix(timeout, heap_node);
|
||||
if (need_update_timeout || heap_node->is_top()) {
|
||||
update_timeout("set_timeout");
|
||||
}
|
||||
} else {
|
||||
CHECK(item.second);
|
||||
timeout_queue_.insert(timeout, heap_node);
|
||||
if (heap_node->is_top()) {
|
||||
update_timeout("set_timeout 2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiTimeout::add_timeout_at(int64 key, double timeout) {
|
||||
LOG(DEBUG) << "Add " << get_name() << " for " << key << " in " << timeout - Time::now();
|
||||
auto item = items_.emplace(key);
|
||||
auto heap_node = static_cast<HeapNode *>(const_cast<Item *>(&*item.first));
|
||||
if (heap_node->in_heap()) {
|
||||
CHECK(!item.second);
|
||||
} else {
|
||||
CHECK(item.second);
|
||||
timeout_queue_.insert(timeout, heap_node);
|
||||
if (heap_node->is_top()) {
|
||||
update_timeout("add_timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiTimeout::cancel_timeout(int64 key, const char *source) {
|
||||
LOG(DEBUG) << "Cancel " << get_name() << " for " << key;
|
||||
auto item = items_.find(Item(key));
|
||||
if (item != items_.end()) {
|
||||
auto heap_node = static_cast<HeapNode *>(const_cast<Item *>(&*item));
|
||||
CHECK(heap_node->in_heap());
|
||||
bool need_update_timeout = heap_node->is_top();
|
||||
timeout_queue_.erase(heap_node);
|
||||
items_.erase(item);
|
||||
|
||||
if (need_update_timeout) {
|
||||
update_timeout(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiTimeout::update_timeout(const char *source) {
|
||||
if (items_.empty()) {
|
||||
LOG(DEBUG) << "Cancel timeout of " << get_name();
|
||||
LOG_CHECK(timeout_queue_.empty()) << get_name() << ' ' << source;
|
||||
if (!Actor::has_timeout()) {
|
||||
bool has_pending_timeout = false;
|
||||
for (auto &event : get_info()->mailbox_) {
|
||||
if (event.type == Event::Type::Timeout) {
|
||||
has_pending_timeout = true;
|
||||
}
|
||||
}
|
||||
LOG_CHECK(has_pending_timeout) << get_name() << ' ' << get_info()->mailbox_.size() << ' ' << source;
|
||||
} else {
|
||||
Actor::cancel_timeout();
|
||||
}
|
||||
} else {
|
||||
LOG(DEBUG) << "Set timeout of " << get_name() << " in " << timeout_queue_.top_key() - Time::now_cached();
|
||||
Actor::set_timeout_at(timeout_queue_.top_key());
|
||||
}
|
||||
}
|
||||
|
||||
vector<int64> MultiTimeout::get_expired_keys(double now) {
|
||||
vector<int64> expired_keys;
|
||||
while (!timeout_queue_.empty() && timeout_queue_.top_key() < now) {
|
||||
int64 key = static_cast<Item *>(timeout_queue_.pop())->key;
|
||||
items_.erase(Item(key));
|
||||
expired_keys.push_back(key);
|
||||
}
|
||||
return expired_keys;
|
||||
}
|
||||
|
||||
void MultiTimeout::timeout_expired() {
|
||||
vector<int64> expired_keys = get_expired_keys(Time::now_cached());
|
||||
if (!items_.empty()) {
|
||||
update_timeout("timeout_expired");
|
||||
}
|
||||
for (auto key : expired_keys) {
|
||||
callback_(data_, key);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiTimeout::run_all() {
|
||||
vector<int64> expired_keys = get_expired_keys(Time::now_cached() + 1e10);
|
||||
if (!expired_keys.empty()) {
|
||||
update_timeout("run_all");
|
||||
}
|
||||
for (auto key : expired_keys) {
|
||||
callback_(data_, key);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
81
td/tdactor/td/actor/MultiTimeout.h
Normal file
81
td/tdactor/td/actor/MultiTimeout.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace td {
|
||||
|
||||
// TODO optimize
|
||||
class MultiTimeout final : public Actor {
|
||||
struct Item final : public HeapNode {
|
||||
int64 key;
|
||||
|
||||
explicit Item(int64 key) : key(key) {
|
||||
}
|
||||
|
||||
bool operator<(const Item &other) const {
|
||||
return key < other.key;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
using Data = void *;
|
||||
using Callback = void (*)(Data, int64);
|
||||
explicit MultiTimeout(Slice name) {
|
||||
register_actor(name, this).release();
|
||||
}
|
||||
|
||||
void set_callback(Callback callback) {
|
||||
callback_ = callback;
|
||||
}
|
||||
void set_callback_data(Data data) {
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
bool has_timeout(int64 key) const;
|
||||
|
||||
void set_timeout_in(int64 key, double timeout) {
|
||||
set_timeout_at(key, Time::now() + timeout);
|
||||
}
|
||||
|
||||
void add_timeout_in(int64 key, double timeout) {
|
||||
add_timeout_at(key, Time::now() + timeout);
|
||||
}
|
||||
|
||||
void set_timeout_at(int64 key, double timeout);
|
||||
|
||||
void add_timeout_at(int64 key, double timeout); // memcache semantics, doesn't replace old timeout
|
||||
|
||||
void cancel_timeout(int64 key, const char *source = "cancel_timeout");
|
||||
|
||||
void run_all();
|
||||
|
||||
private:
|
||||
friend class Scheduler;
|
||||
|
||||
Callback callback_;
|
||||
Data data_;
|
||||
|
||||
KHeap<double> timeout_queue_;
|
||||
std::set<Item> items_;
|
||||
|
||||
void update_timeout(const char *source);
|
||||
|
||||
void timeout_expired() final;
|
||||
|
||||
vector<int64> get_expired_keys(double now);
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
324
td/tdactor/td/actor/PromiseFuture.h
Normal file
324
td/tdactor/td/actor/PromiseFuture.h
Normal file
@@ -0,0 +1,324 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/Closure.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/invoke.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
namespace detail {
|
||||
class EventPromise final : public PromiseInterface<Unit> {
|
||||
public:
|
||||
void set_value(Unit &&) final {
|
||||
ok_.try_emit();
|
||||
fail_.clear();
|
||||
}
|
||||
void set_error(Status &&) final {
|
||||
do_set_error();
|
||||
}
|
||||
|
||||
EventPromise(const EventPromise &) = delete;
|
||||
EventPromise &operator=(const EventPromise &) = delete;
|
||||
EventPromise(EventPromise &&) = delete;
|
||||
EventPromise &operator=(EventPromise &&) = delete;
|
||||
~EventPromise() final {
|
||||
do_set_error();
|
||||
}
|
||||
|
||||
EventPromise() = default;
|
||||
explicit EventPromise(EventFull ok) : ok_(std::move(ok)), use_ok_as_fail_(true) {
|
||||
}
|
||||
EventPromise(EventFull ok, EventFull fail) : ok_(std::move(ok)), fail_(std::move(fail)), use_ok_as_fail_(false) {
|
||||
}
|
||||
|
||||
private:
|
||||
EventFull ok_;
|
||||
EventFull fail_;
|
||||
bool use_ok_as_fail_ = false;
|
||||
void do_set_error() {
|
||||
if (use_ok_as_fail_) {
|
||||
ok_.try_emit();
|
||||
} else {
|
||||
ok_.clear();
|
||||
fail_.try_emit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SendClosure {
|
||||
public:
|
||||
template <class... ArgsT>
|
||||
void operator()(ArgsT &&...args) const {
|
||||
send_closure(std::forward<ArgsT>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
inline Promise<Unit> create_event_promise(EventFull &&ok) {
|
||||
return Promise<Unit>(td::make_unique<detail::EventPromise>(std::move(ok)));
|
||||
}
|
||||
|
||||
inline Promise<Unit> create_event_promise(EventFull ok, EventFull fail) {
|
||||
return Promise<Unit>(td::make_unique<detail::EventPromise>(std::move(ok), std::move(fail)));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class FutureActor;
|
||||
|
||||
template <class T>
|
||||
class PromiseActor;
|
||||
|
||||
template <class T>
|
||||
class ActorTraits<FutureActor<T>> {
|
||||
public:
|
||||
static constexpr bool need_context = false;
|
||||
static constexpr bool need_start_up = false;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class PromiseActor final : public PromiseInterface<T> {
|
||||
friend class FutureActor<T>;
|
||||
enum State { Waiting, Hangup };
|
||||
|
||||
public:
|
||||
PromiseActor() = default;
|
||||
PromiseActor(const PromiseActor &) = delete;
|
||||
PromiseActor &operator=(const PromiseActor &) = delete;
|
||||
PromiseActor(PromiseActor &&) = default;
|
||||
PromiseActor &operator=(PromiseActor &&) = default;
|
||||
~PromiseActor() final {
|
||||
close();
|
||||
}
|
||||
|
||||
void set_value(T &&value) final;
|
||||
void set_error(Status &&error) final;
|
||||
|
||||
void close() {
|
||||
future_id_.reset();
|
||||
}
|
||||
|
||||
// NB: if true is returned no further events will be sent
|
||||
bool is_hangup() {
|
||||
if (state_ == State::Hangup) {
|
||||
return true;
|
||||
}
|
||||
if (!future_id_.is_alive()) {
|
||||
state_ = State::Hangup;
|
||||
future_id_.release();
|
||||
event_.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
friend void init_promise_future(PromiseActor<S> *promise, FutureActor<S> *future);
|
||||
|
||||
bool empty_promise() {
|
||||
return future_id_.empty();
|
||||
}
|
||||
bool empty() {
|
||||
return future_id_.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
ActorOwn<FutureActor<T>> future_id_;
|
||||
EventFull event_;
|
||||
State state_ = State::Hangup;
|
||||
|
||||
void init() {
|
||||
state_ = State::Waiting;
|
||||
event_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class FutureActor final : public Actor {
|
||||
friend class PromiseActor<T>;
|
||||
|
||||
public:
|
||||
enum State { Waiting, Ready };
|
||||
|
||||
static constexpr int HANGUP_ERROR_CODE = 426487;
|
||||
|
||||
FutureActor() = default;
|
||||
|
||||
FutureActor(const FutureActor &) = delete;
|
||||
FutureActor &operator=(const FutureActor &) = delete;
|
||||
|
||||
FutureActor(FutureActor &&) = default;
|
||||
FutureActor &operator=(FutureActor &&) = default;
|
||||
|
||||
~FutureActor() final = default;
|
||||
|
||||
bool is_ok() const {
|
||||
return is_ready() && result_.is_ok();
|
||||
}
|
||||
bool is_error() const {
|
||||
CHECK(is_ready());
|
||||
return is_ready() && result_.is_error();
|
||||
}
|
||||
T move_as_ok() {
|
||||
return move_as_result().move_as_ok();
|
||||
}
|
||||
Status move_as_error() TD_WARN_UNUSED_RESULT {
|
||||
return move_as_result().move_as_error();
|
||||
}
|
||||
Result<T> move_as_result() TD_WARN_UNUSED_RESULT {
|
||||
CHECK(is_ready());
|
||||
SCOPE_EXIT {
|
||||
do_stop();
|
||||
};
|
||||
return std::move(result_);
|
||||
}
|
||||
bool is_ready() const {
|
||||
return !empty() && state_ == State::Ready;
|
||||
}
|
||||
|
||||
void close() {
|
||||
event_.clear();
|
||||
result_.clear();
|
||||
do_stop();
|
||||
}
|
||||
|
||||
void set_event(EventFull &&event) {
|
||||
CHECK(!empty());
|
||||
event_ = std::move(event);
|
||||
if (state_ != State::Waiting) {
|
||||
event_.try_emit_later();
|
||||
}
|
||||
}
|
||||
|
||||
State get_state() const {
|
||||
return state_;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
friend void init_promise_future(PromiseActor<S> *promise, FutureActor<S> *future);
|
||||
|
||||
private:
|
||||
EventFull event_;
|
||||
Result<T> result_ = Status::Error(500, "Empty FutureActor");
|
||||
State state_ = State::Waiting;
|
||||
|
||||
void set_value(T &&value) {
|
||||
set_result(std::move(value));
|
||||
}
|
||||
|
||||
void set_error(Status &&error) {
|
||||
set_result(std::move(error));
|
||||
}
|
||||
|
||||
void set_result(Result<T> &&result) {
|
||||
CHECK(state_ == State::Waiting);
|
||||
result_ = std::move(result);
|
||||
state_ = State::Ready;
|
||||
|
||||
event_.try_emit_later();
|
||||
}
|
||||
|
||||
void hangup() final {
|
||||
set_error(Status::Error<HANGUP_ERROR_CODE>());
|
||||
}
|
||||
|
||||
void start_up() final {
|
||||
// empty
|
||||
}
|
||||
|
||||
void init() {
|
||||
CHECK(empty());
|
||||
state_ = State::Waiting;
|
||||
event_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void PromiseActor<T>::set_value(T &&value) {
|
||||
if (state_ == State::Waiting && !future_id_.empty()) {
|
||||
send_closure(std::move(future_id_), &FutureActor<T>::set_value, std::move(value));
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
void PromiseActor<T>::set_error(Status &&error) {
|
||||
if (state_ == State::Waiting && !future_id_.empty()) {
|
||||
send_closure(std::move(future_id_), &FutureActor<T>::set_error, std::move(error));
|
||||
}
|
||||
}
|
||||
|
||||
template <class S>
|
||||
void init_promise_future(PromiseActor<S> *promise, FutureActor<S> *future) {
|
||||
promise->init();
|
||||
future->init();
|
||||
promise->future_id_ = register_actor("FutureActor", future);
|
||||
|
||||
CHECK(future->get_info() != nullptr);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
class PromiseFuture {
|
||||
public:
|
||||
PromiseFuture() {
|
||||
init_promise_future(&promise_, &future_);
|
||||
}
|
||||
PromiseActor<T> &promise() {
|
||||
return promise_;
|
||||
}
|
||||
FutureActor<T> &future() {
|
||||
return future_;
|
||||
}
|
||||
PromiseActor<T> &&move_promise() {
|
||||
return std::move(promise_);
|
||||
}
|
||||
FutureActor<T> &&move_future() {
|
||||
return std::move(future_);
|
||||
}
|
||||
|
||||
private:
|
||||
PromiseActor<T> promise_;
|
||||
FutureActor<T> future_;
|
||||
};
|
||||
|
||||
template <class T, class ActorAT, class ActorBT, class ResultT, class... DestArgsT, class... ArgsT>
|
||||
FutureActor<T> send_promise_immediately(ActorId<ActorAT> actor_id,
|
||||
ResultT (ActorBT::*func)(PromiseActor<T> &&, DestArgsT...), ArgsT &&...args) {
|
||||
PromiseFuture<T> pf;
|
||||
Scheduler::instance()->send_closure_immediately(
|
||||
std::move(actor_id), create_immediate_closure(func, pf.move_promise(), std::forward<ArgsT>(args)...));
|
||||
return pf.move_future();
|
||||
}
|
||||
|
||||
template <class T, class ActorAT, class ActorBT, class ResultT, class... DestArgsT, class... ArgsT>
|
||||
FutureActor<T> send_promise_later(ActorId<ActorAT> actor_id, ResultT (ActorBT::*func)(PromiseActor<T> &&, DestArgsT...),
|
||||
ArgsT &&...args) {
|
||||
PromiseFuture<T> pf;
|
||||
Scheduler::instance()->send_closure_later(
|
||||
std::move(actor_id), create_immediate_closure(func, pf.move_promise(), std::forward<ArgsT>(args)...));
|
||||
return pf.move_future();
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
auto promise_send_closure(ArgsT &&...args) {
|
||||
return [t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&res) mutable {
|
||||
call_tuple(detail::SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::forward<decltype(res)>(res))));
|
||||
};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Promise<T> create_promise_from_promise_actor(PromiseActor<T> &&from) {
|
||||
return Promise<T>(td::make_unique<PromiseActor<T>>(std::move(from)));
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
81
td/tdactor/td/actor/SchedulerLocalStorage.h
Normal file
81
td/tdactor/td/actor/SchedulerLocalStorage.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/optional.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace td {
|
||||
|
||||
template <class T>
|
||||
class SchedulerLocalStorage {
|
||||
public:
|
||||
SchedulerLocalStorage() : data_(Scheduler::instance()->sched_count()) {
|
||||
}
|
||||
T &get() {
|
||||
return data_[Scheduler::instance()->sched_id()];
|
||||
}
|
||||
template <class F>
|
||||
void for_each(F &&f) {
|
||||
for (auto &value : data_) {
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
template <class F>
|
||||
void for_each(F &&f) const {
|
||||
for (const auto &value : data_) {
|
||||
f(value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> data_;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class LazySchedulerLocalStorage {
|
||||
public:
|
||||
LazySchedulerLocalStorage() = default;
|
||||
explicit LazySchedulerLocalStorage(std::function<T()> create_func) : create_func_(std::move(create_func)) {
|
||||
}
|
||||
void set_create_func(std::function<T()> create_func) {
|
||||
CHECK(!create_func_);
|
||||
create_func_ = create_func;
|
||||
}
|
||||
|
||||
void set(T &&t) {
|
||||
auto &optional_value_ = sls_optional_value_.get();
|
||||
CHECK(!optional_value_);
|
||||
optional_value_ = std::move(t);
|
||||
}
|
||||
|
||||
T &get() {
|
||||
auto &optional_value_ = sls_optional_value_.get();
|
||||
if (!optional_value_) {
|
||||
CHECK(create_func_);
|
||||
optional_value_ = create_func_();
|
||||
}
|
||||
return *optional_value_;
|
||||
}
|
||||
void clear_values() {
|
||||
sls_optional_value_.for_each([&](auto &optional_value) {
|
||||
if (optional_value) {
|
||||
optional_value = optional<T>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<T()> create_func_;
|
||||
SchedulerLocalStorage<optional<T>> sls_optional_value_;
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
112
td/tdactor/td/actor/SignalSlot.h
Normal file
112
td/tdactor/td/actor/SignalSlot.h
Normal file
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
class Slot;
|
||||
|
||||
class Signal {
|
||||
public:
|
||||
void emit();
|
||||
|
||||
explicit Signal(ActorId<Slot> slot_id) : slot_id_(std::move(slot_id)) {
|
||||
}
|
||||
|
||||
private:
|
||||
ActorId<Slot> slot_id_;
|
||||
};
|
||||
|
||||
class Slot final : public Actor {
|
||||
public:
|
||||
Slot() = default;
|
||||
Slot(const Slot &) = delete;
|
||||
Slot &operator=(const Slot &) = delete;
|
||||
Slot(Slot &&) = default;
|
||||
Slot &operator=(Slot &&) = default;
|
||||
~Slot() final {
|
||||
close();
|
||||
}
|
||||
void set_event(EventFull &&event) {
|
||||
was_signal_ = false;
|
||||
event_ = std::move(event);
|
||||
}
|
||||
|
||||
bool has_event() {
|
||||
return !event_.empty();
|
||||
}
|
||||
|
||||
bool was_signal() {
|
||||
return was_signal_;
|
||||
}
|
||||
|
||||
void clear_event() {
|
||||
event_.clear();
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!empty()) {
|
||||
do_stop();
|
||||
}
|
||||
}
|
||||
|
||||
void set_timeout_in(double timeout_in) {
|
||||
register_if_empty();
|
||||
Actor::set_timeout_in(timeout_in);
|
||||
}
|
||||
void set_timeout_at(double timeout_at) {
|
||||
register_if_empty();
|
||||
Actor::set_timeout_at(timeout_at);
|
||||
}
|
||||
|
||||
friend class Signal;
|
||||
Signal get_signal() {
|
||||
register_if_empty();
|
||||
return Signal(actor_id(this));
|
||||
}
|
||||
ActorShared<> get_signal_new() {
|
||||
register_if_empty();
|
||||
return actor_shared(this);
|
||||
}
|
||||
|
||||
private:
|
||||
bool was_signal_ = false;
|
||||
EventFull event_;
|
||||
|
||||
void timeout_expired() final {
|
||||
signal();
|
||||
}
|
||||
|
||||
void start_up() final {
|
||||
empty();
|
||||
}
|
||||
|
||||
void register_if_empty() {
|
||||
if (empty()) {
|
||||
register_actor("Slot", this).release();
|
||||
}
|
||||
}
|
||||
|
||||
// send event only once
|
||||
void signal() {
|
||||
if (!was_signal_) {
|
||||
was_signal_ = true;
|
||||
event_.try_emit_later();
|
||||
}
|
||||
}
|
||||
void hangup_shared() final {
|
||||
signal();
|
||||
}
|
||||
};
|
||||
|
||||
inline void Signal::emit() {
|
||||
send_closure(slot_id_, &Slot::signal);
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
41
td/tdactor/td/actor/SleepActor.h
Normal file
41
td/tdactor/td/actor/SleepActor.h
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Promise.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
class SleepActor final : public Actor {
|
||||
public:
|
||||
SleepActor(double timeout, Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
|
||||
}
|
||||
|
||||
private:
|
||||
double timeout_;
|
||||
Promise<> promise_;
|
||||
|
||||
void start_up() final {
|
||||
set_timeout_in(timeout_);
|
||||
}
|
||||
void timeout_expired() final {
|
||||
promise_.set_value(Unit());
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class ActorTraits<SleepActor> {
|
||||
public:
|
||||
static constexpr bool need_context = false;
|
||||
static constexpr bool need_start_up = true;
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
68
td/tdactor/td/actor/Timeout.h
Normal file
68
td/tdactor/td/actor/Timeout.h
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
class Timeout final : public Actor {
|
||||
public:
|
||||
using Data = void *;
|
||||
using Callback = void (*)(Data);
|
||||
Timeout() {
|
||||
register_actor("Timeout", this).release();
|
||||
}
|
||||
|
||||
void set_callback(Callback callback) {
|
||||
callback_ = callback;
|
||||
}
|
||||
void set_callback_data(Data &&data) {
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
bool has_timeout() const {
|
||||
return Actor::has_timeout();
|
||||
}
|
||||
double get_timeout() const {
|
||||
return Actor::get_timeout();
|
||||
}
|
||||
void set_timeout_in(double timeout) {
|
||||
Actor::set_timeout_in(timeout);
|
||||
}
|
||||
void set_timeout_at(double timeout) {
|
||||
Actor::set_timeout_at(timeout);
|
||||
}
|
||||
void cancel_timeout() {
|
||||
if (has_timeout()) {
|
||||
Actor::cancel_timeout();
|
||||
callback_ = Callback();
|
||||
data_ = Data();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Scheduler;
|
||||
|
||||
Callback callback_{};
|
||||
Data data_{};
|
||||
|
||||
void timeout_expired() final {
|
||||
CHECK(!has_timeout());
|
||||
CHECK(callback_ != Callback());
|
||||
Callback callback = callback_;
|
||||
Data data = data_;
|
||||
callback_ = Callback();
|
||||
data_ = Data();
|
||||
|
||||
callback(data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
13
td/tdactor/td/actor/actor.h
Normal file
13
td/tdactor/td/actor/actor.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/Actor.h"
|
||||
#include "td/actor/impl/ActorId.h"
|
||||
#include "td/actor/impl/ActorInfo.h"
|
||||
#include "td/actor/impl/EventFull.h"
|
||||
#include "td/actor/impl/Scheduler.h"
|
||||
121
td/tdactor/td/actor/impl/Actor-decl.h
Normal file
121
td/tdactor/td/actor/impl/Actor-decl.h
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/ActorId-decl.h"
|
||||
#include "td/actor/impl/ActorInfo-decl.h"
|
||||
#include "td/actor/impl/Event.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/Observer.h"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace td {
|
||||
|
||||
class Actor : public ObserverBase {
|
||||
public:
|
||||
using Deleter = ActorInfo::Deleter;
|
||||
Actor() = default;
|
||||
Actor(const Actor &) = delete;
|
||||
Actor &operator=(const Actor &) = delete;
|
||||
Actor(Actor &&other) noexcept;
|
||||
Actor &operator=(Actor &&other) noexcept;
|
||||
~Actor() override {
|
||||
if (!empty()) {
|
||||
do_stop();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void start_up() {
|
||||
yield();
|
||||
}
|
||||
virtual void tear_down() {
|
||||
}
|
||||
virtual void wakeup() {
|
||||
loop();
|
||||
}
|
||||
virtual void hangup() {
|
||||
stop();
|
||||
}
|
||||
virtual void hangup_shared() {
|
||||
// ignore
|
||||
}
|
||||
virtual void timeout_expired() {
|
||||
loop();
|
||||
}
|
||||
virtual void raw_event(const Event::Raw &event) {
|
||||
}
|
||||
virtual void loop() {
|
||||
}
|
||||
|
||||
// TODO: not called in events. Can't use stop, or migrate inside of them
|
||||
virtual void on_start_migrate(int32 sched_id) {
|
||||
}
|
||||
virtual void on_finish_migrate() {
|
||||
}
|
||||
|
||||
void notify() override;
|
||||
|
||||
// proxy to scheduler
|
||||
void yield();
|
||||
void stop();
|
||||
void do_stop();
|
||||
bool has_timeout() const;
|
||||
double get_timeout() const;
|
||||
void set_timeout_in(double timeout_in);
|
||||
void set_timeout_at(double timeout_at);
|
||||
void cancel_timeout();
|
||||
void migrate(int32 sched_id);
|
||||
void do_migrate(int32 sched_id);
|
||||
|
||||
uint64 get_link_token();
|
||||
std::weak_ptr<ActorContext> get_context_weak_ptr() const;
|
||||
std::shared_ptr<ActorContext> set_context(std::shared_ptr<ActorContext> context);
|
||||
string set_tag(string tag);
|
||||
|
||||
// for ActorInfo mostly
|
||||
void set_info(ObjectPool<ActorInfo>::OwnerPtr &&info);
|
||||
ActorInfo *get_info();
|
||||
const ActorInfo *get_info() const;
|
||||
ObjectPool<ActorInfo>::OwnerPtr clear();
|
||||
|
||||
bool empty() const;
|
||||
|
||||
template <class FuncT, class... ArgsT>
|
||||
auto self_closure(FuncT &&func, ArgsT &&...args);
|
||||
|
||||
template <class SelfT, class FuncT, class... ArgsT>
|
||||
auto self_closure(SelfT *self, FuncT &&func, ArgsT &&...args);
|
||||
|
||||
template <class LambdaT>
|
||||
auto self_lambda(LambdaT &&func);
|
||||
|
||||
// proxy to info_
|
||||
ActorId<> actor_id();
|
||||
template <class SelfT>
|
||||
ActorId<SelfT> actor_id(SelfT *self);
|
||||
|
||||
template <class SelfT>
|
||||
ActorShared<SelfT> actor_shared(SelfT *self, uint64 id = static_cast<uint64>(-1));
|
||||
|
||||
Slice get_name() const;
|
||||
|
||||
private:
|
||||
ObjectPool<ActorInfo>::OwnerPtr info_;
|
||||
};
|
||||
|
||||
template <class ActorT>
|
||||
class ActorTraits {
|
||||
public:
|
||||
static constexpr bool need_context = true;
|
||||
static constexpr bool need_start_up = true;
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
167
td/tdactor/td/actor/impl/Actor.h
Normal file
167
td/tdactor/td/actor/impl/Actor.h
Normal file
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/Actor-decl.h"
|
||||
#include "td/actor/impl/EventFull-decl.h"
|
||||
#include "td/actor/impl/Scheduler-decl.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
inline Actor::Actor(Actor &&other) noexcept {
|
||||
CHECK(info_.empty());
|
||||
info_ = std::move(other.info_);
|
||||
if (!empty()) {
|
||||
info_->on_actor_moved(this);
|
||||
}
|
||||
}
|
||||
inline Actor &Actor::operator=(Actor &&other) noexcept {
|
||||
CHECK(info_.empty());
|
||||
info_ = std::move(other.info_);
|
||||
if (!empty()) {
|
||||
info_->on_actor_moved(this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void Actor::notify() {
|
||||
yield();
|
||||
}
|
||||
|
||||
// proxy to scheduler
|
||||
inline void Actor::yield() {
|
||||
Scheduler::instance()->yield_actor(this);
|
||||
}
|
||||
inline void Actor::stop() {
|
||||
Scheduler::instance()->stop_actor(this);
|
||||
}
|
||||
inline void Actor::do_stop() {
|
||||
Scheduler::instance()->do_stop_actor(this);
|
||||
CHECK(empty());
|
||||
}
|
||||
inline bool Actor::has_timeout() const {
|
||||
return get_info()->get_heap_node()->in_heap();
|
||||
}
|
||||
inline double Actor::get_timeout() const {
|
||||
return Scheduler::instance()->get_actor_timeout(this);
|
||||
}
|
||||
inline void Actor::set_timeout_in(double timeout_in) {
|
||||
Scheduler::instance()->set_actor_timeout_in(this, timeout_in);
|
||||
}
|
||||
inline void Actor::set_timeout_at(double timeout_at) {
|
||||
Scheduler::instance()->set_actor_timeout_at(this, timeout_at);
|
||||
}
|
||||
inline void Actor::cancel_timeout() {
|
||||
Scheduler::instance()->cancel_actor_timeout(this);
|
||||
}
|
||||
inline void Actor::migrate(int32 sched_id) {
|
||||
Scheduler::instance()->migrate_actor(this, sched_id);
|
||||
}
|
||||
inline void Actor::do_migrate(int32 sched_id) {
|
||||
Scheduler::instance()->do_migrate_actor(this, sched_id);
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
std::enable_if_t<std::is_base_of<Actor, ActorType>::value> start_migrate(ActorType &obj, int32 sched_id) {
|
||||
if (!obj.empty()) {
|
||||
Scheduler::instance()->start_migrate_actor(&obj, sched_id);
|
||||
}
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
std::enable_if_t<std::is_base_of<Actor, ActorType>::value> finish_migrate(ActorType &obj) {
|
||||
if (!obj.empty()) {
|
||||
Scheduler::instance()->finish_migrate_actor(&obj);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint64 Actor::get_link_token() {
|
||||
return Scheduler::instance()->get_link_token(this);
|
||||
}
|
||||
|
||||
inline std::weak_ptr<ActorContext> Actor::get_context_weak_ptr() const {
|
||||
return info_->get_context_weak_ptr();
|
||||
}
|
||||
|
||||
inline std::shared_ptr<ActorContext> Actor::set_context(std::shared_ptr<ActorContext> context) {
|
||||
return info_->set_context(std::move(context));
|
||||
}
|
||||
|
||||
inline string Actor::set_tag(string tag) {
|
||||
auto *ctx = info_->get_context();
|
||||
string old_tag;
|
||||
if (ctx->tag_) {
|
||||
old_tag = ctx->tag_;
|
||||
}
|
||||
ctx->set_tag(std::move(tag));
|
||||
Scheduler::on_context_updated();
|
||||
return old_tag;
|
||||
}
|
||||
|
||||
inline void Actor::set_info(ObjectPool<ActorInfo>::OwnerPtr &&info) {
|
||||
info_ = std::move(info);
|
||||
}
|
||||
|
||||
inline ActorInfo *Actor::get_info() {
|
||||
return &*info_;
|
||||
}
|
||||
inline const ActorInfo *Actor::get_info() const {
|
||||
return &*info_;
|
||||
}
|
||||
|
||||
inline ObjectPool<ActorInfo>::OwnerPtr Actor::clear() {
|
||||
return std::move(info_);
|
||||
}
|
||||
|
||||
inline bool Actor::empty() const {
|
||||
return info_.empty();
|
||||
}
|
||||
|
||||
inline ActorId<> Actor::actor_id() {
|
||||
return actor_id(this);
|
||||
}
|
||||
template <class SelfT>
|
||||
ActorId<SelfT> Actor::actor_id(SelfT *self) {
|
||||
CHECK(static_cast<Actor *>(self) == this);
|
||||
return ActorId<SelfT>(info_.get_weak());
|
||||
}
|
||||
|
||||
template <class SelfT>
|
||||
ActorShared<SelfT> Actor::actor_shared(SelfT *self, uint64 id) {
|
||||
CHECK(static_cast<Actor *>(self) == this);
|
||||
CHECK(id != 0);
|
||||
return ActorShared<SelfT>(actor_id(self), id);
|
||||
}
|
||||
|
||||
template <class FuncT, class... ArgsT>
|
||||
auto Actor::self_closure(FuncT &&func, ArgsT &&...args) {
|
||||
return self_closure(this, std::forward<FuncT>(func), std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class SelfT, class FuncT, class... ArgsT>
|
||||
auto Actor::self_closure(SelfT *self, FuncT &&func, ArgsT &&...args) {
|
||||
return EventCreator::closure(actor_id(self), std::forward<FuncT>(func), std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class LambdaT>
|
||||
auto Actor::self_lambda(LambdaT &&func) {
|
||||
return EventCreator::from_lambda(actor_id(), std::forward<LambdaT>(func));
|
||||
}
|
||||
|
||||
inline Slice Actor::get_name() const {
|
||||
return info_->get_name();
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
159
td/tdactor/td/actor/impl/ActorId-decl.h
Normal file
159
td/tdactor/td/actor/impl/ActorId-decl.h
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace td {
|
||||
|
||||
class Actor;
|
||||
class ActorInfo;
|
||||
|
||||
template <class ActorType = Actor>
|
||||
class ActorId {
|
||||
public:
|
||||
using ActorT = ActorType;
|
||||
explicit ActorId(ObjectPool<ActorInfo>::WeakPtr ptr) : ptr_(ptr) {
|
||||
}
|
||||
ActorId() = default;
|
||||
ActorId(const ActorId &) = default;
|
||||
ActorId &operator=(const ActorId &) = default;
|
||||
ActorId(ActorId &&other) noexcept : ptr_(other.ptr_) {
|
||||
other.clear();
|
||||
}
|
||||
ActorId &operator=(ActorId &&other) noexcept {
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
ptr_ = other.ptr_;
|
||||
other.clear();
|
||||
return *this;
|
||||
}
|
||||
~ActorId() = default;
|
||||
|
||||
bool empty() const {
|
||||
return ptr_.empty();
|
||||
}
|
||||
void clear() {
|
||||
ptr_.clear();
|
||||
}
|
||||
|
||||
bool is_alive() const {
|
||||
return ptr_.is_alive_unsafe();
|
||||
}
|
||||
|
||||
ActorInfo *get_actor_info() const;
|
||||
|
||||
ActorType *get_actor_unsafe() const;
|
||||
|
||||
Slice get_name() const;
|
||||
|
||||
template <class ToActorType, class = std::enable_if_t<std::is_base_of<ToActorType, ActorType>::value>>
|
||||
explicit operator ActorId<ToActorType>() const {
|
||||
return ActorId<ToActorType>(ptr_);
|
||||
}
|
||||
|
||||
private:
|
||||
ObjectPool<ActorInfo>::WeakPtr ptr_;
|
||||
};
|
||||
|
||||
// treat ActorId as pointer and ActorOwn as unique_ptr<ActorId>
|
||||
template <class ActorType = Actor>
|
||||
class ActorOwn {
|
||||
public:
|
||||
using ActorT = ActorType;
|
||||
ActorOwn() = default;
|
||||
explicit ActorOwn(ActorId<ActorType> id);
|
||||
template <class OtherActorType>
|
||||
explicit ActorOwn(ActorId<OtherActorType> id);
|
||||
template <class OtherActorType>
|
||||
explicit ActorOwn(ActorOwn<OtherActorType> &&other);
|
||||
template <class OtherActorType>
|
||||
ActorOwn &operator=(ActorOwn<OtherActorType> &&other);
|
||||
ActorOwn(ActorOwn &&other) noexcept;
|
||||
ActorOwn &operator=(ActorOwn &&other) noexcept;
|
||||
ActorOwn(const ActorOwn &) = delete;
|
||||
ActorOwn &operator=(const ActorOwn &) = delete;
|
||||
~ActorOwn();
|
||||
|
||||
bool empty() const;
|
||||
bool is_alive() const {
|
||||
return id_.is_alive();
|
||||
}
|
||||
ActorId<ActorType> get() const;
|
||||
ActorId<ActorType> release();
|
||||
void reset(ActorId<ActorType> other = ActorId<ActorType>());
|
||||
ActorType *get_actor_unsafe() const;
|
||||
|
||||
private:
|
||||
ActorId<ActorType> id_;
|
||||
};
|
||||
|
||||
template <class ActorType = Actor>
|
||||
class ActorShared {
|
||||
public:
|
||||
using ActorT = ActorType;
|
||||
ActorShared() = default;
|
||||
template <class OtherActorType>
|
||||
ActorShared(ActorId<OtherActorType> id, uint64 token);
|
||||
template <class OtherActorType>
|
||||
ActorShared(ActorShared<OtherActorType> &&other);
|
||||
template <class OtherActorType>
|
||||
ActorShared(ActorOwn<OtherActorType> &&other);
|
||||
template <class OtherActorType>
|
||||
ActorShared &operator=(ActorShared<OtherActorType> &&other);
|
||||
ActorShared(ActorShared &&other) noexcept;
|
||||
ActorShared &operator=(ActorShared &&other) noexcept;
|
||||
ActorShared(const ActorShared &) = delete;
|
||||
ActorShared &operator=(const ActorShared &) = delete;
|
||||
~ActorShared();
|
||||
|
||||
uint64 token() const;
|
||||
bool empty() const;
|
||||
bool is_alive() const {
|
||||
return id_.is_alive();
|
||||
}
|
||||
ActorId<ActorType> get() const;
|
||||
ActorId<ActorType> release();
|
||||
void reset(ActorId<ActorType> other = ActorId<ActorType>());
|
||||
|
||||
private:
|
||||
ActorId<ActorType> id_;
|
||||
uint64 token_ = 0;
|
||||
};
|
||||
|
||||
class ActorRef {
|
||||
public:
|
||||
ActorRef() = default;
|
||||
template <class T>
|
||||
ActorRef(const ActorId<T> &actor_id);
|
||||
template <class T>
|
||||
ActorRef(ActorId<T> &&actor_id);
|
||||
template <class T>
|
||||
ActorRef(const ActorShared<T> &actor_id);
|
||||
template <class T>
|
||||
ActorRef(ActorShared<T> &&actor_id);
|
||||
template <class T>
|
||||
ActorRef(const ActorOwn<T> &actor_id);
|
||||
template <class T>
|
||||
ActorRef(ActorOwn<T> &&actor_id);
|
||||
ActorId<> get() const {
|
||||
return actor_id_;
|
||||
}
|
||||
uint64 token() const {
|
||||
return token_;
|
||||
}
|
||||
|
||||
private:
|
||||
ActorId<> actor_id_;
|
||||
uint64 token_ = 0;
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
195
td/tdactor/td/actor/impl/ActorId.h
Normal file
195
td/tdactor/td/actor/impl/ActorId.h
Normal file
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/ActorId-decl.h"
|
||||
#include "td/actor/impl/ActorInfo-decl.h"
|
||||
#include "td/actor/impl/Scheduler-decl.h"
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
// If actor is on our scheduler(thread) result will be valid
|
||||
// If actor is on another scheduler we will see it in migrate_dest_flags
|
||||
template <class ActorType>
|
||||
ActorInfo *ActorId<ActorType>::get_actor_info() const {
|
||||
if (ptr_.is_alive()) {
|
||||
return &*ptr_;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorType *ActorId<ActorType>::get_actor_unsafe() const {
|
||||
return static_cast<ActorType *>(ptr_->get_actor_unsafe());
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
Slice ActorId<ActorType>::get_name() const {
|
||||
return ptr_->get_name();
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorOwn<ActorType>::ActorOwn(ActorId<ActorType> id) : id_(std::move(id)) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorOwn<ActorType>::ActorOwn(ActorId<OtherActorType> id) : id_(std::move(id)) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorOwn<ActorType>::ActorOwn(ActorOwn<OtherActorType> &&other) : id_(other.release()) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorOwn<ActorType> &ActorOwn<ActorType>::operator=(ActorOwn<OtherActorType> &&other) {
|
||||
reset(static_cast<ActorId<ActorType>>(other.release()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorOwn<ActorType>::ActorOwn(ActorOwn &&other) noexcept : id_(other.release()) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorOwn<ActorType> &ActorOwn<ActorType>::operator=(ActorOwn &&other) noexcept {
|
||||
reset(other.release());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorOwn<ActorType>::~ActorOwn() {
|
||||
reset();
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
bool ActorOwn<ActorType>::empty() const {
|
||||
return id_.empty();
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorId<ActorType> ActorOwn<ActorType>::get() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorId<ActorType> ActorOwn<ActorType>::release() {
|
||||
return std::move(id_);
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
void ActorOwn<ActorType>::reset(ActorId<ActorType> other) {
|
||||
static_assert(sizeof(ActorType) > 0, "Can't use ActorOwn with incomplete type");
|
||||
if (!id_.empty()) {
|
||||
send_event(id_, Event::hangup());
|
||||
}
|
||||
id_ = std::move(other);
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorType *ActorOwn<ActorType>::get_actor_unsafe() const {
|
||||
return id_.get_actor_unsafe();
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorShared<ActorType>::ActorShared(ActorId<OtherActorType> id, uint64 token) : id_(std::move(id)), token_(token) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorShared<ActorType>::ActorShared(ActorShared<OtherActorType> &&other) : id_(other.release()), token_(other.token()) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorShared<ActorType>::ActorShared(ActorOwn<OtherActorType> &&other) : id_(other.release()), token_(0) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
template <class OtherActorType>
|
||||
ActorShared<ActorType> &ActorShared<ActorType>::operator=(ActorShared<OtherActorType> &&other) {
|
||||
reset(other.release());
|
||||
token_ = other.token();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorShared<ActorType>::ActorShared(ActorShared &&other) noexcept : id_(other.release()), token_(other.token_) {
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorShared<ActorType> &ActorShared<ActorType>::operator=(ActorShared &&other) noexcept {
|
||||
reset(other.release());
|
||||
token_ = other.token_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorShared<ActorType>::~ActorShared() {
|
||||
reset();
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
uint64 ActorShared<ActorType>::token() const {
|
||||
return token_;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
bool ActorShared<ActorType>::empty() const {
|
||||
return id_.empty();
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorId<ActorType> ActorShared<ActorType>::get() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
ActorId<ActorType> ActorShared<ActorType>::release() {
|
||||
return std::move(id_);
|
||||
}
|
||||
|
||||
template <class ActorType>
|
||||
void ActorShared<ActorType>::reset(ActorId<ActorType> other) {
|
||||
static_assert(sizeof(ActorType) > 0, "Can't use ActorShared with incomplete type");
|
||||
if (!id_.empty()) {
|
||||
send_event(*this, Event::hangup());
|
||||
}
|
||||
id_ = std::move(other);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
ActorRef::ActorRef(const ActorId<T> &actor_id) : actor_id_(actor_id) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
ActorRef::ActorRef(ActorId<T> &&actor_id) : actor_id_(actor_id) {
|
||||
actor_id.clear();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
ActorRef::ActorRef(const ActorShared<T> &actor_id) : actor_id_(actor_id.get()), token_(actor_id.token()) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
ActorRef::ActorRef(ActorShared<T> &&actor_id) : actor_id_(actor_id.release()), token_(actor_id.token()) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
ActorRef::ActorRef(const ActorOwn<T> &actor_id) : actor_id_(actor_id.get()) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
ActorRef::ActorRef(ActorOwn<T> &&actor_id) : actor_id_(actor_id.release()) {
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
128
td/tdactor/td/actor/impl/ActorInfo-decl.h
Normal file
128
td/tdactor/td/actor/impl/ActorInfo-decl.h
Normal file
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/ActorId-decl.h"
|
||||
#include "td/actor/impl/Event.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
class Actor;
|
||||
|
||||
class ActorContext {
|
||||
public:
|
||||
ActorContext() = default;
|
||||
ActorContext(const ActorContext &) = delete;
|
||||
ActorContext &operator=(const ActorContext &) = delete;
|
||||
ActorContext(ActorContext &&) = delete;
|
||||
ActorContext &operator=(ActorContext &&) = delete;
|
||||
virtual ~ActorContext() = default;
|
||||
|
||||
virtual int32 get_id() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_tag(string tag) {
|
||||
tag_storage_ = std::move(tag);
|
||||
tag_ = tag_storage_.c_str();
|
||||
}
|
||||
|
||||
const char *tag_ = nullptr;
|
||||
string tag_storage_; // sometimes tag_ == tag_storage_.c_str()
|
||||
std::weak_ptr<ActorContext> this_ptr_;
|
||||
};
|
||||
|
||||
class ActorInfo final
|
||||
: private ListNode
|
||||
, private HeapNode {
|
||||
public:
|
||||
enum class Deleter : uint8 { Destroy, None };
|
||||
|
||||
ActorInfo() = default;
|
||||
~ActorInfo() = default;
|
||||
|
||||
ActorInfo(ActorInfo &&) = delete;
|
||||
ActorInfo &operator=(ActorInfo &&) = delete;
|
||||
|
||||
ActorInfo(const ActorInfo &) = delete;
|
||||
ActorInfo &operator=(const ActorInfo &) = delete;
|
||||
|
||||
void init(int32 sched_id, Slice name, ObjectPool<ActorInfo>::OwnerPtr &&this_ptr, Actor *actor_ptr, Deleter deleter,
|
||||
bool need_context, bool need_start_up);
|
||||
void on_actor_moved(Actor *actor_new_ptr);
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> transfer_ownership_to_scheduler(unique_ptr<ActorT> actor);
|
||||
void clear();
|
||||
void destroy_actor();
|
||||
|
||||
bool empty() const;
|
||||
void start_migrate(int32 to_sched_id);
|
||||
bool is_migrating() const;
|
||||
int32 migrate_dest() const;
|
||||
std::pair<int32, bool> migrate_dest_flag_atomic() const;
|
||||
|
||||
void finish_migrate();
|
||||
|
||||
ActorId<> actor_id();
|
||||
template <class SelfT>
|
||||
ActorId<SelfT> actor_id(SelfT *self);
|
||||
Actor *get_actor_unsafe();
|
||||
const Actor *get_actor_unsafe() const;
|
||||
|
||||
std::shared_ptr<ActorContext> set_context(std::shared_ptr<ActorContext> context);
|
||||
std::weak_ptr<ActorContext> get_context_weak_ptr() const;
|
||||
ActorContext *get_context();
|
||||
const ActorContext *get_context() const;
|
||||
CSlice get_name() const;
|
||||
|
||||
HeapNode *get_heap_node();
|
||||
const HeapNode *get_heap_node() const;
|
||||
static ActorInfo *from_heap_node(HeapNode *node);
|
||||
|
||||
ListNode *get_list_node();
|
||||
const ListNode *get_list_node() const;
|
||||
static ActorInfo *from_list_node(ListNode *node);
|
||||
|
||||
void start_run();
|
||||
bool is_running() const;
|
||||
void finish_run();
|
||||
|
||||
vector<Event> mailbox_;
|
||||
|
||||
bool need_context() const;
|
||||
bool need_start_up() const;
|
||||
|
||||
private:
|
||||
Deleter deleter_ = Deleter::None;
|
||||
bool need_context_ = true;
|
||||
bool need_start_up_ = true;
|
||||
bool is_running_ = false;
|
||||
|
||||
std::atomic<int32> sched_id_{0};
|
||||
Actor *actor_ = nullptr;
|
||||
|
||||
#ifdef TD_DEBUG
|
||||
string name_;
|
||||
#endif
|
||||
std::shared_ptr<ActorContext> context_;
|
||||
};
|
||||
|
||||
StringBuilder &operator<<(StringBuilder &sb, const ActorInfo &info);
|
||||
|
||||
} // namespace td
|
||||
209
td/tdactor/td/actor/impl/ActorInfo.h
Normal file
209
td/tdactor/td/actor/impl/ActorInfo.h
Normal file
@@ -0,0 +1,209 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/Actor-decl.h"
|
||||
#include "td/actor/impl/ActorInfo-decl.h"
|
||||
#include "td/actor/impl/Scheduler-decl.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
inline StringBuilder &operator<<(StringBuilder &sb, const ActorInfo &info) {
|
||||
sb << info.get_name() << ":" << const_cast<void *>(static_cast<const void *>(&info)) << ":"
|
||||
<< const_cast<void *>(static_cast<const void *>(info.get_context()));
|
||||
return sb;
|
||||
}
|
||||
|
||||
inline void ActorInfo::init(int32 sched_id, Slice name, ObjectPool<ActorInfo>::OwnerPtr &&this_ptr, Actor *actor_ptr,
|
||||
Deleter deleter, bool need_context, bool need_start_up) {
|
||||
CHECK(!is_running());
|
||||
CHECK(!is_migrating());
|
||||
sched_id_.store(sched_id, std::memory_order_relaxed);
|
||||
actor_ = actor_ptr;
|
||||
|
||||
if (need_context) {
|
||||
context_ = Scheduler::context()->this_ptr_.lock();
|
||||
VLOG(actor) << "Set context " << context_.get() << " for " << name;
|
||||
}
|
||||
#ifdef TD_DEBUG
|
||||
name_.assign(name.data(), name.size());
|
||||
#endif
|
||||
|
||||
actor_->set_info(std::move(this_ptr));
|
||||
deleter_ = deleter;
|
||||
need_context_ = need_context;
|
||||
need_start_up_ = need_start_up;
|
||||
is_running_ = false;
|
||||
}
|
||||
|
||||
inline bool ActorInfo::need_context() const {
|
||||
return need_context_;
|
||||
}
|
||||
|
||||
inline bool ActorInfo::need_start_up() const {
|
||||
return need_start_up_;
|
||||
}
|
||||
|
||||
inline void ActorInfo::on_actor_moved(Actor *actor_new_ptr) {
|
||||
actor_ = actor_new_ptr;
|
||||
}
|
||||
|
||||
inline void ActorInfo::clear() {
|
||||
CHECK(mailbox_.empty());
|
||||
CHECK(!actor_);
|
||||
CHECK(!is_running());
|
||||
CHECK(!is_migrating());
|
||||
// NB: must be in non-migrating state
|
||||
// store invalid scheduler identifier
|
||||
sched_id_.store((1 << 30) - 1, std::memory_order_relaxed);
|
||||
VLOG(actor) << "Clear context " << context_.get() << " for " << get_name();
|
||||
context_.reset();
|
||||
}
|
||||
|
||||
inline void ActorInfo::destroy_actor() {
|
||||
if (!actor_) {
|
||||
return;
|
||||
}
|
||||
switch (deleter_) {
|
||||
case Deleter::Destroy:
|
||||
std::default_delete<Actor>()(actor_);
|
||||
break;
|
||||
case Deleter::None:
|
||||
break;
|
||||
}
|
||||
actor_ = nullptr;
|
||||
mailbox_.clear();
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> ActorInfo::transfer_ownership_to_scheduler(unique_ptr<ActorT> actor) {
|
||||
CHECK(!empty());
|
||||
CHECK(deleter_ == Deleter::None);
|
||||
ActorT *actor_ptr = actor.release();
|
||||
CHECK(actor_ == static_cast<Actor *>(actor_ptr));
|
||||
actor_ = static_cast<Actor *>(actor_ptr);
|
||||
deleter_ = Deleter::Destroy;
|
||||
return ActorOwn<ActorT>(actor_id(actor_ptr));
|
||||
}
|
||||
|
||||
inline bool ActorInfo::empty() const {
|
||||
return actor_ == nullptr;
|
||||
}
|
||||
|
||||
inline void ActorInfo::start_migrate(int32 to_sched_id) {
|
||||
sched_id_.store(to_sched_id | (1 << 30), std::memory_order_relaxed);
|
||||
}
|
||||
inline std::pair<int32, bool> ActorInfo::migrate_dest_flag_atomic() const {
|
||||
int32 sched_id = sched_id_.load(std::memory_order_relaxed);
|
||||
return std::make_pair(sched_id & ~(1 << 30), (sched_id & (1 << 30)) != 0);
|
||||
}
|
||||
inline void ActorInfo::finish_migrate() {
|
||||
sched_id_.store(migrate_dest(), std::memory_order_relaxed);
|
||||
}
|
||||
inline bool ActorInfo::is_migrating() const {
|
||||
return migrate_dest_flag_atomic().second;
|
||||
}
|
||||
inline int32 ActorInfo::migrate_dest() const {
|
||||
return migrate_dest_flag_atomic().first;
|
||||
}
|
||||
|
||||
inline ActorId<> ActorInfo::actor_id() {
|
||||
return actor_id(actor_);
|
||||
}
|
||||
|
||||
template <class SelfT>
|
||||
ActorId<SelfT> ActorInfo::actor_id(SelfT *self) {
|
||||
return actor_->actor_id(self);
|
||||
}
|
||||
|
||||
inline Actor *ActorInfo::get_actor_unsafe() {
|
||||
return actor_;
|
||||
}
|
||||
inline const Actor *ActorInfo::get_actor_unsafe() const {
|
||||
return actor_;
|
||||
}
|
||||
|
||||
inline std::shared_ptr<ActorContext> ActorInfo::set_context(std::shared_ptr<ActorContext> context) {
|
||||
CHECK(is_running());
|
||||
context->this_ptr_ = context;
|
||||
if (Scheduler::context()->tag_) {
|
||||
context->set_tag(Scheduler::context()->tag_);
|
||||
}
|
||||
std::swap(context_, context);
|
||||
Scheduler::context() = context_.get();
|
||||
Scheduler::on_context_updated();
|
||||
return context;
|
||||
}
|
||||
|
||||
inline std::weak_ptr<ActorContext> ActorInfo::get_context_weak_ptr() const {
|
||||
return context_;
|
||||
}
|
||||
|
||||
inline const ActorContext *ActorInfo::get_context() const {
|
||||
return context_.get();
|
||||
}
|
||||
|
||||
inline ActorContext *ActorInfo::get_context() {
|
||||
return context_.get();
|
||||
}
|
||||
|
||||
inline CSlice ActorInfo::get_name() const {
|
||||
#ifdef TD_DEBUG
|
||||
return name_;
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void ActorInfo::start_run() {
|
||||
VLOG(actor) << "Start run actor: " << *this;
|
||||
LOG_CHECK(!is_running_) << "Recursive call of actor " << get_name();
|
||||
is_running_ = true;
|
||||
}
|
||||
inline void ActorInfo::finish_run() {
|
||||
is_running_ = false;
|
||||
if (!empty()) {
|
||||
VLOG(actor) << "Stop run actor: " << *this;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ActorInfo::is_running() const {
|
||||
return is_running_;
|
||||
}
|
||||
|
||||
inline HeapNode *ActorInfo::get_heap_node() {
|
||||
return this;
|
||||
}
|
||||
inline const HeapNode *ActorInfo::get_heap_node() const {
|
||||
return this;
|
||||
}
|
||||
inline ActorInfo *ActorInfo::from_heap_node(HeapNode *node) {
|
||||
return static_cast<ActorInfo *>(node);
|
||||
}
|
||||
inline ListNode *ActorInfo::get_list_node() {
|
||||
return this;
|
||||
}
|
||||
inline const ListNode *ActorInfo::get_list_node() const {
|
||||
return this;
|
||||
}
|
||||
inline ActorInfo *ActorInfo::from_list_node(ListNode *node) {
|
||||
return static_cast<ActorInfo *>(node);
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
247
td/tdactor/td/actor/impl/Event.h
Normal file
247
td/tdactor/td/actor/impl/Event.h
Normal file
@@ -0,0 +1,247 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Closure.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
class Actor;
|
||||
|
||||
// Events
|
||||
//
|
||||
// Small structure (up to 16 bytes) used to send events between actors.
|
||||
//
|
||||
// There are some predefined types of events:
|
||||
// NoType -- unitialized event
|
||||
// Start -- start actor
|
||||
// Stop -- stop actor
|
||||
// Yield -- wake up actor
|
||||
// Timeout -- some timeout has expired
|
||||
// Hangup -- hang up called
|
||||
// Raw -- just pass 8 bytes (union Raw is used for convenience)
|
||||
// Custom -- Send CustomEvent
|
||||
|
||||
template <class T>
|
||||
std::enable_if_t<!std::is_base_of<Actor, T>::value> start_migrate(T &obj, int32 sched_id) {
|
||||
}
|
||||
template <class T>
|
||||
std::enable_if_t<!std::is_base_of<Actor, T>::value> finish_migrate(T &obj) {
|
||||
}
|
||||
|
||||
class CustomEvent {
|
||||
public:
|
||||
CustomEvent() = default;
|
||||
CustomEvent(const CustomEvent &) = delete;
|
||||
CustomEvent &operator=(const CustomEvent &) = delete;
|
||||
CustomEvent(CustomEvent &&) = delete;
|
||||
CustomEvent &operator=(CustomEvent &&) = delete;
|
||||
virtual ~CustomEvent() = default;
|
||||
|
||||
virtual void run(Actor *actor) = 0;
|
||||
virtual void start_migrate(int32 sched_id) {
|
||||
}
|
||||
virtual void finish_migrate() {
|
||||
}
|
||||
};
|
||||
|
||||
template <class ClosureT>
|
||||
class ClosureEvent final : public CustomEvent {
|
||||
public:
|
||||
void run(Actor *actor) final {
|
||||
closure_.run(static_cast<typename ClosureT::ActorType *>(actor));
|
||||
}
|
||||
template <class... ArgsT>
|
||||
explicit ClosureEvent(ArgsT &&...args) : closure_(std::forward<ArgsT>(args)...) {
|
||||
}
|
||||
|
||||
void start_migrate(int32 sched_id) final {
|
||||
closure_.for_each([sched_id](auto &obj) {
|
||||
using ::td::start_migrate;
|
||||
start_migrate(obj, sched_id);
|
||||
});
|
||||
}
|
||||
|
||||
void finish_migrate() final {
|
||||
closure_.for_each([](auto &obj) {
|
||||
using ::td::finish_migrate;
|
||||
finish_migrate(obj);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ClosureT closure_;
|
||||
};
|
||||
|
||||
template <class LambdaT>
|
||||
class LambdaEvent final : public CustomEvent {
|
||||
public:
|
||||
void run(Actor *actor) final {
|
||||
f_();
|
||||
}
|
||||
template <class FromLambdaT, std::enable_if_t<!std::is_same<std::decay_t<FromLambdaT>, LambdaEvent>::value, int> = 0>
|
||||
explicit LambdaEvent(FromLambdaT &&func) : f_(std::forward<FromLambdaT>(func)) {
|
||||
}
|
||||
|
||||
private:
|
||||
LambdaT f_;
|
||||
};
|
||||
|
||||
class Event {
|
||||
public:
|
||||
enum class Type { NoType, Start, Stop, Yield, Timeout, Hangup, Raw, Custom };
|
||||
Type type;
|
||||
uint64 link_token = 0;
|
||||
union Raw {
|
||||
void *ptr;
|
||||
CustomEvent *custom_event;
|
||||
uint32 u32;
|
||||
uint64 u64;
|
||||
} data{};
|
||||
|
||||
// factory functions
|
||||
static Event start() {
|
||||
return Event(Type::Start);
|
||||
}
|
||||
static Event stop() {
|
||||
return Event(Type::Stop);
|
||||
}
|
||||
static Event yield() {
|
||||
return Event(Type::Yield);
|
||||
}
|
||||
static Event timeout() {
|
||||
return Event(Type::Timeout);
|
||||
}
|
||||
static Event hangup() {
|
||||
return Event(Type::Hangup);
|
||||
}
|
||||
static Event raw(void *ptr) {
|
||||
return Event(Type::Raw, ptr);
|
||||
}
|
||||
static Event raw(uint32 u32) {
|
||||
return Event(Type::Raw, u32);
|
||||
}
|
||||
static Event raw(uint64 u64) {
|
||||
return Event(Type::Raw, u64);
|
||||
}
|
||||
static Event custom(CustomEvent *custom_event) {
|
||||
return Event(Type::Custom, custom_event);
|
||||
}
|
||||
|
||||
template <class FromImmediateClosureT>
|
||||
static Event immediate_closure(FromImmediateClosureT &&closure) {
|
||||
return custom(
|
||||
new ClosureEvent<typename FromImmediateClosureT::Delayed>(std::forward<FromImmediateClosureT>(closure)));
|
||||
}
|
||||
template <class... ArgsT>
|
||||
static Event delayed_closure(ArgsT &&...args) {
|
||||
using DelayedClosureT = decltype(create_delayed_closure(std::forward<ArgsT>(args)...));
|
||||
return custom(new ClosureEvent<DelayedClosureT>(std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class FromLambdaT>
|
||||
static Event from_lambda(FromLambdaT &&func) {
|
||||
return custom(new LambdaEvent<std::decay_t<FromLambdaT>>(std::forward<FromLambdaT>(func)));
|
||||
}
|
||||
|
||||
Event() : Event(Type::NoType) {
|
||||
}
|
||||
Event(const Event &) = delete;
|
||||
Event &operator=(const Event &) = delete;
|
||||
Event(Event &&other) noexcept : type(other.type), link_token(other.link_token), data(other.data) {
|
||||
other.type = Type::NoType;
|
||||
}
|
||||
Event &operator=(Event &&other) noexcept {
|
||||
destroy();
|
||||
type = other.type;
|
||||
link_token = other.link_token;
|
||||
data = other.data;
|
||||
other.type = Type::NoType;
|
||||
return *this;
|
||||
}
|
||||
~Event() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return type == Type::NoType;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
destroy();
|
||||
type = Type::NoType;
|
||||
}
|
||||
|
||||
Event &set_link_token(uint64 new_link_token) {
|
||||
link_token = new_link_token;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend void start_migrate(Event &obj, int32 sched_id) {
|
||||
if (obj.type == Type::Custom) {
|
||||
obj.data.custom_event->start_migrate(sched_id);
|
||||
}
|
||||
}
|
||||
friend void finish_migrate(Event &obj) {
|
||||
if (obj.type == Type::Custom) {
|
||||
obj.data.custom_event->finish_migrate();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Event(Type type) : type(type) {
|
||||
}
|
||||
|
||||
Event(Type type, void *ptr) : Event(type) {
|
||||
data.ptr = ptr;
|
||||
}
|
||||
Event(Type type, CustomEvent *custom_event) : Event(type) {
|
||||
data.custom_event = custom_event;
|
||||
}
|
||||
Event(Type type, uint32 u32) : Event(type) {
|
||||
data.u32 = u32;
|
||||
}
|
||||
Event(Type type, uint64 u64) : Event(type) {
|
||||
data.u64 = u64;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
if (type == Type::Custom) {
|
||||
delete data.custom_event;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline StringBuilder &operator<<(StringBuilder &string_builder, const Event &e) {
|
||||
string_builder << "Event::";
|
||||
switch (e.type) {
|
||||
case Event::Type::Start:
|
||||
return string_builder << "Start";
|
||||
case Event::Type::Stop:
|
||||
return string_builder << "Stop";
|
||||
case Event::Type::Yield:
|
||||
return string_builder << "Yield";
|
||||
case Event::Type::Hangup:
|
||||
return string_builder << "Hangup";
|
||||
case Event::Type::Timeout:
|
||||
return string_builder << "Timeout";
|
||||
case Event::Type::Raw:
|
||||
return string_builder << "Raw";
|
||||
case Event::Type::Custom:
|
||||
return string_builder << "Custom";
|
||||
case Event::Type::NoType:
|
||||
default:
|
||||
return string_builder << "NoType";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
87
td/tdactor/td/actor/impl/EventFull-decl.h
Normal file
87
td/tdactor/td/actor/impl/EventFull-decl.h
Normal file
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/ActorId-decl.h"
|
||||
#include "td/actor/impl/Event.h"
|
||||
|
||||
#include "td/utils/type_traits.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
class EventFull {
|
||||
public:
|
||||
EventFull() = default;
|
||||
|
||||
bool empty() const {
|
||||
return data_.empty();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
data_.clear();
|
||||
}
|
||||
|
||||
ActorId<> actor_id() const {
|
||||
return actor_id_;
|
||||
}
|
||||
Event &data() {
|
||||
return data_;
|
||||
}
|
||||
|
||||
void try_emit_later();
|
||||
void try_emit();
|
||||
|
||||
private:
|
||||
friend class EventCreator;
|
||||
|
||||
EventFull(ActorRef actor_ref, Event &&data) : actor_id_(actor_ref.get()), data_(std::move(data)) {
|
||||
data_.link_token = actor_ref.token();
|
||||
}
|
||||
template <class T>
|
||||
EventFull(ActorId<T> actor_id, Event &&data) : actor_id_(std::move(actor_id)), data_(std::move(data)) {
|
||||
}
|
||||
|
||||
ActorId<> actor_id_;
|
||||
|
||||
Event data_;
|
||||
};
|
||||
|
||||
class EventCreator {
|
||||
public:
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT>
|
||||
static EventFull closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
using FunctionClassT = member_function_class_t<FunctionT>;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
return EventFull(std::forward<ActorIdT>(actor_id), Event::delayed_closure(function, std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class LambdaT>
|
||||
static EventFull from_lambda(ActorRef actor_ref, LambdaT &&func) {
|
||||
return EventFull(actor_ref, Event::from_lambda(std::forward<LambdaT>(func)));
|
||||
}
|
||||
|
||||
static EventFull yield(ActorRef actor_ref) {
|
||||
return EventFull(actor_ref, Event::yield());
|
||||
}
|
||||
static EventFull raw(ActorRef actor_ref, uint64 data) {
|
||||
return EventFull(actor_ref, Event::raw(data));
|
||||
}
|
||||
static EventFull raw(ActorRef actor_ref, void *ptr) {
|
||||
return EventFull(actor_ref, Event::raw(ptr));
|
||||
}
|
||||
|
||||
static EventFull event_unsafe(ActorId<> actor_id, Event &&event) {
|
||||
return EventFull(actor_id, std::move(event));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace td
|
||||
38
td/tdactor/td/actor/impl/EventFull.h
Normal file
38
td/tdactor/td/actor/impl/EventFull.h
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/EventFull-decl.h"
|
||||
#include "td/actor/impl/Scheduler-decl.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
inline void EventFull::try_emit_later() {
|
||||
if (empty()) {
|
||||
return;
|
||||
}
|
||||
auto link_token = data_.link_token;
|
||||
send_event_later(ActorShared<>(actor_id_, link_token), std::move(data_));
|
||||
data_.clear();
|
||||
CHECK(empty());
|
||||
}
|
||||
|
||||
inline void EventFull::try_emit() {
|
||||
if (empty()) {
|
||||
return;
|
||||
}
|
||||
auto link_token = data_.link_token;
|
||||
send_event(ActorShared<>(actor_id_, link_token), std::move(data_));
|
||||
data_.clear();
|
||||
CHECK(empty());
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
314
td/tdactor/td/actor/impl/Scheduler-decl.h
Normal file
314
td/tdactor/td/actor/impl/Scheduler-decl.h
Normal file
@@ -0,0 +1,314 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/Actor-decl.h"
|
||||
#include "td/actor/impl/ActorId-decl.h"
|
||||
#include "td/actor/impl/EventFull-decl.h"
|
||||
|
||||
#include "td/utils/Closure.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/FlatHashMap.h"
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/MovableValue.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/port/detail/PollableFd.h"
|
||||
#include "td/utils/port/Poll.h"
|
||||
#include "td/utils/port/PollFlags.h"
|
||||
#include "td/utils/port/thread_local.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Time.h"
|
||||
#include "td/utils/type_traits.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
extern int VERBOSITY_NAME(actor);
|
||||
|
||||
class ActorInfo;
|
||||
|
||||
class Scheduler;
|
||||
class SchedulerGuard {
|
||||
public:
|
||||
explicit SchedulerGuard(Scheduler *scheduler, bool lock = true);
|
||||
~SchedulerGuard();
|
||||
SchedulerGuard(const SchedulerGuard &) = delete;
|
||||
SchedulerGuard &operator=(const SchedulerGuard &) = delete;
|
||||
SchedulerGuard(SchedulerGuard &&) = default;
|
||||
SchedulerGuard &operator=(SchedulerGuard &&) = delete;
|
||||
|
||||
private:
|
||||
MovableValue<bool> is_valid_ = true;
|
||||
bool is_locked_;
|
||||
Scheduler *scheduler_;
|
||||
ActorContext *save_context_;
|
||||
Scheduler *save_scheduler_;
|
||||
const char *save_tag_;
|
||||
};
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
class Callback {
|
||||
public:
|
||||
Callback() = default;
|
||||
Callback(const Callback &) = delete;
|
||||
Callback &operator=(const Callback &) = delete;
|
||||
virtual ~Callback() = default;
|
||||
virtual void on_finish() = 0;
|
||||
virtual void register_at_finish(std::function<void()>) = 0;
|
||||
};
|
||||
Scheduler() = default;
|
||||
Scheduler(const Scheduler &) = delete;
|
||||
Scheduler &operator=(const Scheduler &) = delete;
|
||||
Scheduler(Scheduler &&) = delete;
|
||||
Scheduler &operator=(Scheduler &&) = delete;
|
||||
~Scheduler();
|
||||
|
||||
void init(int32 id, std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound, Callback *callback);
|
||||
|
||||
int32 sched_id() const;
|
||||
int32 sched_count() const;
|
||||
|
||||
template <class ActorT, class... Args>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor(Slice name, Args &&...args);
|
||||
template <class ActorT, class... Args>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args);
|
||||
template <class ActorT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_actor(Slice name, ActorT *actor_ptr, int32 sched_id = -1);
|
||||
template <class ActorT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_actor(Slice name, unique_ptr<ActorT> actor_ptr, int32 sched_id = -1);
|
||||
|
||||
template <class ActorT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_existing_actor(unique_ptr<ActorT> actor_ptr);
|
||||
|
||||
void send_to_scheduler(int32 sched_id, const ActorId<> &actor_id, Event &&event);
|
||||
void send_to_other_scheduler(int32 sched_id, const ActorId<> &actor_id, Event &&event);
|
||||
|
||||
void run_on_scheduler(int32 sched_id, Promise<Unit> action); // TODO Action
|
||||
|
||||
template <class T>
|
||||
void destroy_on_scheduler(int32 sched_id, T &value);
|
||||
|
||||
template <class T>
|
||||
void destroy_on_scheduler_unique_ptr(int32 sched_id, T &value);
|
||||
|
||||
template <class... ArgsT>
|
||||
void destroy_on_scheduler(int32 sched_id, ArgsT &...values);
|
||||
|
||||
template <class EventT>
|
||||
void send_lambda_immediately(ActorRef actor_ref, EventT &&func);
|
||||
|
||||
template <class EventT>
|
||||
void send_lambda_later(ActorRef actor_ref, EventT &&func);
|
||||
|
||||
template <class EventT>
|
||||
void send_closure_immediately(ActorRef actor_ref, EventT &&closure);
|
||||
|
||||
template <class EventT>
|
||||
void send_closure_later(ActorRef actor_ref, EventT &&closure);
|
||||
|
||||
void send_immediately(ActorRef actor_ref, Event &&event);
|
||||
|
||||
void send_later(ActorRef actor_ref, Event &&event);
|
||||
|
||||
void before_tail_send(const ActorId<> &actor_id);
|
||||
|
||||
static void subscribe(PollableFd fd, PollFlags flags = PollFlags::ReadWrite());
|
||||
static void unsubscribe(PollableFdRef fd);
|
||||
static void unsubscribe_before_close(PollableFdRef fd);
|
||||
|
||||
void yield_actor(Actor *actor);
|
||||
void stop_actor(Actor *actor);
|
||||
void do_stop_actor(Actor *actor);
|
||||
uint64 get_link_token(Actor *actor);
|
||||
void migrate_actor(Actor *actor, int32 dest_sched_id);
|
||||
void do_migrate_actor(Actor *actor, int32 dest_sched_id);
|
||||
void start_migrate_actor(Actor *actor, int32 dest_sched_id);
|
||||
void finish_migrate_actor(Actor *actor);
|
||||
|
||||
double get_actor_timeout(const Actor *actor) const;
|
||||
void set_actor_timeout_in(Actor *actor, double timeout);
|
||||
void set_actor_timeout_at(Actor *actor, double timeout_at);
|
||||
void cancel_actor_timeout(Actor *actor);
|
||||
|
||||
void finish();
|
||||
void yield();
|
||||
void run(Timestamp timeout);
|
||||
void run_no_guard(Timestamp timeout);
|
||||
|
||||
void wakeup();
|
||||
|
||||
static Scheduler *instance();
|
||||
static ActorContext *&context();
|
||||
static void on_context_updated();
|
||||
|
||||
SchedulerGuard get_guard();
|
||||
SchedulerGuard get_const_guard();
|
||||
|
||||
Timestamp get_timeout();
|
||||
|
||||
private:
|
||||
static void set_scheduler(Scheduler *scheduler);
|
||||
|
||||
void destroy_on_scheduler_impl(int32 sched_id, Promise<Unit> action);
|
||||
|
||||
class ServiceActor final : public Actor {
|
||||
public:
|
||||
void set_queue(std::shared_ptr<MpscPollableQueue<EventFull>> queues);
|
||||
|
||||
private:
|
||||
std::shared_ptr<MpscPollableQueue<EventFull>> inbound_;
|
||||
bool subscribed_{false};
|
||||
|
||||
void start_up() final;
|
||||
void loop() final;
|
||||
void tear_down() final;
|
||||
};
|
||||
friend class ServiceActor;
|
||||
|
||||
void clear();
|
||||
|
||||
void do_event(ActorInfo *actor, Event &&event);
|
||||
|
||||
void enter_actor(ActorInfo *actor_info);
|
||||
void exit_actor(ActorInfo *actor_info);
|
||||
|
||||
void yield_actor(ActorInfo *actor_info);
|
||||
void stop_actor(ActorInfo *actor_info);
|
||||
void do_stop_actor(ActorInfo *actor_info);
|
||||
uint64 get_link_token(ActorInfo *actor_info);
|
||||
void migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
|
||||
void do_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
|
||||
void start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id);
|
||||
|
||||
double get_actor_timeout(const ActorInfo *actor_info) const;
|
||||
void set_actor_timeout_in(ActorInfo *actor_info, double timeout);
|
||||
void set_actor_timeout_at(ActorInfo *actor_info, double timeout_at);
|
||||
void cancel_actor_timeout(ActorInfo *actor_info);
|
||||
|
||||
void register_migrated_actor(ActorInfo *actor_info);
|
||||
void add_to_mailbox(ActorInfo *actor_info, Event &&event);
|
||||
void clear_mailbox(ActorInfo *actor_info);
|
||||
|
||||
void flush_mailbox(ActorInfo *actor_info);
|
||||
|
||||
void get_actor_sched_id_to_send_immediately(const ActorInfo *actor_info, int32 &actor_sched_id,
|
||||
bool &on_current_sched, bool &can_send_immediately);
|
||||
|
||||
template <class RunFuncT, class EventFuncT>
|
||||
void send_immediately_impl(const ActorId<> &actor_id, const RunFuncT &run_func, const EventFuncT &event_func);
|
||||
|
||||
void send_later_impl(const ActorId<> &actor_id, Event &&event);
|
||||
|
||||
Timestamp run_timeout();
|
||||
void run_mailbox();
|
||||
Timestamp run_events(Timestamp timeout);
|
||||
void run_poll(Timestamp timeout);
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> register_actor_impl(Slice name, ActorT *actor_ptr, Actor::Deleter deleter, int32 sched_id);
|
||||
void destroy_actor(ActorInfo *actor_info);
|
||||
|
||||
static TD_THREAD_LOCAL Scheduler *scheduler_;
|
||||
static TD_THREAD_LOCAL ActorContext *context_;
|
||||
|
||||
Callback *callback_ = nullptr;
|
||||
unique_ptr<ObjectPool<ActorInfo>> actor_info_pool_;
|
||||
|
||||
int32 actor_count_ = 0;
|
||||
ListNode pending_actors_list_;
|
||||
ListNode ready_actors_list_;
|
||||
KHeap<double> timeout_queue_;
|
||||
|
||||
FlatHashMap<ActorInfo *, std::vector<Event>> pending_events_;
|
||||
|
||||
ServiceActor service_actor_;
|
||||
Poll poll_;
|
||||
|
||||
bool yield_flag_ = false;
|
||||
bool has_guard_ = false;
|
||||
bool close_flag_ = false;
|
||||
|
||||
int32 sched_id_ = 0;
|
||||
int32 sched_n_ = 0;
|
||||
std::shared_ptr<MpscPollableQueue<EventFull>> inbound_queue_;
|
||||
std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound_queues_;
|
||||
|
||||
std::shared_ptr<ActorContext> save_context_;
|
||||
|
||||
struct EventContext {
|
||||
int32 dest_sched_id{0};
|
||||
enum Flags { Stop = 1, Migrate = 2 };
|
||||
int32 flags{0};
|
||||
uint64 link_token{0};
|
||||
|
||||
ActorInfo *actor_info{nullptr};
|
||||
};
|
||||
EventContext *event_context_ptr_{nullptr};
|
||||
|
||||
friend class GlobalScheduler;
|
||||
friend class SchedulerGuard;
|
||||
friend class EventGuard;
|
||||
};
|
||||
|
||||
/*** Interface to current scheduler ***/
|
||||
template <class ActorT, class... Args>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor(Slice name, Args &&...args);
|
||||
template <class ActorT, class... Args>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args);
|
||||
template <class ActorT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_actor(Slice name, ActorT *actor_ptr, int32 sched_id = -1);
|
||||
template <class ActorT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_actor(Slice name, unique_ptr<ActorT> actor_ptr, int32 sched_id = -1);
|
||||
|
||||
template <class ActorT>
|
||||
TD_WARN_UNUSED_RESULT ActorOwn<ActorT> register_existing_actor(unique_ptr<ActorT> actor_ptr);
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT>
|
||||
void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
using FunctionClassT = member_function_class_t<FunctionT>;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
Scheduler::instance()->send_closure_immediately(std::forward<ActorIdT>(actor_id),
|
||||
create_immediate_closure(function, std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT>
|
||||
void send_closure_later(ActorIdT &&actor_id, FunctionT function, ArgsT &&...args) {
|
||||
using ActorT = typename std::decay_t<ActorIdT>::ActorT;
|
||||
using FunctionClassT = member_function_class_t<FunctionT>;
|
||||
static_assert(std::is_base_of<FunctionClassT, ActorT>::value, "unsafe send_closure");
|
||||
|
||||
Scheduler::instance()->send_later(std::forward<ActorIdT>(actor_id),
|
||||
Event::delayed_closure(function, std::forward<ArgsT>(args)...));
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
void send_lambda(ActorRef actor_ref, ArgsT &&...args) {
|
||||
Scheduler::instance()->send_lambda_immediately(actor_ref, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
void send_event(ActorRef actor_ref, ArgsT &&...args) {
|
||||
Scheduler::instance()->send_immediately(actor_ref, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
void send_event_later(ActorRef actor_ref, ArgsT &&...args) {
|
||||
Scheduler::instance()->send_later(actor_ref, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
611
td/tdactor/td/actor/impl/Scheduler.cpp
Normal file
611
td/tdactor/td/actor/impl/Scheduler.cpp
Normal file
@@ -0,0 +1,611 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/impl/Scheduler.h"
|
||||
|
||||
#include "td/actor/impl/Actor.h"
|
||||
#include "td/actor/impl/ActorId.h"
|
||||
#include "td/actor/impl/ActorInfo.h"
|
||||
#include "td/actor/impl/Event.h"
|
||||
#include "td/actor/impl/EventFull.h"
|
||||
|
||||
#include "td/utils/algorithm.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/ExitGuard.h"
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/List.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/port/thread_local.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
int VERBOSITY_NAME(actor) = VERBOSITY_NAME(DEBUG) + 10;
|
||||
|
||||
TD_THREAD_LOCAL Scheduler *Scheduler::scheduler_; // static zero-initialized
|
||||
TD_THREAD_LOCAL ActorContext *Scheduler::context_; // static zero-initialized
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
clear();
|
||||
}
|
||||
|
||||
Scheduler *Scheduler::instance() {
|
||||
return scheduler_;
|
||||
}
|
||||
|
||||
ActorContext *&Scheduler::context() {
|
||||
return context_;
|
||||
}
|
||||
|
||||
void Scheduler::on_context_updated() {
|
||||
LOG_TAG = context_->tag_;
|
||||
}
|
||||
|
||||
void Scheduler::set_scheduler(Scheduler *scheduler) {
|
||||
scheduler_ = scheduler;
|
||||
}
|
||||
|
||||
void Scheduler::ServiceActor::set_queue(std::shared_ptr<MpscPollableQueue<EventFull>> queues) {
|
||||
inbound_ = std::move(queues);
|
||||
}
|
||||
|
||||
void Scheduler::ServiceActor::start_up() {
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
CHECK(!inbound_);
|
||||
#else
|
||||
if (!inbound_) {
|
||||
return;
|
||||
}
|
||||
#if !TD_PORT_WINDOWS
|
||||
auto &fd = inbound_->reader_get_event_fd();
|
||||
Scheduler::subscribe(fd.get_poll_info().extract_pollable_fd(this), PollFlags::Read());
|
||||
subscribed_ = true;
|
||||
#endif
|
||||
yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Scheduler::ServiceActor::loop() {
|
||||
auto &queue = inbound_;
|
||||
int ready_n = queue->reader_wait_nonblock();
|
||||
VLOG(actor) << "Have " << ready_n << " pending events";
|
||||
if (ready_n == 0) {
|
||||
return;
|
||||
}
|
||||
while (ready_n-- > 0) {
|
||||
EventFull event = queue->reader_get_unsafe();
|
||||
if (event.actor_id().empty()) {
|
||||
if (event.data().empty()) {
|
||||
Scheduler::instance()->yield();
|
||||
} else {
|
||||
Scheduler::instance()->register_migrated_actor(static_cast<ActorInfo *>(event.data().data.ptr));
|
||||
}
|
||||
} else {
|
||||
VLOG(actor) << "Receive " << event.data();
|
||||
finish_migrate(event.data());
|
||||
event.try_emit();
|
||||
}
|
||||
}
|
||||
queue->reader_flush();
|
||||
yield();
|
||||
}
|
||||
|
||||
void Scheduler::ServiceActor::tear_down() {
|
||||
if (!subscribed_) {
|
||||
return;
|
||||
}
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
CHECK(!inbound_);
|
||||
#else
|
||||
if (!inbound_) {
|
||||
return;
|
||||
}
|
||||
auto &fd = inbound_->reader_get_event_fd();
|
||||
Scheduler::unsubscribe(fd.get_poll_info().get_pollable_fd_ref());
|
||||
subscribed_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*** SchedlerGuard ***/
|
||||
SchedulerGuard::SchedulerGuard(Scheduler *scheduler, bool lock) : scheduler_(scheduler) {
|
||||
if (lock) {
|
||||
// the next check can fail if OS killed the scheduler's thread without releasing the guard
|
||||
CHECK(!scheduler_->has_guard_);
|
||||
scheduler_->has_guard_ = true;
|
||||
}
|
||||
is_locked_ = lock;
|
||||
save_scheduler_ = Scheduler::instance();
|
||||
Scheduler::set_scheduler(scheduler_);
|
||||
|
||||
// Scheduler::context() must be not null
|
||||
save_context_ = scheduler_->save_context_.get();
|
||||
save_tag_ = LOG_TAG;
|
||||
LOG_TAG = save_context_->tag_;
|
||||
std::swap(save_context_, Scheduler::context());
|
||||
}
|
||||
|
||||
SchedulerGuard::~SchedulerGuard() {
|
||||
if (is_valid_.get()) {
|
||||
std::swap(save_context_, scheduler_->context());
|
||||
Scheduler::set_scheduler(save_scheduler_);
|
||||
if (is_locked_) {
|
||||
CHECK(scheduler_->has_guard_);
|
||||
scheduler_->has_guard_ = false;
|
||||
}
|
||||
LOG_TAG = save_tag_;
|
||||
}
|
||||
}
|
||||
|
||||
/*** EventGuard ***/
|
||||
EventGuard::EventGuard(Scheduler *scheduler, ActorInfo *actor_info) : scheduler_(scheduler) {
|
||||
actor_info->start_run();
|
||||
event_context_.actor_info = actor_info;
|
||||
event_context_ptr_ = &event_context_;
|
||||
|
||||
save_context_ = actor_info->get_context();
|
||||
#ifdef TD_DEBUG
|
||||
save_log_tag2_ = actor_info->get_name().c_str();
|
||||
#endif
|
||||
swap_context(actor_info);
|
||||
}
|
||||
|
||||
EventGuard::~EventGuard() {
|
||||
auto info = event_context_.actor_info;
|
||||
auto node = info->get_list_node();
|
||||
node->remove();
|
||||
if (info->mailbox_.empty()) {
|
||||
scheduler_->pending_actors_list_.put(node);
|
||||
} else {
|
||||
scheduler_->ready_actors_list_.put(node);
|
||||
}
|
||||
info->finish_run();
|
||||
swap_context(info);
|
||||
CHECK(!info->need_context() || save_context_ == info->get_context());
|
||||
#ifdef TD_DEBUG
|
||||
LOG_CHECK(!info->need_context() || save_log_tag2_ == info->get_name().c_str())
|
||||
<< info->need_context() << " " << info->empty() << " " << info->is_migrating() << " " << save_log_tag2_ << " "
|
||||
<< info->get_name() << " " << scheduler_->close_flag_;
|
||||
#endif
|
||||
if (event_context_.flags & Scheduler::EventContext::Stop) {
|
||||
scheduler_->do_stop_actor(info);
|
||||
return;
|
||||
}
|
||||
if (event_context_.flags & Scheduler::EventContext::Migrate) {
|
||||
scheduler_->do_migrate_actor(info, event_context_.dest_sched_id);
|
||||
}
|
||||
}
|
||||
|
||||
void EventGuard::swap_context(ActorInfo *info) {
|
||||
std::swap(scheduler_->event_context_ptr_, event_context_ptr_);
|
||||
|
||||
if (!info->need_context()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TD_DEBUG
|
||||
std::swap(LOG_TAG2, save_log_tag2_);
|
||||
#endif
|
||||
|
||||
auto *current_context_ptr = &Scheduler::context();
|
||||
if (save_context_ != *current_context_ptr) {
|
||||
std::swap(save_context_, *current_context_ptr);
|
||||
Scheduler::on_context_updated();
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::init(int32 id, std::vector<std::shared_ptr<MpscPollableQueue<EventFull>>> outbound,
|
||||
Callback *callback) {
|
||||
save_context_ = std::make_shared<ActorContext>();
|
||||
save_context_->this_ptr_ = save_context_;
|
||||
save_context_->tag_ = LOG_TAG;
|
||||
|
||||
auto guard = get_guard();
|
||||
|
||||
callback_ = callback;
|
||||
actor_info_pool_ = make_unique<ObjectPool<ActorInfo>>();
|
||||
|
||||
yield_flag_ = false;
|
||||
actor_count_ = 0;
|
||||
sched_id_ = 0;
|
||||
|
||||
poll_.init();
|
||||
|
||||
if (!outbound.empty()) {
|
||||
inbound_queue_ = std::move(outbound[id]);
|
||||
}
|
||||
outbound_queues_ = std::move(outbound);
|
||||
sched_id_ = id;
|
||||
sched_n_ = static_cast<int32>(outbound_queues_.size());
|
||||
service_actor_.set_queue(inbound_queue_);
|
||||
register_actor(PSLICE() << "ServiceActor" << id, &service_actor_).release();
|
||||
}
|
||||
|
||||
void Scheduler::clear() {
|
||||
if (service_actor_.empty()) {
|
||||
return;
|
||||
}
|
||||
close_flag_ = true;
|
||||
auto guard = get_guard();
|
||||
|
||||
// Stop all actors
|
||||
if (!service_actor_.empty()) {
|
||||
service_actor_.do_stop();
|
||||
}
|
||||
while (!pending_actors_list_.empty()) {
|
||||
auto actor_info = ActorInfo::from_list_node(pending_actors_list_.get());
|
||||
do_stop_actor(actor_info);
|
||||
}
|
||||
while (!ready_actors_list_.empty()) {
|
||||
auto actor_info = ActorInfo::from_list_node(ready_actors_list_.get());
|
||||
do_stop_actor(actor_info);
|
||||
}
|
||||
poll_.clear();
|
||||
|
||||
if (callback_ && !ExitGuard::is_exited()) {
|
||||
// can't move lambda with unique_ptr inside into std::function
|
||||
auto ptr = actor_info_pool_.release();
|
||||
callback_->register_at_finish([ptr] { delete ptr; });
|
||||
} else {
|
||||
actor_info_pool_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::do_event(ActorInfo *actor_info, Event &&event) {
|
||||
event_context_ptr_->link_token = event.link_token;
|
||||
auto actor = actor_info->get_actor_unsafe();
|
||||
VLOG(actor) << *actor_info << ' ' << event;
|
||||
switch (event.type) {
|
||||
case Event::Type::Start:
|
||||
actor->start_up();
|
||||
break;
|
||||
case Event::Type::Stop:
|
||||
actor->tear_down();
|
||||
break;
|
||||
case Event::Type::Yield:
|
||||
actor->wakeup();
|
||||
break;
|
||||
case Event::Type::Hangup:
|
||||
if (get_link_token(actor) != 0) {
|
||||
actor->hangup_shared();
|
||||
} else {
|
||||
actor->hangup();
|
||||
}
|
||||
break;
|
||||
case Event::Type::Timeout:
|
||||
actor->timeout_expired();
|
||||
break;
|
||||
case Event::Type::Raw:
|
||||
actor->raw_event(event.data);
|
||||
break;
|
||||
case Event::Type::Custom:
|
||||
event.data.custom_event->run(actor);
|
||||
break;
|
||||
case Event::Type::NoType:
|
||||
default:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
// can't clear event here. It may be already destroyed during destroy_actor
|
||||
}
|
||||
|
||||
void Scheduler::get_actor_sched_id_to_send_immediately(const ActorInfo *actor_info, int32 &actor_sched_id,
|
||||
bool &on_current_sched, bool &can_send_immediately) {
|
||||
bool is_migrating;
|
||||
std::tie(actor_sched_id, is_migrating) = actor_info->migrate_dest_flag_atomic();
|
||||
on_current_sched = !is_migrating && sched_id_ == actor_sched_id;
|
||||
CHECK(has_guard_ || !on_current_sched);
|
||||
can_send_immediately = on_current_sched && !actor_info->is_running() && actor_info->mailbox_.empty();
|
||||
}
|
||||
|
||||
void Scheduler::send_later_impl(const ActorId<> &actor_id, Event &&event) {
|
||||
ActorInfo *actor_info = actor_id.get_actor_info();
|
||||
if (unlikely(actor_info == nullptr || close_flag_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32 actor_sched_id;
|
||||
bool is_migrating;
|
||||
std::tie(actor_sched_id, is_migrating) = actor_info->migrate_dest_flag_atomic();
|
||||
bool on_current_sched = !is_migrating && sched_id_ == actor_sched_id;
|
||||
CHECK(has_guard_ || !on_current_sched);
|
||||
|
||||
if (on_current_sched) {
|
||||
add_to_mailbox(actor_info, std::move(event));
|
||||
} else {
|
||||
send_to_scheduler(actor_sched_id, actor_id, std::move(event));
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::register_migrated_actor(ActorInfo *actor_info) {
|
||||
VLOG(actor) << "Register migrated actor " << *actor_info << ", " << tag("actor_count", actor_count_);
|
||||
actor_count_++;
|
||||
LOG_CHECK(actor_info->is_migrating()) << *actor_info << ' ' << actor_count_ << ' ' << sched_id_ << ' '
|
||||
<< actor_info->migrate_dest() << ' ' << actor_info->is_running() << ' '
|
||||
<< close_flag_;
|
||||
CHECK(sched_id_ == actor_info->migrate_dest());
|
||||
// CHECK(!actor_info->is_running());
|
||||
actor_info->finish_migrate();
|
||||
for (auto &event : actor_info->mailbox_) {
|
||||
finish_migrate(event);
|
||||
}
|
||||
auto it = pending_events_.find(actor_info);
|
||||
if (it != pending_events_.end()) {
|
||||
append(actor_info->mailbox_, std::move(it->second));
|
||||
pending_events_.erase(it);
|
||||
}
|
||||
if (actor_info->mailbox_.empty()) {
|
||||
pending_actors_list_.put(actor_info->get_list_node());
|
||||
} else {
|
||||
ready_actors_list_.put(actor_info->get_list_node());
|
||||
}
|
||||
actor_info->get_actor_unsafe()->on_finish_migrate();
|
||||
}
|
||||
|
||||
void Scheduler::send_to_other_scheduler(int32 sched_id, const ActorId<> &actor_id, Event &&event) {
|
||||
if (sched_id < sched_count()) {
|
||||
auto actor_info = actor_id.get_actor_info();
|
||||
if (actor_info) {
|
||||
VLOG(actor) << "Send to " << *actor_info << " on scheduler " << sched_id << ": " << event;
|
||||
} else {
|
||||
VLOG(actor) << "Send to scheduler " << sched_id << ": " << event;
|
||||
}
|
||||
start_migrate(event, sched_id);
|
||||
outbound_queues_[sched_id]->writer_put(EventCreator::event_unsafe(actor_id, std::move(event)));
|
||||
outbound_queues_[sched_id]->writer_flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::run_on_scheduler(int32 sched_id, Promise<Unit> action) {
|
||||
if (sched_id >= 0 && sched_id_ != sched_id) {
|
||||
class Worker final : public Actor {
|
||||
public:
|
||||
explicit Worker(Promise<Unit> action) : action_(std::move(action)) {
|
||||
}
|
||||
|
||||
private:
|
||||
Promise<Unit> action_;
|
||||
|
||||
void start_up() final {
|
||||
action_.set_value(Unit());
|
||||
stop();
|
||||
}
|
||||
};
|
||||
create_actor_on_scheduler<Worker>("RunOnSchedulerWorker", sched_id, std::move(action)).release();
|
||||
return;
|
||||
}
|
||||
|
||||
action.set_value(Unit());
|
||||
}
|
||||
|
||||
void Scheduler::destroy_on_scheduler_impl(int32 sched_id, Promise<Unit> action) {
|
||||
auto empty_context = std::make_shared<ActorContext>();
|
||||
empty_context->this_ptr_ = empty_context;
|
||||
ActorContext *current_context = context_;
|
||||
context_ = empty_context.get();
|
||||
|
||||
const char *current_tag = LOG_TAG;
|
||||
LOG_TAG = nullptr;
|
||||
|
||||
run_on_scheduler(sched_id, std::move(action));
|
||||
|
||||
context_ = current_context;
|
||||
LOG_TAG = current_tag;
|
||||
}
|
||||
|
||||
void Scheduler::add_to_mailbox(ActorInfo *actor_info, Event &&event) {
|
||||
if (!actor_info->is_running()) {
|
||||
auto node = actor_info->get_list_node();
|
||||
node->remove();
|
||||
ready_actors_list_.put(node);
|
||||
}
|
||||
VLOG(actor) << "Add to mailbox: " << *actor_info << " " << event;
|
||||
actor_info->mailbox_.push_back(std::move(event));
|
||||
}
|
||||
|
||||
void Scheduler::do_stop_actor(Actor *actor) {
|
||||
return do_stop_actor(actor->get_info());
|
||||
}
|
||||
|
||||
void Scheduler::do_stop_actor(ActorInfo *actor_info) {
|
||||
CHECK(!actor_info->is_migrating());
|
||||
LOG_CHECK(actor_info->migrate_dest() == sched_id_) << actor_info->migrate_dest() << " " << sched_id_;
|
||||
ObjectPool<ActorInfo>::OwnerPtr owner_ptr;
|
||||
if (actor_info->need_start_up()) {
|
||||
EventGuard guard(this, actor_info);
|
||||
do_event(actor_info, Event::stop());
|
||||
owner_ptr = actor_info->get_actor_unsafe()->clear();
|
||||
// Actor context is visible in destructor
|
||||
actor_info->destroy_actor();
|
||||
event_context_ptr_->flags = 0;
|
||||
} else {
|
||||
owner_ptr = actor_info->get_actor_unsafe()->clear();
|
||||
actor_info->destroy_actor();
|
||||
}
|
||||
destroy_actor(actor_info);
|
||||
}
|
||||
|
||||
void Scheduler::migrate_actor(Actor *actor, int32 dest_sched_id) {
|
||||
migrate_actor(actor->get_info(), dest_sched_id);
|
||||
}
|
||||
|
||||
void Scheduler::migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
|
||||
CHECK(event_context_ptr_->actor_info == actor_info);
|
||||
if (sched_id_ == dest_sched_id) {
|
||||
return;
|
||||
}
|
||||
event_context_ptr_->flags |= EventContext::Migrate;
|
||||
event_context_ptr_->dest_sched_id = dest_sched_id;
|
||||
}
|
||||
|
||||
void Scheduler::do_migrate_actor(Actor *actor, int32 dest_sched_id) {
|
||||
do_migrate_actor(actor->get_info(), dest_sched_id);
|
||||
}
|
||||
|
||||
void Scheduler::do_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
dest_sched_id = 0;
|
||||
#endif
|
||||
if (sched_id_ == dest_sched_id) {
|
||||
return;
|
||||
}
|
||||
start_migrate_actor(actor_info, dest_sched_id);
|
||||
send_to_other_scheduler(dest_sched_id, ActorId<>(), Event::raw(actor_info));
|
||||
}
|
||||
|
||||
void Scheduler::start_migrate_actor(Actor *actor, int32 dest_sched_id) {
|
||||
start_migrate_actor(actor->get_info(), dest_sched_id);
|
||||
}
|
||||
|
||||
void Scheduler::start_migrate_actor(ActorInfo *actor_info, int32 dest_sched_id) {
|
||||
VLOG(actor) << "Start migrate actor " << *actor_info << " to scheduler " << dest_sched_id << ", "
|
||||
<< tag("actor_count", actor_count_);
|
||||
actor_count_--;
|
||||
CHECK(actor_count_ >= 0);
|
||||
actor_info->get_actor_unsafe()->on_start_migrate(dest_sched_id);
|
||||
for (auto &event : actor_info->mailbox_) {
|
||||
start_migrate(event, dest_sched_id);
|
||||
}
|
||||
actor_info->start_migrate(dest_sched_id);
|
||||
actor_info->get_list_node()->remove();
|
||||
cancel_actor_timeout(actor_info);
|
||||
}
|
||||
|
||||
double Scheduler::get_actor_timeout(const ActorInfo *actor_info) const {
|
||||
const HeapNode *heap_node = actor_info->get_heap_node();
|
||||
return heap_node->in_heap() ? timeout_queue_.get_key(heap_node) - Time::now() : 0.0;
|
||||
}
|
||||
|
||||
void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) {
|
||||
if (timeout > 1e10) {
|
||||
timeout = 1e10;
|
||||
}
|
||||
if (timeout < 0) {
|
||||
timeout = 0;
|
||||
}
|
||||
double expires_at = Time::now() + timeout;
|
||||
set_actor_timeout_at(actor_info, expires_at);
|
||||
}
|
||||
|
||||
void Scheduler::set_actor_timeout_at(ActorInfo *actor_info, double timeout_at) {
|
||||
HeapNode *heap_node = actor_info->get_heap_node();
|
||||
VLOG(actor) << "Set actor " << *actor_info << " timeout in " << timeout_at - Time::now_cached();
|
||||
if (heap_node->in_heap()) {
|
||||
timeout_queue_.fix(timeout_at, heap_node);
|
||||
} else {
|
||||
timeout_queue_.insert(timeout_at, heap_node);
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::run_poll(Timestamp timeout) {
|
||||
// we can't wait for less than 1ms
|
||||
auto timeout_ms = static_cast<int>(clamp(timeout.in(), 0.0, 1000000.0) * 1000 + 1);
|
||||
#if TD_PORT_WINDOWS
|
||||
CHECK(inbound_queue_);
|
||||
inbound_queue_->reader_get_event_fd().wait(timeout_ms);
|
||||
service_actor_.notify();
|
||||
#elif TD_PORT_POSIX
|
||||
poll_.run(timeout_ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Scheduler::flush_mailbox(ActorInfo *actor_info) {
|
||||
auto &mailbox = actor_info->mailbox_;
|
||||
size_t mailbox_size = mailbox.size();
|
||||
CHECK(mailbox_size != 0);
|
||||
EventGuard guard(this, actor_info);
|
||||
size_t i = 0;
|
||||
for (; i < mailbox_size && guard.can_run(); i++) {
|
||||
do_event(actor_info, std::move(mailbox[i]));
|
||||
}
|
||||
mailbox.erase(mailbox.begin(), mailbox.begin() + i);
|
||||
}
|
||||
|
||||
void Scheduler::run_mailbox() {
|
||||
VLOG(actor) << "Run mailbox : begin";
|
||||
ListNode actors_list = std::move(ready_actors_list_);
|
||||
while (!actors_list.empty()) {
|
||||
ListNode *node = actors_list.get();
|
||||
CHECK(node);
|
||||
auto actor_info = ActorInfo::from_list_node(node);
|
||||
flush_mailbox(actor_info);
|
||||
}
|
||||
VLOG(actor) << "Run mailbox : finish " << actor_count_;
|
||||
|
||||
//Useful for debug, but O(ActorsCount) check
|
||||
|
||||
//int cnt = 0;
|
||||
//for (ListNode *end = &pending_actors_list_, *it = pending_actors_list_.next; it != end; it = it->next) {
|
||||
//cnt++;
|
||||
//auto actor_info = ActorInfo::from_list_node(it);
|
||||
//LOG(ERROR) << *actor_info;
|
||||
//CHECK(actor_info->mailbox_.empty());
|
||||
//CHECK(!actor_info->is_running());
|
||||
//}
|
||||
//for (ListNode *end = &ready_actors_list_, *it = ready_actors_list_.next; it != end; it = it->next) {
|
||||
//auto actor_info = ActorInfo::from_list_node(it);
|
||||
//LOG(ERROR) << *actor_info;
|
||||
//cnt++;
|
||||
//}
|
||||
//LOG_CHECK(cnt == actor_count_) << cnt << " vs " << actor_count_;
|
||||
}
|
||||
|
||||
Timestamp Scheduler::run_timeout() {
|
||||
double now = Time::now();
|
||||
//TODO: use Timestamp().is_in_past()
|
||||
while (!timeout_queue_.empty() && timeout_queue_.top_key() < now) {
|
||||
HeapNode *node = timeout_queue_.pop();
|
||||
ActorInfo *actor_info = ActorInfo::from_heap_node(node);
|
||||
send_immediately(actor_info->actor_id(), Event::timeout());
|
||||
}
|
||||
return get_timeout();
|
||||
}
|
||||
|
||||
Timestamp Scheduler::run_events(Timestamp timeout) {
|
||||
Timestamp res;
|
||||
VLOG(actor) << "Run events " << sched_id_ << " " << tag("pending", pending_events_.size())
|
||||
<< tag("actors", actor_count_);
|
||||
do {
|
||||
run_mailbox();
|
||||
res = run_timeout();
|
||||
} while (!ready_actors_list_.empty() && !timeout.is_in_past());
|
||||
return res;
|
||||
}
|
||||
|
||||
void Scheduler::run_no_guard(Timestamp timeout) {
|
||||
CHECK(has_guard_);
|
||||
SCOPE_EXIT {
|
||||
yield_flag_ = false;
|
||||
};
|
||||
|
||||
timeout.relax(run_events(timeout));
|
||||
if (yield_flag_) {
|
||||
return;
|
||||
}
|
||||
run_poll(timeout);
|
||||
run_events(timeout);
|
||||
}
|
||||
|
||||
Timestamp Scheduler::get_timeout() {
|
||||
if (!ready_actors_list_.empty()) {
|
||||
return Timestamp::in(0);
|
||||
}
|
||||
if (timeout_queue_.empty()) {
|
||||
return Timestamp::in(10000);
|
||||
}
|
||||
return Timestamp::at(timeout_queue_.top_key());
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
370
td/tdactor/td/actor/impl/Scheduler.h
Normal file
370
td/tdactor/td/actor/impl/Scheduler.h
Normal file
@@ -0,0 +1,370 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "td/actor/impl/ActorInfo-decl.h"
|
||||
#include "td/actor/impl/Scheduler-decl.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/Heap.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/ObjectPool.h"
|
||||
#include "td/utils/port/detail/PollableFd.h"
|
||||
#include "td/utils/port/PollFlags.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace td {
|
||||
|
||||
/*** EventGuard ***/
|
||||
class EventGuard {
|
||||
public:
|
||||
EventGuard(Scheduler *scheduler, ActorInfo *actor_info);
|
||||
|
||||
bool can_run() const {
|
||||
return event_context_.flags == 0;
|
||||
}
|
||||
|
||||
EventGuard(const EventGuard &) = delete;
|
||||
EventGuard &operator=(const EventGuard &) = delete;
|
||||
EventGuard(EventGuard &&) = delete;
|
||||
EventGuard &operator=(EventGuard &&) = delete;
|
||||
~EventGuard();
|
||||
|
||||
private:
|
||||
Scheduler::EventContext event_context_;
|
||||
Scheduler::EventContext *event_context_ptr_;
|
||||
Scheduler *scheduler_;
|
||||
ActorContext *save_context_;
|
||||
const char *save_log_tag2_;
|
||||
|
||||
void swap_context(ActorInfo *info);
|
||||
};
|
||||
|
||||
/*** Scheduler ***/
|
||||
inline SchedulerGuard Scheduler::get_guard() {
|
||||
return SchedulerGuard(this);
|
||||
}
|
||||
|
||||
inline SchedulerGuard Scheduler::get_const_guard() {
|
||||
return SchedulerGuard(this, false);
|
||||
}
|
||||
|
||||
inline int32 Scheduler::sched_id() const {
|
||||
return sched_id_;
|
||||
}
|
||||
|
||||
inline int32 Scheduler::sched_count() const {
|
||||
return sched_n_;
|
||||
}
|
||||
|
||||
template <class ActorT, class... Args>
|
||||
ActorOwn<ActorT> Scheduler::create_actor(Slice name, Args &&...args) {
|
||||
return register_actor_impl(name, new ActorT(std::forward<Args>(args)...), Actor::Deleter::Destroy, sched_id_);
|
||||
}
|
||||
|
||||
template <class ActorT, class... Args>
|
||||
ActorOwn<ActorT> Scheduler::create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args) {
|
||||
return register_actor_impl(name, new ActorT(std::forward<Args>(args)...), Actor::Deleter::Destroy, sched_id);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> Scheduler::register_actor(Slice name, ActorT *actor_ptr, int32 sched_id) {
|
||||
return register_actor_impl(name, actor_ptr, Actor::Deleter::None, sched_id);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> Scheduler::register_actor(Slice name, unique_ptr<ActorT> actor_ptr, int32 sched_id) {
|
||||
return register_actor_impl(name, actor_ptr.release(), Actor::Deleter::Destroy, sched_id);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> Scheduler::register_actor_impl(Slice name, ActorT *actor_ptr, Actor::Deleter deleter, int32 sched_id) {
|
||||
CHECK(has_guard_);
|
||||
if (sched_id == -1) {
|
||||
sched_id = sched_id_;
|
||||
}
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
sched_id = 0;
|
||||
#endif
|
||||
LOG_CHECK(sched_id == sched_id_ || (0 <= sched_id && sched_id < static_cast<int32>(outbound_queues_.size())))
|
||||
<< sched_id;
|
||||
auto info = actor_info_pool_->create_empty();
|
||||
actor_count_++;
|
||||
auto weak_info = info.get_weak();
|
||||
auto actor_info = info.get();
|
||||
actor_info->init(sched_id_, name, std::move(info), static_cast<Actor *>(actor_ptr), deleter,
|
||||
ActorTraits<ActorT>::need_context, ActorTraits<ActorT>::need_start_up);
|
||||
VLOG(actor) << "Create actor " << *actor_info << " (actor_count = " << actor_count_ << ')';
|
||||
|
||||
ActorId<ActorT> actor_id = weak_info->actor_id(actor_ptr);
|
||||
if (sched_id != sched_id_) {
|
||||
send_later(actor_id, Event::start());
|
||||
do_migrate_actor(actor_info, sched_id);
|
||||
} else {
|
||||
pending_actors_list_.put(weak_info->get_list_node());
|
||||
if (ActorTraits<ActorT>::need_start_up) {
|
||||
send_later(actor_id, Event::start());
|
||||
}
|
||||
}
|
||||
|
||||
return ActorOwn<ActorT>(actor_id);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> Scheduler::register_existing_actor(unique_ptr<ActorT> actor_ptr) {
|
||||
CHECK(!actor_ptr->empty());
|
||||
auto actor_info = actor_ptr->get_info();
|
||||
CHECK(actor_info->migrate_dest_flag_atomic().first == sched_id_);
|
||||
return actor_info->transfer_ownership_to_scheduler(std::move(actor_ptr));
|
||||
}
|
||||
|
||||
inline void Scheduler::destroy_actor(ActorInfo *actor_info) {
|
||||
VLOG(actor) << "Destroy actor " << *actor_info << " (actor_count = " << actor_count_ << ')';
|
||||
|
||||
LOG_CHECK(actor_info->migrate_dest() == sched_id_) << actor_info->migrate_dest() << " " << sched_id_;
|
||||
cancel_actor_timeout(actor_info);
|
||||
actor_info->get_list_node()->remove();
|
||||
// called by ObjectPool
|
||||
// actor_info->clear();
|
||||
actor_count_--;
|
||||
CHECK(actor_count_ >= 0);
|
||||
}
|
||||
|
||||
inline void Scheduler::send_to_scheduler(int32 sched_id, const ActorId<Actor> &actor_id, Event &&event) {
|
||||
if (sched_id == sched_id_) {
|
||||
ActorInfo *actor_info = actor_id.get_actor_info();
|
||||
pending_events_[actor_info].push_back(std::move(event));
|
||||
} else {
|
||||
send_to_other_scheduler(sched_id, actor_id, std::move(event));
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Scheduler::destroy_on_scheduler(int32 sched_id, T &value) {
|
||||
if (!value.empty()) {
|
||||
destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([value = std::move(value)](Unit) {
|
||||
// destroy value
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Scheduler::destroy_on_scheduler_unique_ptr(int32 sched_id, T &value) {
|
||||
if (value != nullptr) {
|
||||
destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([value = std::move(value)](Unit) {
|
||||
// destroy value
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
void Scheduler::destroy_on_scheduler(int32 sched_id, ArgsT &...values) {
|
||||
destroy_on_scheduler_impl(sched_id, PromiseCreator::lambda([values = std::make_tuple(std::move(values)...)](Unit) {
|
||||
// destroy values
|
||||
}));
|
||||
}
|
||||
|
||||
inline void Scheduler::before_tail_send(const ActorId<> &actor_id) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
template <class RunFuncT, class EventFuncT>
|
||||
void Scheduler::send_immediately_impl(const ActorId<> &actor_id, const RunFuncT &run_func,
|
||||
const EventFuncT &event_func) {
|
||||
ActorInfo *actor_info = actor_id.get_actor_info();
|
||||
if (unlikely(actor_info == nullptr || close_flag_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32 actor_sched_id;
|
||||
bool on_current_sched;
|
||||
bool can_send_immediately;
|
||||
get_actor_sched_id_to_send_immediately(actor_info, actor_sched_id, on_current_sched, can_send_immediately);
|
||||
|
||||
if (likely(can_send_immediately)) { // run immediately
|
||||
EventGuard guard(this, actor_info);
|
||||
run_func(actor_info);
|
||||
} else {
|
||||
if (on_current_sched) {
|
||||
add_to_mailbox(actor_info, event_func());
|
||||
} else {
|
||||
send_to_scheduler(actor_sched_id, actor_id, event_func());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class EventT>
|
||||
void Scheduler::send_lambda_immediately(ActorRef actor_ref, EventT &&func) {
|
||||
return send_immediately_impl(
|
||||
actor_ref.get(),
|
||||
[&](ActorInfo *actor_info) {
|
||||
event_context_ptr_->link_token = actor_ref.token();
|
||||
func();
|
||||
},
|
||||
[&] {
|
||||
auto event = Event::from_lambda(std::forward<EventT>(func));
|
||||
event.set_link_token(actor_ref.token());
|
||||
return event;
|
||||
});
|
||||
}
|
||||
|
||||
template <class EventT>
|
||||
void Scheduler::send_lambda_later(ActorRef actor_ref, EventT &&func) {
|
||||
auto event = Event::from_lambda(std::forward<EventT>(func));
|
||||
event.set_link_token(actor_ref.token());
|
||||
return send_later_impl(actor_ref.get(), std::move(event));
|
||||
}
|
||||
|
||||
template <class EventT>
|
||||
void Scheduler::send_closure_immediately(ActorRef actor_ref, EventT &&closure) {
|
||||
return send_immediately_impl(
|
||||
actor_ref.get(),
|
||||
[&](ActorInfo *actor_info) {
|
||||
event_context_ptr_->link_token = actor_ref.token();
|
||||
closure.run(static_cast<typename EventT::ActorType *>(actor_info->get_actor_unsafe()));
|
||||
},
|
||||
[&] {
|
||||
auto event = Event::immediate_closure(std::forward<EventT>(closure));
|
||||
event.set_link_token(actor_ref.token());
|
||||
return event;
|
||||
});
|
||||
}
|
||||
|
||||
template <class EventT>
|
||||
void Scheduler::send_closure_later(ActorRef actor_ref, EventT &&closure) {
|
||||
auto event = Event::immediate_closure(std::forward<EventT>(closure));
|
||||
event.set_link_token(actor_ref.token());
|
||||
return send_later_impl(actor_ref.get(), std::move(event));
|
||||
}
|
||||
|
||||
inline void Scheduler::send_immediately(ActorRef actor_ref, Event &&event) {
|
||||
event.set_link_token(actor_ref.token());
|
||||
return send_immediately_impl(
|
||||
actor_ref.get(), [&](ActorInfo *actor_info) { do_event(actor_info, std::move(event)); },
|
||||
[&] { return std::move(event); });
|
||||
}
|
||||
|
||||
inline void Scheduler::send_later(ActorRef actor_ref, Event &&event) {
|
||||
event.set_link_token(actor_ref.token());
|
||||
return send_later_impl(actor_ref.get(), std::move(event));
|
||||
}
|
||||
|
||||
inline void Scheduler::subscribe(PollableFd fd, PollFlags flags) {
|
||||
instance()->poll_.subscribe(std::move(fd), flags);
|
||||
}
|
||||
|
||||
inline void Scheduler::unsubscribe(PollableFdRef fd) {
|
||||
instance()->poll_.unsubscribe(std::move(fd));
|
||||
}
|
||||
|
||||
inline void Scheduler::unsubscribe_before_close(PollableFdRef fd) {
|
||||
instance()->poll_.unsubscribe_before_close(std::move(fd));
|
||||
}
|
||||
|
||||
inline void Scheduler::yield_actor(Actor *actor) {
|
||||
yield_actor(actor->get_info());
|
||||
}
|
||||
inline void Scheduler::yield_actor(ActorInfo *actor_info) {
|
||||
send_later(actor_info->actor_id(), Event::yield());
|
||||
}
|
||||
|
||||
inline void Scheduler::stop_actor(Actor *actor) {
|
||||
stop_actor(actor->get_info());
|
||||
}
|
||||
inline void Scheduler::stop_actor(ActorInfo *actor_info) {
|
||||
CHECK(event_context_ptr_->actor_info == actor_info);
|
||||
event_context_ptr_->flags |= EventContext::Stop;
|
||||
}
|
||||
|
||||
inline uint64 Scheduler::get_link_token(Actor *actor) {
|
||||
return get_link_token(actor->get_info());
|
||||
}
|
||||
inline uint64 Scheduler::get_link_token(ActorInfo *actor_info) {
|
||||
LOG_CHECK(event_context_ptr_->actor_info == actor_info) << actor_info->get_name();
|
||||
return event_context_ptr_->link_token;
|
||||
}
|
||||
|
||||
inline void Scheduler::finish_migrate_actor(Actor *actor) {
|
||||
register_migrated_actor(actor->get_info());
|
||||
}
|
||||
|
||||
inline double Scheduler::get_actor_timeout(const Actor *actor) const {
|
||||
return get_actor_timeout(actor->get_info());
|
||||
}
|
||||
inline void Scheduler::set_actor_timeout_in(Actor *actor, double timeout) {
|
||||
set_actor_timeout_in(actor->get_info(), timeout);
|
||||
}
|
||||
inline void Scheduler::set_actor_timeout_at(Actor *actor, double timeout_at) {
|
||||
set_actor_timeout_at(actor->get_info(), timeout_at);
|
||||
}
|
||||
inline void Scheduler::cancel_actor_timeout(Actor *actor) {
|
||||
cancel_actor_timeout(actor->get_info());
|
||||
}
|
||||
|
||||
inline void Scheduler::cancel_actor_timeout(ActorInfo *actor_info) {
|
||||
HeapNode *heap_node = actor_info->get_heap_node();
|
||||
if (heap_node->in_heap()) {
|
||||
timeout_queue_.erase(heap_node);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Scheduler::finish() {
|
||||
if (callback_) {
|
||||
callback_->on_finish();
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
inline void Scheduler::yield() {
|
||||
yield_flag_ = true;
|
||||
}
|
||||
|
||||
inline void Scheduler::wakeup() {
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
inbound_queue_->writer_put({});
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Scheduler::run(Timestamp timeout) {
|
||||
auto guard = get_guard();
|
||||
run_no_guard(timeout);
|
||||
}
|
||||
|
||||
/*** Interface to current scheduler ***/
|
||||
template <class ActorT, class... Args>
|
||||
ActorOwn<ActorT> create_actor(Slice name, Args &&...args) {
|
||||
return Scheduler::instance()->create_actor<ActorT>(name, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class ActorT, class... Args>
|
||||
ActorOwn<ActorT> create_actor_on_scheduler(Slice name, int32 sched_id, Args &&...args) {
|
||||
return Scheduler::instance()->create_actor_on_scheduler<ActorT>(name, sched_id, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> register_actor(Slice name, ActorT *actor_ptr, int32 sched_id) {
|
||||
return Scheduler::instance()->register_actor<ActorT>(name, actor_ptr, sched_id);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> register_actor(Slice name, unique_ptr<ActorT> actor_ptr, int32 sched_id) {
|
||||
return Scheduler::instance()->register_actor<ActorT>(name, std::move(actor_ptr), sched_id);
|
||||
}
|
||||
|
||||
template <class ActorT>
|
||||
ActorOwn<ActorT> register_existing_actor(unique_ptr<ActorT> actor_ptr) {
|
||||
return Scheduler::instance()->register_existing_actor(std::move(actor_ptr));
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
112
td/tdactor/test/actors_bugs.cpp
Normal file
112
td/tdactor/test/actors_bugs.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
#include "td/actor/MultiTimeout.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/tests.h"
|
||||
|
||||
TEST(MultiTimeout, bug) {
|
||||
td::ConcurrentScheduler sched(0, 0);
|
||||
|
||||
sched.start();
|
||||
td::unique_ptr<td::MultiTimeout> multi_timeout;
|
||||
struct Data {
|
||||
td::MultiTimeout *multi_timeout;
|
||||
};
|
||||
Data data;
|
||||
|
||||
{
|
||||
auto guard = sched.get_main_guard();
|
||||
multi_timeout = td::make_unique<td::MultiTimeout>("MultiTimeout");
|
||||
data.multi_timeout = multi_timeout.get();
|
||||
multi_timeout->set_callback([](void *void_data, td::int64 key) {
|
||||
auto &data = *static_cast<Data *>(void_data);
|
||||
if (key == 1) {
|
||||
data.multi_timeout->cancel_timeout(key + 1);
|
||||
data.multi_timeout->set_timeout_in(key + 2, 1);
|
||||
} else {
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
});
|
||||
multi_timeout->set_callback_data(&data);
|
||||
multi_timeout->set_timeout_in(1, 1);
|
||||
multi_timeout->set_timeout_in(2, 2);
|
||||
}
|
||||
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
|
||||
class TimeoutManager final : public td::Actor {
|
||||
static td::int32 count;
|
||||
|
||||
public:
|
||||
TimeoutManager() {
|
||||
count++;
|
||||
|
||||
test_timeout_.set_callback(on_test_timeout_callback);
|
||||
test_timeout_.set_callback_data(static_cast<void *>(this));
|
||||
}
|
||||
TimeoutManager(const TimeoutManager &) = delete;
|
||||
TimeoutManager &operator=(const TimeoutManager &) = delete;
|
||||
TimeoutManager(TimeoutManager &&) = delete;
|
||||
TimeoutManager &operator=(TimeoutManager &&) = delete;
|
||||
~TimeoutManager() final {
|
||||
count--;
|
||||
LOG(INFO) << "Destroy TimeoutManager";
|
||||
}
|
||||
|
||||
static void on_test_timeout_callback(void *timeout_manager_ptr, td::int64 id) {
|
||||
CHECK(count >= 0);
|
||||
if (count == 0) {
|
||||
LOG(ERROR) << "Receive timeout after manager was closed";
|
||||
return;
|
||||
}
|
||||
|
||||
auto manager = static_cast<TimeoutManager *>(timeout_manager_ptr);
|
||||
send_closure_later(manager->actor_id(manager), &TimeoutManager::test_timeout);
|
||||
}
|
||||
|
||||
void test_timeout() {
|
||||
CHECK(count > 0);
|
||||
// we must yield scheduler, so run_main breaks immediately, if timeouts are handled immediately
|
||||
td::Scheduler::instance()->yield();
|
||||
}
|
||||
|
||||
td::MultiTimeout test_timeout_{"TestTimeout"};
|
||||
};
|
||||
|
||||
td::int32 TimeoutManager::count;
|
||||
|
||||
TEST(MultiTimeout, Destroy) {
|
||||
td::ConcurrentScheduler sched(0, 0);
|
||||
|
||||
auto timeout_manager = sched.create_actor_unsafe<TimeoutManager>(0, "TimeoutManager");
|
||||
TimeoutManager *manager = timeout_manager.get().get_actor_unsafe();
|
||||
sched.start();
|
||||
int cnt = 100;
|
||||
while (sched.run_main(cnt == 100 || cnt <= 0 ? 0.001 : 10)) {
|
||||
auto guard = sched.get_main_guard();
|
||||
cnt--;
|
||||
if (cnt > 0) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
manager->test_timeout_.set_timeout_in(td::Random::fast(0, 1000000000), td::Random::fast(2, 5) / 1000.0);
|
||||
}
|
||||
} else if (cnt == 0) {
|
||||
timeout_manager.reset();
|
||||
} else if (cnt == -10) {
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
506
td/tdactor/test/actors_main.cpp
Normal file
506
td/tdactor/test/actors_main.cpp
Normal file
@@ -0,0 +1,506 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/tests.h"
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
template <class ContainerT>
|
||||
static typename ContainerT::value_type &rand_elem(ContainerT &cont) {
|
||||
CHECK(0 < cont.size() && cont.size() <= static_cast<size_t>(std::numeric_limits<int>::max()));
|
||||
return cont[td::Random::fast(0, static_cast<int>(cont.size()) - 1)];
|
||||
}
|
||||
|
||||
static td::uint32 fast_pow_mod_uint32(td::uint32 x, td::uint32 p) {
|
||||
td::uint32 res = 1;
|
||||
while (p) {
|
||||
if (p & 1) {
|
||||
res *= x;
|
||||
}
|
||||
x *= x;
|
||||
p >>= 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static td::uint32 slow_pow_mod_uint32(td::uint32 x, td::uint32 p) {
|
||||
td::uint32 res = 1;
|
||||
for (td::uint32 i = 0; i < p; i++) {
|
||||
res *= x;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
struct ActorQuery {
|
||||
td::uint32 query_id{};
|
||||
td::uint32 result{};
|
||||
td::vector<int> todo;
|
||||
ActorQuery() = default;
|
||||
ActorQuery(const ActorQuery &) = delete;
|
||||
ActorQuery &operator=(const ActorQuery &) = delete;
|
||||
ActorQuery(ActorQuery &&) = default;
|
||||
ActorQuery &operator=(ActorQuery &&) = default;
|
||||
~ActorQuery() {
|
||||
LOG_CHECK(todo.empty()) << "ActorQuery lost";
|
||||
}
|
||||
int next_pow() {
|
||||
CHECK(!todo.empty());
|
||||
int res = todo.back();
|
||||
todo.pop_back();
|
||||
return res;
|
||||
}
|
||||
bool ready() {
|
||||
return todo.empty();
|
||||
}
|
||||
};
|
||||
|
||||
static td::uint32 fast_calc(ActorQuery &q) {
|
||||
td::uint32 result = q.result;
|
||||
for (auto x : q.todo) {
|
||||
result = fast_pow_mod_uint32(result, x);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class Worker final : public td::Actor {
|
||||
public:
|
||||
explicit Worker(int threads_n) : threads_n_(threads_n) {
|
||||
}
|
||||
void query(td::PromiseActor<td::uint32> &&promise, td::uint32 x, td::uint32 p) {
|
||||
td::uint32 result = slow_pow_mod_uint32(x, p);
|
||||
promise.set_value(std::move(result));
|
||||
|
||||
(void)threads_n_;
|
||||
// if (threads_n_ > 1 && td::Random::fast(0, 9) == 0) {
|
||||
// migrate(td::Random::fast(2, threads_n));
|
||||
//}
|
||||
}
|
||||
|
||||
private:
|
||||
int threads_n_;
|
||||
};
|
||||
|
||||
class QueryActor final : public td::Actor {
|
||||
public:
|
||||
class Callback {
|
||||
public:
|
||||
Callback() = default;
|
||||
Callback(const Callback &) = delete;
|
||||
Callback &operator=(const Callback &) = delete;
|
||||
Callback(Callback &&) = delete;
|
||||
Callback &operator=(Callback &&) = delete;
|
||||
virtual ~Callback() = default;
|
||||
virtual void on_result(ActorQuery &&query) = 0;
|
||||
virtual void on_closed() = 0;
|
||||
};
|
||||
|
||||
explicit QueryActor(int threads_n) : threads_n_(threads_n) {
|
||||
}
|
||||
|
||||
void set_callback(td::unique_ptr<Callback> callback) {
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
void set_workers(td::vector<td::ActorId<Worker>> workers) {
|
||||
workers_ = std::move(workers);
|
||||
}
|
||||
|
||||
void query(ActorQuery &&query) {
|
||||
td::uint32 x = query.result;
|
||||
td::uint32 p = query.next_pow();
|
||||
if (td::Random::fast(0, 3) && (p <= 1000 || workers_.empty())) {
|
||||
query.result = slow_pow_mod_uint32(x, p);
|
||||
callback_->on_result(std::move(query));
|
||||
} else {
|
||||
auto future = td::Random::fast(0, 3) == 0
|
||||
? td::send_promise_immediately(rand_elem(workers_), &Worker::query, x, p)
|
||||
: td::send_promise_later(rand_elem(workers_), &Worker::query, x, p);
|
||||
if (future.is_ready()) {
|
||||
query.result = future.move_as_ok();
|
||||
callback_->on_result(std::move(query));
|
||||
} else {
|
||||
future.set_event(td::EventCreator::raw(actor_id(), query.query_id));
|
||||
auto query_id = query.query_id;
|
||||
pending_.emplace(query_id, std::make_pair(std::move(future), std::move(query)));
|
||||
}
|
||||
}
|
||||
if (threads_n_ > 1 && td::Random::fast(0, 9) == 0) {
|
||||
migrate(td::Random::fast(2, threads_n_));
|
||||
}
|
||||
}
|
||||
|
||||
void raw_event(const td::Event::Raw &event) final {
|
||||
td::uint32 id = event.u32;
|
||||
auto it = pending_.find(id);
|
||||
auto future = std::move(it->second.first);
|
||||
auto query = std::move(it->second.second);
|
||||
pending_.erase(it);
|
||||
CHECK(future.is_ready());
|
||||
query.result = future.move_as_ok();
|
||||
callback_->on_result(std::move(query));
|
||||
}
|
||||
|
||||
void close() {
|
||||
callback_->on_closed();
|
||||
stop();
|
||||
}
|
||||
|
||||
void on_start_migrate(td::int32 sched_id) final {
|
||||
for (auto &it : pending_) {
|
||||
start_migrate(it.second.first, sched_id);
|
||||
}
|
||||
}
|
||||
void on_finish_migrate() final {
|
||||
for (auto &it : pending_) {
|
||||
finish_migrate(it.second.first);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
td::unique_ptr<Callback> callback_;
|
||||
std::map<td::uint32, std::pair<td::FutureActor<td::uint32>, ActorQuery>> pending_;
|
||||
td::vector<td::ActorId<Worker>> workers_;
|
||||
int threads_n_;
|
||||
};
|
||||
|
||||
class MainQueryActor final : public td::Actor {
|
||||
class QueryActorCallback final : public QueryActor::Callback {
|
||||
public:
|
||||
void on_result(ActorQuery &&query) final {
|
||||
if (query.ready()) {
|
||||
send_closure(parent_id_, &MainQueryActor::on_result, std::move(query));
|
||||
} else {
|
||||
send_closure(next_solver_, &QueryActor::query, std::move(query));
|
||||
}
|
||||
}
|
||||
void on_closed() final {
|
||||
send_closure(parent_id_, &MainQueryActor::on_closed);
|
||||
}
|
||||
QueryActorCallback(td::ActorId<MainQueryActor> parent_id, td::ActorId<QueryActor> next_solver)
|
||||
: parent_id_(parent_id), next_solver_(next_solver) {
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorId<MainQueryActor> parent_id_;
|
||||
td::ActorId<QueryActor> next_solver_;
|
||||
};
|
||||
|
||||
const int ACTORS_CNT = 10;
|
||||
const int WORKERS_CNT = 4;
|
||||
|
||||
public:
|
||||
explicit MainQueryActor(int threads_n) : threads_n_(threads_n) {
|
||||
}
|
||||
|
||||
void start_up() final {
|
||||
actors_.resize(ACTORS_CNT);
|
||||
for (auto &actor : actors_) {
|
||||
auto actor_ptr = td::make_unique<QueryActor>(threads_n_);
|
||||
actor = register_actor("QueryActor", std::move(actor_ptr), threads_n_ > 1 ? td::Random::fast(2, threads_n_) : 0)
|
||||
.release();
|
||||
}
|
||||
|
||||
workers_.resize(WORKERS_CNT);
|
||||
for (auto &worker : workers_) {
|
||||
auto actor_ptr = td::make_unique<Worker>(threads_n_);
|
||||
worker = register_actor("Worker", std::move(actor_ptr), threads_n_ > 1 ? td::Random::fast(2, threads_n_) : 0)
|
||||
.release();
|
||||
}
|
||||
|
||||
for (int i = 0; i < ACTORS_CNT; i++) {
|
||||
ref_cnt_++;
|
||||
send_closure(actors_[i], &QueryActor::set_callback,
|
||||
td::make_unique<QueryActorCallback>(actor_id(this), actors_[(i + 1) % ACTORS_CNT]));
|
||||
send_closure(actors_[i], &QueryActor::set_workers, workers_);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
void on_result(ActorQuery &&query) {
|
||||
CHECK(query.ready());
|
||||
CHECK(query.result == expected_[query.query_id]);
|
||||
in_cnt_++;
|
||||
wakeup();
|
||||
}
|
||||
|
||||
ActorQuery create_query() {
|
||||
ActorQuery q;
|
||||
q.query_id = (query_id_ += 2);
|
||||
q.result = q.query_id;
|
||||
q.todo = {1, 1, 1, 1, 1, 1, 1, 1, 10000};
|
||||
expected_[q.query_id] = fast_calc(q);
|
||||
return q;
|
||||
}
|
||||
|
||||
void on_closed() {
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
}
|
||||
|
||||
void wakeup() final {
|
||||
int cnt = 10000;
|
||||
while (out_cnt_ < in_cnt_ + 100 && out_cnt_ < cnt) {
|
||||
if (td::Random::fast_bool()) {
|
||||
send_closure(rand_elem(actors_), &QueryActor::query, create_query());
|
||||
} else {
|
||||
send_closure_later(rand_elem(actors_), &QueryActor::query, create_query());
|
||||
}
|
||||
out_cnt_++;
|
||||
}
|
||||
if (in_cnt_ == cnt) {
|
||||
in_cnt_++;
|
||||
ref_cnt_--;
|
||||
for (auto &actor : actors_) {
|
||||
send_closure(actor, &QueryActor::close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<td::uint32, td::uint32> expected_;
|
||||
td::vector<td::ActorId<QueryActor>> actors_;
|
||||
td::vector<td::ActorId<Worker>> workers_;
|
||||
int out_cnt_ = 0;
|
||||
int in_cnt_ = 0;
|
||||
int query_id_ = 1;
|
||||
int ref_cnt_ = 1;
|
||||
int threads_n_;
|
||||
};
|
||||
|
||||
class SimpleActor final : public td::Actor {
|
||||
public:
|
||||
explicit SimpleActor(td::int32 threads_n) : threads_n_(threads_n) {
|
||||
}
|
||||
void start_up() final {
|
||||
auto actor_ptr = td::make_unique<Worker>(threads_n_);
|
||||
worker_ =
|
||||
register_actor("Worker", std::move(actor_ptr), threads_n_ > 1 ? td::Random::fast(2, threads_n_) : 0).release();
|
||||
yield();
|
||||
}
|
||||
|
||||
void wakeup() final {
|
||||
if (q_ == 10000) {
|
||||
td::Scheduler::instance()->finish();
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
q_++;
|
||||
p_ = td::Random::fast_bool() ? 1 : 10000;
|
||||
auto future = td::Random::fast(0, 3) == 0 ? td::send_promise_immediately(worker_, &Worker::query, q_, p_)
|
||||
: td::send_promise_later(worker_, &Worker::query, q_, p_);
|
||||
if (future.is_ready()) {
|
||||
auto result = future.move_as_ok();
|
||||
CHECK(result == fast_pow_mod_uint32(q_, p_));
|
||||
yield();
|
||||
} else {
|
||||
future.set_event(td::EventCreator::raw(actor_id(), nullptr));
|
||||
future_ = std::move(future);
|
||||
}
|
||||
// if (threads_n_ > 1 && td::Random::fast(0, 2) == 0) {
|
||||
// migrate(td::Random::fast(1, threads_n));
|
||||
//}
|
||||
}
|
||||
void raw_event(const td::Event::Raw &event) final {
|
||||
auto result = future_.move_as_ok();
|
||||
CHECK(result == fast_pow_mod_uint32(q_, p_));
|
||||
yield();
|
||||
}
|
||||
|
||||
void on_start_migrate(td::int32 sched_id) final {
|
||||
start_migrate(future_, sched_id);
|
||||
}
|
||||
void on_finish_migrate() final {
|
||||
finish_migrate(future_);
|
||||
}
|
||||
|
||||
private:
|
||||
td::int32 threads_n_;
|
||||
td::ActorId<Worker> worker_;
|
||||
td::FutureActor<td::uint32> future_;
|
||||
td::uint32 q_ = 1;
|
||||
td::uint32 p_ = 0;
|
||||
};
|
||||
|
||||
class SendToDead final : public td::Actor {
|
||||
public:
|
||||
class Parent final : public td::Actor {
|
||||
public:
|
||||
explicit Parent(td::ActorShared<> parent, int ttl = 3) : parent_(std::move(parent)), ttl_(ttl) {
|
||||
}
|
||||
void start_up() final {
|
||||
set_timeout_in(td::Random::fast_uint32() % 3 * 0.001);
|
||||
if (ttl_ != 0) {
|
||||
child_ = td::create_actor_on_scheduler<Parent>(
|
||||
"Child", td::Random::fast_uint32() % td::Scheduler::instance()->sched_count(), actor_shared(this),
|
||||
ttl_ - 1);
|
||||
}
|
||||
}
|
||||
void timeout_expired() final {
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorOwn<Parent> child_;
|
||||
td::ActorShared<> parent_;
|
||||
int ttl_;
|
||||
};
|
||||
|
||||
void start_up() final {
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
td::create_actor_on_scheduler<Parent>(
|
||||
"Parent", td::Random::fast_uint32() % td::Scheduler::instance()->sched_count(), create_reference(), 4)
|
||||
.release();
|
||||
}
|
||||
}
|
||||
|
||||
td::ActorShared<> create_reference() {
|
||||
ref_cnt_++;
|
||||
return actor_shared(this);
|
||||
}
|
||||
|
||||
void hangup_shared() final {
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
ttl_--;
|
||||
if (ttl_ <= 0) {
|
||||
td::Scheduler::instance()->finish();
|
||||
stop();
|
||||
} else {
|
||||
start_up();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td::uint32 ttl_{50};
|
||||
td::uint32 ref_cnt_{0};
|
||||
};
|
||||
|
||||
TEST(Actors, send_to_dead) {
|
||||
//TODO: fix CHECK(storage_count_.load() == 0)
|
||||
return;
|
||||
int threads_n = 5;
|
||||
td::ConcurrentScheduler sched(threads_n, 0);
|
||||
|
||||
sched.create_actor_unsafe<SendToDead>(0, "SendToDead").release();
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
|
||||
TEST(Actors, main_simple) {
|
||||
int threads_n = 3;
|
||||
td::ConcurrentScheduler sched(threads_n, 0);
|
||||
|
||||
sched.create_actor_unsafe<SimpleActor>(threads_n > 1 ? 1 : 0, "simple", threads_n).release();
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
|
||||
TEST(Actors, main) {
|
||||
int threads_n = 9;
|
||||
td::ConcurrentScheduler sched(threads_n, 0);
|
||||
|
||||
sched.create_actor_unsafe<MainQueryActor>(threads_n > 1 ? 1 : 0, "MainQuery", threads_n).release();
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
|
||||
class DoAfterStop final : public td::Actor {
|
||||
public:
|
||||
void loop() final {
|
||||
ptr = td::make_unique<int>(10);
|
||||
stop();
|
||||
CHECK(*ptr == 10);
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
|
||||
private:
|
||||
td::unique_ptr<int> ptr;
|
||||
};
|
||||
|
||||
TEST(Actors, do_after_stop) {
|
||||
int threads_n = 0;
|
||||
td::ConcurrentScheduler sched(threads_n, 0);
|
||||
|
||||
sched.create_actor_unsafe<DoAfterStop>(0, "DoAfterStop").release();
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
|
||||
class XContext final : public td::ActorContext {
|
||||
public:
|
||||
td::int32 get_id() const final {
|
||||
return 123456789;
|
||||
}
|
||||
|
||||
void validate() {
|
||||
CHECK(x == 1234);
|
||||
}
|
||||
~XContext() final {
|
||||
x = 0;
|
||||
}
|
||||
int x = 1234;
|
||||
};
|
||||
|
||||
class WithXContext final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
auto old_context = set_context(std::make_shared<XContext>());
|
||||
}
|
||||
void f(td::unique_ptr<td::Guard> guard) {
|
||||
}
|
||||
void close() {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
static void check_context() {
|
||||
auto ptr = static_cast<XContext *>(td::Scheduler::context());
|
||||
CHECK(ptr != nullptr);
|
||||
ptr->validate();
|
||||
}
|
||||
|
||||
TEST(Actors, context_during_destruction) {
|
||||
int threads_n = 0;
|
||||
td::ConcurrentScheduler sched(threads_n, 0);
|
||||
|
||||
{
|
||||
auto guard = sched.get_main_guard();
|
||||
auto with_context = td::create_actor<WithXContext>("WithXContext").release();
|
||||
send_closure(with_context, &WithXContext::f, td::create_lambda_guard([] { check_context(); }));
|
||||
send_closure_later(with_context, &WithXContext::close);
|
||||
send_closure(with_context, &WithXContext::f, td::create_lambda_guard([] { check_context(); }));
|
||||
send_closure(with_context, &WithXContext::f, td::create_lambda_guard([] { td::Scheduler::instance()->finish(); }));
|
||||
}
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
682
td/tdactor/test/actors_simple.cpp
Normal file
682
td/tdactor/test/actors_simple.cpp
Normal file
@@ -0,0 +1,682 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
#include "td/actor/MultiPromise.h"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
#include "td/actor/SleepActor.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
#include "td/utils/Observer.h"
|
||||
#include "td/utils/port/FileFd.h"
|
||||
#include "td/utils/port/path.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
#include "td/utils/Promise.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "td/utils/tests.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
static const size_t BUF_SIZE = 1024 * 1024;
|
||||
static char buf[BUF_SIZE];
|
||||
static char buf2[BUF_SIZE];
|
||||
static td::StringBuilder sb(td::MutableSlice(buf, BUF_SIZE - 1));
|
||||
static td::StringBuilder sb2(td::MutableSlice(buf2, BUF_SIZE - 1));
|
||||
|
||||
static td::vector<std::shared_ptr<td::MpscPollableQueue<td::EventFull>>> create_queues() {
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
return {};
|
||||
#else
|
||||
auto res = std::make_shared<td::MpscPollableQueue<td::EventFull>>();
|
||||
res->init();
|
||||
return {res};
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Actors, SendLater) {
|
||||
sb.clear();
|
||||
td::Scheduler scheduler;
|
||||
scheduler.init(0, create_queues(), nullptr);
|
||||
|
||||
auto guard = scheduler.get_guard();
|
||||
class Worker final : public td::Actor {
|
||||
public:
|
||||
void f() {
|
||||
sb << "A";
|
||||
}
|
||||
};
|
||||
auto id = td::create_actor<Worker>("Worker");
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
td::send_closure(id, &Worker::f);
|
||||
td::send_closure_later(id, &Worker::f);
|
||||
td::send_closure(id, &Worker::f);
|
||||
ASSERT_STREQ("A", sb.as_cslice().c_str());
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
ASSERT_STREQ("AAA", sb.as_cslice().c_str());
|
||||
}
|
||||
|
||||
class X {
|
||||
public:
|
||||
X() {
|
||||
sb << "[cnstr_default]";
|
||||
}
|
||||
X(const X &) {
|
||||
sb << "[cnstr_copy]";
|
||||
}
|
||||
X(X &&) noexcept {
|
||||
sb << "[cnstr_move]";
|
||||
}
|
||||
X &operator=(const X &) {
|
||||
sb << "[set_copy]";
|
||||
return *this;
|
||||
}
|
||||
X &operator=(X &&) noexcept {
|
||||
sb << "[set_move]";
|
||||
return *this;
|
||||
}
|
||||
~X() = default;
|
||||
};
|
||||
|
||||
class XReceiver final : public td::Actor {
|
||||
public:
|
||||
void by_const_ref(const X &) {
|
||||
sb << "[by_const_ref]";
|
||||
}
|
||||
void by_lvalue_ref(const X &) {
|
||||
sb << "[by_lvalue_ref]";
|
||||
}
|
||||
void by_value(X) {
|
||||
sb << "[by_value]";
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Actors, simple_pass_event_arguments) {
|
||||
td::Scheduler scheduler;
|
||||
scheduler.init(0, create_queues(), nullptr);
|
||||
|
||||
auto guard = scheduler.get_guard();
|
||||
auto id = td::create_actor<XReceiver>("XR").release();
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
|
||||
X x;
|
||||
|
||||
// check tuple
|
||||
// std::tuple<X> tx;
|
||||
// sb.clear();
|
||||
// std::tuple<X> ty(std::move(tx));
|
||||
// tx = std::move(ty);
|
||||
// ASSERT_STREQ("[cnstr_move]", sb.as_cslice().c_str());
|
||||
|
||||
// Send temporary object
|
||||
|
||||
// Tmp-->ConstRef
|
||||
sb.clear();
|
||||
td::send_closure(id, &XReceiver::by_const_ref, X());
|
||||
ASSERT_STREQ("[cnstr_default][by_const_ref]", sb.as_cslice().c_str());
|
||||
|
||||
// Tmp-->ConstRef (Delayed)
|
||||
sb.clear();
|
||||
td::send_closure_later(id, &XReceiver::by_const_ref, X());
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
// LOG(ERROR) << sb.as_cslice();
|
||||
ASSERT_STREQ("[cnstr_default][cnstr_move][by_const_ref]", sb.as_cslice().c_str());
|
||||
|
||||
// Tmp-->LvalueRef
|
||||
sb.clear();
|
||||
td::send_closure(id, &XReceiver::by_lvalue_ref, X());
|
||||
ASSERT_STREQ("[cnstr_default][by_lvalue_ref]", sb.as_cslice().c_str());
|
||||
|
||||
// Tmp-->LvalueRef (Delayed)
|
||||
sb.clear();
|
||||
td::send_closure_later(id, &XReceiver::by_lvalue_ref, X());
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
ASSERT_STREQ("[cnstr_default][cnstr_move][by_lvalue_ref]", sb.as_cslice().c_str());
|
||||
|
||||
// Tmp-->Value
|
||||
sb.clear();
|
||||
td::send_closure(id, &XReceiver::by_value, X());
|
||||
ASSERT_STREQ("[cnstr_default][cnstr_move][by_value]", sb.as_cslice().c_str());
|
||||
|
||||
// Tmp-->Value (Delayed)
|
||||
sb.clear();
|
||||
td::send_closure_later(id, &XReceiver::by_value, X());
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
ASSERT_STREQ("[cnstr_default][cnstr_move][cnstr_move][by_value]", sb.as_cslice().c_str());
|
||||
|
||||
// Var-->ConstRef
|
||||
sb.clear();
|
||||
td::send_closure(id, &XReceiver::by_const_ref, x);
|
||||
ASSERT_STREQ("[by_const_ref]", sb.as_cslice().c_str());
|
||||
|
||||
// Var-->ConstRef (Delayed)
|
||||
sb.clear();
|
||||
td::send_closure_later(id, &XReceiver::by_const_ref, x);
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
ASSERT_STREQ("[cnstr_copy][by_const_ref]", sb.as_cslice().c_str());
|
||||
|
||||
// Var-->LvalueRef
|
||||
// Var-->LvalueRef (Delayed)
|
||||
// CE or strange behaviour
|
||||
|
||||
// Var-->Value
|
||||
sb.clear();
|
||||
td::send_closure(id, &XReceiver::by_value, x);
|
||||
ASSERT_STREQ("[cnstr_copy][by_value]", sb.as_cslice().c_str());
|
||||
|
||||
// Var-->Value (Delayed)
|
||||
sb.clear();
|
||||
td::send_closure_later(id, &XReceiver::by_value, x);
|
||||
scheduler.run_no_guard(td::Timestamp::in(1));
|
||||
ASSERT_STREQ("[cnstr_copy][cnstr_move][by_value]", sb.as_cslice().c_str());
|
||||
}
|
||||
|
||||
class PrintChar final : public td::Actor {
|
||||
public:
|
||||
PrintChar(char c, int cnt) : char_(c), cnt_(cnt) {
|
||||
}
|
||||
void start_up() final {
|
||||
yield();
|
||||
}
|
||||
void wakeup() final {
|
||||
if (cnt_ == 0) {
|
||||
stop();
|
||||
} else {
|
||||
sb << char_;
|
||||
cnt_--;
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
char char_;
|
||||
int cnt_;
|
||||
};
|
||||
|
||||
//
|
||||
// Yield must add actor to the end of queue
|
||||
//
|
||||
TEST(Actors, simple_hand_yield) {
|
||||
td::Scheduler scheduler;
|
||||
scheduler.init(0, create_queues(), nullptr);
|
||||
sb.clear();
|
||||
int cnt = 1000;
|
||||
{
|
||||
auto guard = scheduler.get_guard();
|
||||
td::create_actor<PrintChar>("PrintA", 'A', cnt).release();
|
||||
td::create_actor<PrintChar>("PrintB", 'B', cnt).release();
|
||||
td::create_actor<PrintChar>("PrintC", 'C', cnt).release();
|
||||
}
|
||||
scheduler.run(td::Timestamp::in(1));
|
||||
td::string expected;
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
expected += "ABC";
|
||||
}
|
||||
ASSERT_STREQ(expected.c_str(), sb.as_cslice().c_str());
|
||||
}
|
||||
|
||||
class Ball {
|
||||
public:
|
||||
friend void start_migrate(Ball &ball, td::int32 sched_id) {
|
||||
sb << "start";
|
||||
}
|
||||
friend void finish_migrate(Ball &ball) {
|
||||
sb2 << "finish";
|
||||
}
|
||||
};
|
||||
|
||||
class Pong final : public td::Actor {
|
||||
public:
|
||||
void pong(Ball ball) {
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
};
|
||||
|
||||
class Ping final : public td::Actor {
|
||||
public:
|
||||
explicit Ping(td::ActorId<Pong> pong) : pong_(pong) {
|
||||
}
|
||||
void start_up() final {
|
||||
td::send_closure(pong_, &Pong::pong, Ball());
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorId<Pong> pong_;
|
||||
};
|
||||
|
||||
TEST(Actors, simple_migrate) {
|
||||
sb.clear();
|
||||
sb2.clear();
|
||||
|
||||
td::ConcurrentScheduler scheduler(2, 0);
|
||||
auto pong = scheduler.create_actor_unsafe<Pong>(2, "Pong").release();
|
||||
scheduler.create_actor_unsafe<Ping>(1, "Ping", pong).release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
#if TD_THREAD_UNSUPPORTED || TD_EVENTFD_UNSUPPORTED
|
||||
ASSERT_STREQ("", sb.as_cslice().c_str());
|
||||
ASSERT_STREQ("", sb2.as_cslice().c_str());
|
||||
#else
|
||||
ASSERT_STREQ("start", sb.as_cslice().c_str());
|
||||
ASSERT_STREQ("finish", sb2.as_cslice().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
class OpenClose final : public td::Actor {
|
||||
public:
|
||||
explicit OpenClose(int cnt) : cnt_(cnt) {
|
||||
}
|
||||
void start_up() final {
|
||||
yield();
|
||||
}
|
||||
void wakeup() final {
|
||||
auto observer = reinterpret_cast<td::ObserverBase *>(123);
|
||||
td::CSlice file_name = "server";
|
||||
if (cnt_ > 0) {
|
||||
auto r_file_fd = td::FileFd::open(file_name, td::FileFd::Read | td::FileFd::Create);
|
||||
LOG_CHECK(r_file_fd.is_ok()) << r_file_fd.error();
|
||||
auto file_fd = r_file_fd.move_as_ok();
|
||||
{ auto pollable_fd = file_fd.get_poll_info().extract_pollable_fd(observer); }
|
||||
file_fd.close();
|
||||
cnt_--;
|
||||
yield();
|
||||
} else {
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int cnt_;
|
||||
};
|
||||
|
||||
TEST(Actors, open_close) {
|
||||
td::ConcurrentScheduler scheduler(2, 0);
|
||||
int cnt = 10000; // TODO(perf) optimize
|
||||
scheduler.create_actor_unsafe<OpenClose>(1, "A", cnt).release();
|
||||
scheduler.create_actor_unsafe<OpenClose>(2, "B", cnt).release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
td::unlink("server").ignore();
|
||||
}
|
||||
|
||||
class MsgActor : public td::Actor {
|
||||
public:
|
||||
virtual void msg() = 0;
|
||||
};
|
||||
|
||||
class Slave final : public td::Actor {
|
||||
public:
|
||||
td::ActorId<MsgActor> msg;
|
||||
explicit Slave(td::ActorId<MsgActor> msg) : msg(msg) {
|
||||
}
|
||||
void hangup() final {
|
||||
td::send_closure(msg, &MsgActor::msg);
|
||||
}
|
||||
};
|
||||
|
||||
class MasterActor final : public MsgActor {
|
||||
public:
|
||||
void loop() final {
|
||||
alive_ = true;
|
||||
slave = td::create_actor<Slave>("Slave", static_cast<td::ActorId<MsgActor>>(actor_id(this)));
|
||||
stop();
|
||||
}
|
||||
td::ActorOwn<Slave> slave;
|
||||
|
||||
MasterActor() = default;
|
||||
MasterActor(const MasterActor &) = delete;
|
||||
MasterActor &operator=(const MasterActor &) = delete;
|
||||
MasterActor(MasterActor &&) = delete;
|
||||
MasterActor &operator=(MasterActor &&) = delete;
|
||||
~MasterActor() final {
|
||||
alive_ = 987654321;
|
||||
}
|
||||
void msg() final {
|
||||
CHECK(alive_ == 123456789);
|
||||
}
|
||||
td::uint64 alive_ = 123456789;
|
||||
};
|
||||
|
||||
TEST(Actors, call_after_destruct) {
|
||||
td::Scheduler scheduler;
|
||||
scheduler.init(0, create_queues(), nullptr);
|
||||
{
|
||||
auto guard = scheduler.get_guard();
|
||||
td::create_actor<MasterActor>("Master").release();
|
||||
}
|
||||
scheduler.run(td::Timestamp::in(1));
|
||||
}
|
||||
|
||||
class LinkTokenSlave final : public td::Actor {
|
||||
public:
|
||||
explicit LinkTokenSlave(td::ActorShared<> parent) : parent_(std::move(parent)) {
|
||||
}
|
||||
void add(td::uint64 link_token) {
|
||||
CHECK(link_token == get_link_token());
|
||||
}
|
||||
void close() {
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorShared<> parent_;
|
||||
};
|
||||
|
||||
class LinkTokenMasterActor final : public td::Actor {
|
||||
public:
|
||||
explicit LinkTokenMasterActor(int cnt) : cnt_(cnt) {
|
||||
}
|
||||
void start_up() final {
|
||||
child_ = td::create_actor<LinkTokenSlave>("Slave", actor_shared(this, 123)).release();
|
||||
yield();
|
||||
}
|
||||
void loop() final {
|
||||
for (int i = 0; i < 100 && cnt_ > 0; cnt_--, i++) {
|
||||
auto token = static_cast<td::uint64>(cnt_) + 1;
|
||||
switch (i % 4) {
|
||||
case 0: {
|
||||
td::send_closure(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
td::send_closure_later(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
td::EventCreator::closure(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token)
|
||||
.try_emit();
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
td::EventCreator::closure(td::ActorShared<LinkTokenSlave>(child_, token), &LinkTokenSlave::add, token)
|
||||
.try_emit_later();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cnt_ == 0) {
|
||||
td::send_closure(child_, &LinkTokenSlave::close);
|
||||
} else {
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
void hangup_shared() final {
|
||||
CHECK(get_link_token() == 123);
|
||||
td::Scheduler::instance()->finish();
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
int cnt_;
|
||||
td::ActorId<LinkTokenSlave> child_;
|
||||
};
|
||||
|
||||
TEST(Actors, link_token) {
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
auto cnt = 100000;
|
||||
scheduler.create_actor_unsafe<LinkTokenMasterActor>(0, "A", cnt).release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
|
||||
TEST(Actors, promise) {
|
||||
int value = -1;
|
||||
td::Promise<int> p1 = td::PromiseCreator::lambda([&](int x) { value = x; });
|
||||
p1.set_error(td::Status::Error("Test error"));
|
||||
ASSERT_EQ(0, value);
|
||||
td::Promise<td::int32> p2 = td::PromiseCreator::lambda([&](td::Result<td::int32> x) { value = 1; });
|
||||
p2.set_error(td::Status::Error("Test error"));
|
||||
ASSERT_EQ(1, value);
|
||||
}
|
||||
|
||||
class LaterSlave final : public td::Actor {
|
||||
public:
|
||||
explicit LaterSlave(td::ActorShared<> parent) : parent_(std::move(parent)) {
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorShared<> parent_;
|
||||
|
||||
void hangup() final {
|
||||
sb << "A";
|
||||
td::send_closure(actor_id(this), &LaterSlave::finish);
|
||||
}
|
||||
void finish() {
|
||||
sb << "B";
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
class LaterMasterActor final : public td::Actor {
|
||||
int cnt_ = 3;
|
||||
td::vector<td::ActorOwn<LaterSlave>> children_;
|
||||
void start_up() final {
|
||||
for (int i = 0; i < cnt_; i++) {
|
||||
children_.push_back(td::create_actor<LaterSlave>("B", actor_shared(this)));
|
||||
}
|
||||
yield();
|
||||
}
|
||||
void loop() final {
|
||||
children_.clear();
|
||||
}
|
||||
void hangup_shared() final {
|
||||
if (!--cnt_) {
|
||||
td::Scheduler::instance()->finish();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Actors, later) {
|
||||
sb.clear();
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
scheduler.create_actor_unsafe<LaterMasterActor>(0, "A").release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
ASSERT_STREQ(sb.as_cslice().c_str(), "AAABBB");
|
||||
}
|
||||
|
||||
class MultiPromise2 final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
auto promise = td::PromiseCreator::lambda([](td::Result<td::Unit> result) {
|
||||
result.ensure();
|
||||
td::Scheduler::instance()->finish();
|
||||
});
|
||||
|
||||
td::MultiPromiseActorSafe multi_promise{"MultiPromiseActor2"};
|
||||
multi_promise.add_promise(std::move(promise));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
td::create_actor<td::SleepActor>("Sleep", 0.1, multi_promise.get_promise()).release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MultiPromise1 final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
auto promise = td::PromiseCreator::lambda([](td::Result<td::Unit> result) {
|
||||
CHECK(result.is_error());
|
||||
td::create_actor<MultiPromise2>("B").release();
|
||||
});
|
||||
td::MultiPromiseActorSafe multi_promise{"MultiPromiseActor1"};
|
||||
multi_promise.add_promise(std::move(promise));
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Actors, MultiPromise) {
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
scheduler.create_actor_unsafe<MultiPromise1>(0, "A").release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
|
||||
class FastPromise final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
td::PromiseFuture<int> pf;
|
||||
auto promise = pf.move_promise();
|
||||
auto future = pf.move_future();
|
||||
promise.set_value(123);
|
||||
CHECK(future.move_as_ok() == 123);
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Actors, FastPromise) {
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
scheduler.create_actor_unsafe<FastPromise>(0, "A").release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
|
||||
class StopInTeardown final : public td::Actor {
|
||||
void loop() final {
|
||||
stop();
|
||||
}
|
||||
void tear_down() final {
|
||||
stop();
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Actors, stop_in_teardown) {
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
scheduler.create_actor_unsafe<StopInTeardown>(0, "A").release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
|
||||
class AlwaysWaitForMailbox final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
td::create_actor<td::SleepActor>("Sleep", 0.1,
|
||||
td::PromiseCreator::lambda([actor_id = actor_id(this), ptr = this](td::Unit) {
|
||||
td::send_closure(actor_id, &AlwaysWaitForMailbox::g);
|
||||
td::send_closure(actor_id, &AlwaysWaitForMailbox::g);
|
||||
CHECK(!ptr->was_f_);
|
||||
}))
|
||||
.release();
|
||||
}
|
||||
|
||||
void f() {
|
||||
was_f_ = true;
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
void g() {
|
||||
td::send_closure(actor_id(this), &AlwaysWaitForMailbox::f);
|
||||
}
|
||||
|
||||
private:
|
||||
bool was_f_{false};
|
||||
};
|
||||
|
||||
TEST(Actors, always_wait_for_mailbox) {
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
scheduler.create_actor_unsafe<AlwaysWaitForMailbox>(0, "A").release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
|
||||
#if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED
|
||||
TEST(Actors, send_from_other_threads) {
|
||||
td::ConcurrentScheduler scheduler(1, 0);
|
||||
int thread_n = 10;
|
||||
class Listener final : public td::Actor {
|
||||
public:
|
||||
explicit Listener(int cnt) : cnt_(cnt) {
|
||||
}
|
||||
void dec() {
|
||||
if (--cnt_ == 0) {
|
||||
td::Scheduler::instance()->finish();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int cnt_;
|
||||
};
|
||||
|
||||
auto A = scheduler.create_actor_unsafe<Listener>(1, "A", thread_n).release();
|
||||
scheduler.start();
|
||||
td::vector<td::thread> threads(thread_n);
|
||||
for (auto &thread : threads) {
|
||||
thread = td::thread([&A, &scheduler] {
|
||||
auto guard = scheduler.get_send_guard();
|
||||
td::send_closure(A, &Listener::dec);
|
||||
});
|
||||
}
|
||||
while (scheduler.run_main(10)) {
|
||||
}
|
||||
for (auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
#endif
|
||||
|
||||
class DelayedCall final : public td::Actor {
|
||||
public:
|
||||
void on_called(int *step) {
|
||||
CHECK(*step == 0);
|
||||
*step = 1;
|
||||
}
|
||||
};
|
||||
|
||||
class MultiPromiseSendClosureLaterTest final : public td::Actor {
|
||||
public:
|
||||
void start_up() final {
|
||||
delayed_call_ = td::create_actor<DelayedCall>("DelayedCall").release();
|
||||
mpa_.add_promise(td::PromiseCreator::lambda([this](td::Unit) {
|
||||
CHECK(step_ == 1);
|
||||
step_++;
|
||||
td::Scheduler::instance()->finish();
|
||||
}));
|
||||
auto lock = mpa_.get_promise();
|
||||
td::send_closure_later(delayed_call_, &DelayedCall::on_called, &step_);
|
||||
lock.set_value(td::Unit());
|
||||
}
|
||||
|
||||
void tear_down() final {
|
||||
CHECK(step_ == 2);
|
||||
}
|
||||
|
||||
private:
|
||||
int step_ = 0;
|
||||
td::MultiPromiseActor mpa_{"MultiPromiseActor"};
|
||||
td::ActorId<DelayedCall> delayed_call_;
|
||||
};
|
||||
|
||||
TEST(Actors, MultiPromiseSendClosureLater) {
|
||||
td::ConcurrentScheduler scheduler(0, 0);
|
||||
scheduler.create_actor_unsafe<MultiPromiseSendClosureLaterTest>(0, "MultiPromiseSendClosureLaterTest").release();
|
||||
scheduler.start();
|
||||
while (scheduler.run_main(1)) {
|
||||
}
|
||||
scheduler.finish();
|
||||
}
|
||||
188
td/tdactor/test/actors_workers.cpp
Normal file
188
td/tdactor/test/actors_workers.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "td/actor/actor.h"
|
||||
#include "td/actor/ConcurrentScheduler.h"
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/SliceBuilder.h"
|
||||
#include "td/utils/tests.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
class PowerWorker final : public td::Actor {
|
||||
public:
|
||||
class Callback {
|
||||
public:
|
||||
Callback() = default;
|
||||
Callback(const Callback &) = delete;
|
||||
Callback &operator=(const Callback &) = delete;
|
||||
Callback(Callback &&) = delete;
|
||||
Callback &operator=(Callback &&) = delete;
|
||||
virtual ~Callback() = default;
|
||||
virtual void on_ready(int query, int res) = 0;
|
||||
virtual void on_closed() = 0;
|
||||
};
|
||||
void set_callback(td::unique_ptr<Callback> callback) {
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
void task(td::uint32 x, td::uint32 p) {
|
||||
td::uint32 res = 1;
|
||||
for (td::uint32 i = 0; i < p; i++) {
|
||||
res *= x;
|
||||
}
|
||||
callback_->on_ready(x, res);
|
||||
}
|
||||
void close() {
|
||||
callback_->on_closed();
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
td::unique_ptr<Callback> callback_;
|
||||
};
|
||||
|
||||
class Manager final : public td::Actor {
|
||||
public:
|
||||
Manager(int queries_n, int query_size, td::vector<td::ActorId<PowerWorker>> workers)
|
||||
: workers_(std::move(workers))
|
||||
, ref_cnt_(static_cast<int>(workers_.size()))
|
||||
, left_query_(queries_n)
|
||||
, query_size_(query_size) {
|
||||
}
|
||||
|
||||
class Callback final : public PowerWorker::Callback {
|
||||
public:
|
||||
Callback(td::ActorId<Manager> actor_id, int worker_id) : actor_id_(actor_id), worker_id_(worker_id) {
|
||||
}
|
||||
void on_ready(int query, int result) final {
|
||||
td::send_closure(actor_id_, &Manager::on_ready, worker_id_, query, result);
|
||||
}
|
||||
void on_closed() final {
|
||||
td::send_closure_later(actor_id_, &Manager::on_closed, worker_id_);
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorId<Manager> actor_id_;
|
||||
int worker_id_;
|
||||
};
|
||||
|
||||
void start_up() final {
|
||||
int i = 0;
|
||||
for (auto &worker : workers_) {
|
||||
ref_cnt_++;
|
||||
td::send_closure_later(worker, &PowerWorker::set_callback, td::make_unique<Callback>(actor_id(this), i));
|
||||
i++;
|
||||
td::send_closure_later(worker, &PowerWorker::task, 3, query_size_);
|
||||
left_query_--;
|
||||
}
|
||||
}
|
||||
|
||||
void on_ready(int worker_id, int query, int res) {
|
||||
ref_cnt_--;
|
||||
if (left_query_ == 0) {
|
||||
td::send_closure(workers_[worker_id], &PowerWorker::close);
|
||||
} else {
|
||||
ref_cnt_++;
|
||||
td::send_closure(workers_[worker_id], &PowerWorker::task, 3, query_size_);
|
||||
left_query_--;
|
||||
}
|
||||
}
|
||||
|
||||
void on_closed(int worker_id) {
|
||||
ref_cnt_--;
|
||||
if (ref_cnt_ == 0) {
|
||||
td::Scheduler::instance()->finish();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
td::vector<td::ActorId<PowerWorker>> workers_;
|
||||
int ref_cnt_;
|
||||
int left_query_;
|
||||
int query_size_;
|
||||
};
|
||||
|
||||
static void test_workers(int threads_n, int workers_n, int queries_n, int query_size) {
|
||||
td::ConcurrentScheduler sched(threads_n, 0);
|
||||
|
||||
td::vector<td::ActorId<PowerWorker>> workers;
|
||||
for (int i = 0; i < workers_n; i++) {
|
||||
int thread_id = threads_n ? i % (threads_n - 1) + 2 : 0;
|
||||
workers.push_back(sched.create_actor_unsafe<PowerWorker>(thread_id, PSLICE() << "worker" << i).release());
|
||||
}
|
||||
sched.create_actor_unsafe<Manager>(threads_n ? 1 : 0, "Manager", queries_n, query_size, std::move(workers)).release();
|
||||
|
||||
sched.start();
|
||||
while (sched.run_main(10)) {
|
||||
// empty
|
||||
}
|
||||
sched.finish();
|
||||
|
||||
// sched.test_one_thread_run();
|
||||
}
|
||||
|
||||
TEST(Actors, workers_big_query_one_thread) {
|
||||
test_workers(0, 10, 1000, 300000);
|
||||
}
|
||||
|
||||
TEST(Actors, workers_big_query_two_threads) {
|
||||
test_workers(2, 10, 1000, 300000);
|
||||
}
|
||||
|
||||
TEST(Actors, workers_big_query_nine_threads) {
|
||||
test_workers(9, 10, 1000, 300000);
|
||||
}
|
||||
|
||||
TEST(Actors, workers_small_query_one_thread) {
|
||||
test_workers(0, 10, 100000, 1);
|
||||
}
|
||||
|
||||
TEST(Actors, workers_small_query_two_threads) {
|
||||
test_workers(2, 10, 100000, 1);
|
||||
}
|
||||
|
||||
TEST(Actors, workers_small_query_nine_threads) {
|
||||
test_workers(9, 10, 10000, 1);
|
||||
}
|
||||
|
||||
class SenderActor;
|
||||
|
||||
class ReceiverActor final : public td::Actor {
|
||||
public:
|
||||
void receive(td::ActorId<SenderActor>) {
|
||||
}
|
||||
};
|
||||
|
||||
class SenderActor final : public td::Actor {
|
||||
public:
|
||||
explicit SenderActor(td::ActorId<ReceiverActor> actor_id) : actor_id_(std::move(actor_id)) {
|
||||
}
|
||||
|
||||
private:
|
||||
td::ActorId<ReceiverActor> actor_id_;
|
||||
|
||||
void loop() final {
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
send_closure(actor_id_, &ReceiverActor::receive, actor_id(this));
|
||||
}
|
||||
set_timeout_in(0.001);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(Actors, send_closure_while_finish) {
|
||||
td::ConcurrentScheduler sched(1, 0);
|
||||
|
||||
auto receiver = sched.create_actor_unsafe<ReceiverActor>(0, "ReceiverActor").release();
|
||||
sched.create_actor_unsafe<SenderActor>(1, "SenderActor", receiver).release();
|
||||
|
||||
sched.start();
|
||||
auto end_time = td::Time::now() + 0.2;
|
||||
while (td::Time::now() < end_time) {
|
||||
sched.run_main(0.1);
|
||||
}
|
||||
sched.finish();
|
||||
}
|
||||
Reference in New Issue
Block a user