#pragma once #include "actor_bootstrapped.h" #include "events.h" #include "event_local.h" #include #include #include #include #include namespace NActors { struct TEvents::TEvInvokeResult : TEventLocal { using TProcessCallback = std::function; TProcessCallback ProcessCallback; std::variant Result; // This constructor creates TEvInvokeResult with the result of calling callback(args...) or exception_ptr, // if exception occurs during evaluation. template TEvInvokeResult(TProcessCallback&& process, TCallback&& callback, TArgs&&... args) : ProcessCallback(std::move(process)) { try { if constexpr (std::is_void_v>) { // just invoke callback without saving any value std::invoke(std::forward(callback), std::forward(args)...); } else { Result.emplace(std::invoke(std::forward(callback), std::forward(args)...)); } } catch (...) { Result.emplace(std::current_exception()); } } void Process(const TActorContext& ctx) { ProcessCallback(*this, ctx); } template std::invoke_result_t GetResult() { using T = std::invoke_result_t; return std::visit([](auto& arg) -> T { using TArg = std::decay_t; if constexpr (std::is_same_v) { std::rethrow_exception(arg); } else if constexpr (std::is_void_v) { Y_VERIFY(!arg.has_value()); } else if (auto *value = std::any_cast(&arg)) { return std::move(*value); } else { Y_FAIL("unspported return type for TEvInvokeResult: actual# %s != expected# %s", TypeName(arg.type()).data(), TypeName().data()); } }, Result); } }; // Invoke Actor is used to make different procedure calls in specific threads pools. // // Actor is created by CreateInvokeActor(callback, complete) where `callback` is the function that will be invoked // upon actor registration, which will issue then TEvInvokeResult to the parent actor with the result of called // function. If the called function throws exception, then the exception will arrive in the result. Receiver of // this message can either handle it by its own means calling ev.GetResult() (which will rethrow exception if it // has occured in called function or return its return value; notice that when there is no return value, then // GetResult() should also be called to prevent losing exception), or invoke ev.Process(), which will invoke // callback provided as `complete` parameter to the CreateInvokeActor function. Complete handler is invoked with // the result-getter lambda as the first argument and the actor system context as the second one. Result-getter // should be called to obtain resulting value or exception like the GetResult() method of the TEvInvokeResult event. // // Notice that `callback` execution usually occurs in separate actor on separate mailbox and should not use parent // actor's class. But `complete` handler is invoked in parent context and can use its contents. Do not forget to // handle TEvInvokeResult event by calling Process/GetResult method, whichever is necessary. template class TInvokeActor : public TActorBootstrapped> { TCallback Callback; TCompletion Complete; public: static constexpr auto ActorActivityType() { return static_cast(Activity); } TInvokeActor(TCallback&& callback, TCompletion&& complete) : Callback(std::move(callback)) , Complete(std::move(complete)) {} void Bootstrap(const TActorId& parentId, const TActorContext& ctx) { auto process = [complete = std::move(Complete)](TEvents::TEvInvokeResult& res, const TActorContext& ctx) { complete([&] { return res.GetResult(); }, ctx); }; ctx.Send(parentId, new TEvents::TEvInvokeResult(std::move(process), std::move(Callback), ctx)); TActorBootstrapped::Die(ctx); } }; template std::unique_ptr CreateInvokeActor(TCallback&& callback, TCompletion&& complete) { return std::make_unique, std::decay_t, Activity>>( std::forward(callback), std::forward(complete)); } } // NActors