/** * * \section COPYRIGHT * * Copyright 2013-2021 Software Radio Systems Limited * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the distribution. * */ #include "srsran/adt/move_callback.h" #include #include #include #include #include #ifndef SRSRAN_RESUMABLE_PROCEDURES_H #define SRSRAN_RESUMABLE_PROCEDURES_H namespace srsran { enum class proc_outcome_t { yield, success, error }; /************************************************************************************** * helper functions for method optional overloading ************************************************************************************/ namespace proc_detail { // used by proc_t to call T::then() method only if it exists template auto optional_then(T* obj, const ProcResult* result) -> decltype(obj->then(*result)) { obj->then(*result); } inline auto optional_then(...) -> void { // do nothing } // used by proc_t to call proc T::clear() method only if it exists template auto optional_clear(T* obj) -> decltype(obj->clear()) { obj->clear(); } inline auto optional_clear(...) -> void { // do nothing } template auto get_result_type(const T& obj) -> decltype(obj.get_result()); inline auto get_result_type(...) -> void; } // namespace proc_detail /************************************************************************************** * class: callback_group_t * Bundles several callbacks with callable interface "void(Args...)". * Calls to operator(Args&&...) call all the registered callbacks. * Two methods to register a callback - call it once, or always call it. ************************************************************************************/ template class callback_group_t { public: using callback_id_t = uint32_t; using callback_t = srsran::move_callback; //! register callback, that gets called once callback_id_t on_next_call(callback_t f_) { uint32_t idx = get_new_callback(); func_list[idx].func = std::move(f_); func_list[idx].call_always = false; return idx; } callback_id_t on_every_call(callback_t f_) { uint32_t idx = get_new_callback(); func_list[idx].func = std::move(f_); func_list[idx].call_always = true; return idx; } // call all callbacks template void operator()(ArgsRef&&... args) { for (auto& f : func_list) { if (f.active) { f.func(std::forward(args)...); if (not f.call_always) { f.active = false; } } } } private: uint32_t get_new_callback() { uint32_t i = 0; for (; i < func_list.size() and func_list[i].active; ++i) { } if (i == func_list.size()) { func_list.emplace_back(); } func_list[i].active = true; return i; } struct call_item_t { bool active; callback_t func; bool call_always; }; std::vector func_list; }; /************************************************************************************** * class: proc_result_t * Stores the result of a procedure run. Can optionally contain a value T, in case of a * successful run. **************************************************************************************/ namespace proc_detail { struct proc_result_base_t { bool is_success() const { return state == result_state_t::value; } bool is_error() const { return state == result_state_t::error; } bool is_complete() const { return state != result_state_t::none; } void set_val() { state = result_state_t::value; } void set_error() { state = result_state_t::error; } void clear() { state = result_state_t::none; } protected: enum class result_state_t { none, value, error } state = result_state_t::none; }; } // namespace proc_detail template struct proc_result_t : public proc_detail::proc_result_base_t { const T* value() const { return state == result_state_t::value ? &t : nullptr; } void set_val(const T& t_) { proc_result_base_t::set_val(); t = t_; } template void extract_val(Proc& p) { set_val(p.get_result()); } protected: T t; }; template <> struct proc_result_t : public proc_detail::proc_result_base_t { template void extract_val(Proc& p) { set_val(); } }; // specialization for ResultType=void using proc_state_t = proc_result_t; /************************************************************************************** * class: proc_future_t * Contains a pointer to the result of a procedure run. This pointer gets updated with * the actual result once the procedure completes. **************************************************************************************/ template class proc_future_t { public: proc_future_t() = default; explicit proc_future_t(const std::shared_ptr >& p_) : ptr(p_) {} bool is_error() const { return not is_empty() and ptr->is_error(); } bool is_success() const { return not is_empty() and ptr->is_success(); } bool is_complete() const { return not is_empty() and ptr->is_complete(); } const ResultType* value() const { return is_success() ? ptr->value() : nullptr; } bool is_empty() const { return ptr == nullptr; } void clear() { ptr.reset(); } private: std::shared_ptr > ptr; }; using proc_future_state_t = proc_future_t; /************************************************************************************** * class: proc_base_t * Provides a polymorphic interface for resumable procedures. This base can then be used * by a "proc_manager_list_t" via the virtual method "proc_base_t::run()". * With public methods: * - run() - executes proc_t::step(), and updates procedure state. * - is_busy()/is_idle() - tells if procedure is currently running. Busy procedures * cannot be re-launched * - then() - called automatically when a procedure has finished. Useful for actions * upon procedure completion, like sending back a response or logging. * With protected methods: * - step() - method overriden by child class that will be called by run(). step() * executes a procedure "action" based on its current internal state, * and return a proc_outcome_t variable with possible values: * - yield - the procedure performed the action but hasn't completed yet. * - error - the procedure has finished unsuccessfully * - success - the procedure has completed successfully ************************************************************************************/ class proc_base_t { public: virtual ~proc_base_t() = default; //! common proc::run() interface. Returns true if procedure is still running bool run() { if (is_busy()) { proc_outcome_t outcome = step(); handle_outcome(outcome); } return is_busy(); } //! interface to check if proc is still running bool is_busy() const { return proc_state == proc_status_t::on_going; } bool is_idle() const { return proc_state == proc_status_t::idle; } protected: enum class proc_status_t { idle, on_going }; virtual proc_outcome_t step() = 0; virtual void run_then(bool is_success) = 0; void handle_outcome(proc_outcome_t outcome) { if (outcome == proc_outcome_t::error or outcome == proc_outcome_t::success) { bool success = outcome == proc_outcome_t::success; run_then(success); } } proc_status_t proc_state = proc_status_t::idle; }; /************************************************************************************** * class: proc_t * Manages the lifetime of a procedure of type T, including its alloc, launching, * and reset back to "inactive" state once the procedure has been completed. * The result of a procedure run is of type "proc_result_t". ResultType has * to coincide with the type returned by the method "T::get_result()". * There are three main ways to use the result of a procedure run: * - "T::then(const proc_result_t&)" - method in T that runs on completion, and * gets as argument the result of the run * - "proc_t::get_future()" - returns a proc_future_t which the user can use * directly to check the result of a run * - "proc_t::then/then_always()" - provide dynamically a continuation task, for * instance, by providing a lambda * It uses a unique_ptr to allow the use of procedures that are forward declared. ************************************************************************************/ // Implementation of the Procedure Manager functionality, including launching, trigger events, clearing template class proc_t : public proc_base_t { public: // cannot derive automatically this type using result_type = ResultType; using proc_result_type = proc_result_t; using proc_future_type = proc_future_t; using then_callback_list_t = callback_group_t; using callback_t = typename then_callback_list_t::callback_t; using callback_id_t = typename then_callback_list_t::callback_id_t; template explicit proc_t(Args&&... args) : proc_ptr(new T(std::forward(args)...)) { static_assert(std::is_same()))>::value, "The types \"proc_t::result_type\" and the return of T::get_result() have to match"); } const T* get() const { return proc_ptr.get(); } T* release() { return proc_ptr.release(); } //! method to handle external events. "T" must have the method "T::react(const Event&)" for the trigger to take effect template bool trigger(Event&& e) { if (is_busy()) { proc_outcome_t outcome = proc_ptr->react(std::forward(e)); handle_outcome(outcome); } return is_busy(); } //! returns an object which the user can use to check if the procedure has ended. proc_future_type get_future() { if (is_idle()) { return proc_future_type{}; } if (future_result == nullptr) { future_result = std::make_shared(); } return proc_future_type{future_result}; } //! methods to schedule continuation tasks callback_id_t then(callback_t c) { return complete_callbacks.on_next_call(std::move(c)); } callback_id_t then_always(callback_t c) { return complete_callbacks.on_every_call(std::move(c)); } //! launch a procedure, returning true if successful or running and false if it error or it failed to launch template bool launch(Args&&... args) { if (is_busy()) { return false; } proc_state = proc_base_t::proc_status_t::on_going; proc_outcome_t init_ret = proc_ptr->init(std::forward(args)...); handle_outcome(init_ret); return init_ret != proc_outcome_t::error; } //! launch a procedure, returning a future where the result is going to be saved template bool launch(proc_future_type* fut, Args&&... args) { if (is_busy()) { fut->clear(); return false; } proc_state = proc_base_t::proc_status_t::on_going; *fut = get_future(); proc_outcome_t init_ret = proc_ptr->init(std::forward(args)...); handle_outcome(init_ret); return init_ret != proc_outcome_t::error; } protected: proc_outcome_t step() final { return proc_ptr->step(); } void run_then(bool is_success) final { proc_state = proc_status_t::idle; proc_result_type result; // update result state if (is_success) { result.extract_val(*proc_ptr); } else { result.set_error(); } // propagate proc_result to future if it exists, and release future if (future_result != nullptr) { *future_result = result; future_result.reset(); } // call T::then() if it exists proc_detail::optional_then(proc_ptr.get(), &result); // signal continuations complete_callbacks(std::move(result)); // back to inactive proc_detail::optional_clear(proc_ptr.get()); } std::unique_ptr proc_ptr; std::shared_ptr future_result; //! used if get_future() itf is used. then_callback_list_t complete_callbacks; }; /************************************************************************************** * class: event_handler_t * Bundles several proc_managers together with same trigger(Args...) itf. * Once trigger(...) is called, all registered proc_managers get triggered * as well. ************************************************************************************/ // NOTE: Potential improvements: a method "trigger_during_this_run" that unregisters the handler // once the procedure run is finished. template class event_handler_t { public: using callback_id_t = typename callback_group_t::callback_id_t; template callback_id_t on_next_trigger(proc_t& p) { return callbacks.on_next_call([&p](EventType&& ev) { p.trigger(std::forward(ev)); }); } template callback_id_t on_every_trigger(proc_t& p) { return callbacks.on_every_call([&p](EventType&& ev) { p.trigger(std::forward(ev)); }); } void trigger(EventType&& ev) { callbacks(std::forward(ev)); } private: callback_group_t callbacks; }; /************************************************************************************** * class: func_proc_t * A proc used to store lambda functions and other function pointers as a step() * method, avoiding this way, always having to create a new class per procedure. ************************************************************************************/ class func_proc_t { public: explicit func_proc_t(std::function step_func_) : step_func(std::move(step_func_)) {} proc_outcome_t init() { return proc_outcome_t::yield; } proc_outcome_t step() { return step_func(); } private: std::function step_func; }; /************************************************************************************** * class: proc_manager_list_t * Stores procedure managers and, when run() is called, calls sequentially all * the stored procedures run() method, and removes the procedures if they have * already completed. * There are two ways to add a procedure to the list: * - add_proc(...) - adds a proc_t, and once the procedure has completed, takes it * out of the container. In case a r-value ref is passed, this class * calls its destructor. * - add_task(...) - same as add_proc(...) but takes a function pointer that * specifies a proc_impl_t step() function ************************************************************************************/ class proc_manager_list_t { using proc_deleter_t = std::function; using proc_obj_t = std::unique_ptr; public: template void add_proc(proc_t& proc) { if (proc.is_idle()) { return; } proc_obj_t ptr(&proc, [](proc_base_t* p) { /* do nothing */ }); proc_list.push_back(std::move(ptr)); } // since it receives a r-value, it calls the default destructor template void add_proc(proc_t&& proc) { if (proc.is_idle()) { return; } proc_obj_t ptr(new proc_t(std::move(proc)), std::default_delete()); proc_list.push_back(std::move(ptr)); } bool add_task(std::function step_func) { proc_t proc(std::move(step_func)); if (not proc.launch()) { return false; } add_proc(std::move(proc)); return true; } void run() { // Calls run for all callbacks. Remove the ones that have finished. The proc dtor is called. proc_list.remove_if([](proc_obj_t& elem) { return not elem->run(); }); } size_t size() const { return proc_list.size(); } private: std::list proc_list; }; } // namespace srsran #endif // SRSRAN_RESUMABLE_PROCEDURES_H