You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

490 lines
17 KiB
C

/**
* Copyright 2013-2022 Software Radio Systems Limited
*
* This file is part of srsRAN.
*
* srsRAN is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* srsRAN is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* A copy of the GNU Affero General Public License can be found in
* the LICENSE file in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/.
*
*/
#include "srsran/adt/move_callback.h"
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <vector>
#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<T> to call T::then() method only if it exists
template <typename T, typename ProcResult>
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<T> to call proc T::clear() method only if it exists
template <class T>
auto optional_clear(T* obj) -> decltype(obj->clear())
{
obj->clear();
}
inline auto optional_clear(...) -> void
{
// do nothing
}
template <class T>
auto get_result_type(const T& obj) -> decltype(obj.get_result());
inline auto get_result_type(...) -> void;
} // namespace proc_detail
/**************************************************************************************
* class: callback_group_t<Args...>
* 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 <typename... Args>
class callback_group_t
{
public:
using callback_id_t = uint32_t;
using callback_t = srsran::move_callback<void(Args...)>;
//! 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 <typename... ArgsRef>
void operator()(ArgsRef&&... args)
{
for (auto& f : func_list) {
if (f.active) {
f.func(std::forward<ArgsRef>(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<call_item_t> 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 <typename T>
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 <typename Proc>
void extract_val(Proc& p)
{
set_val(p.get_result());
}
protected:
T t;
};
template <>
struct proc_result_t<void> : public proc_detail::proc_result_base_t {
template <typename Proc>
void extract_val(Proc& p)
{
set_val();
}
};
// specialization for ResultType=void
using proc_state_t = proc_result_t<void>;
/**************************************************************************************
* 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 <typename ResultType>
class proc_future_t
{
public:
proc_future_t() = default;
explicit proc_future_t(const std::shared_ptr<proc_result_t<ResultType> >& 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<proc_result_t<ResultType> > ptr;
};
using proc_future_state_t = proc_future_t<void>;
/**************************************************************************************
* 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<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<T, ResultType>
* 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>". 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<T>&)" - method in T that runs on completion, and
* gets as argument the result of the run
* - "proc_t<T>::get_future()" - returns a proc_future_t<T> which the user can use
* directly to check the result of a run
* - "proc_t<T>::then/then_always()" - provide dynamically a continuation task, for
* instance, by providing a lambda
* It uses a unique_ptr<T> to allow the use of procedures that are forward declared.
************************************************************************************/
// Implementation of the Procedure Manager functionality, including launching, trigger events, clearing
template <class T, typename ResultType = void>
class proc_t : public proc_base_t
{
public:
// cannot derive automatically this type
using result_type = ResultType;
using proc_result_type = proc_result_t<result_type>;
using proc_future_type = proc_future_t<result_type>;
using then_callback_list_t = callback_group_t<const proc_result_type&>;
using callback_t = typename then_callback_list_t::callback_t;
using callback_id_t = typename then_callback_list_t::callback_id_t;
template <typename... Args>
explicit proc_t(Args&&... args) : proc_ptr(new T(std::forward<Args>(args)...))
{
static_assert(std::is_same<result_type, decltype(proc_detail::get_result_type(std::declval<T>()))>::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 <class Event>
bool trigger(Event&& e)
{
if (is_busy()) {
proc_outcome_t outcome = proc_ptr->react(std::forward<Event>(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<proc_result_type>();
}
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 <class... Args>
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>(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 <class... Args>
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>(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<T> proc_ptr;
std::shared_ptr<proc_result_type> future_result; //! used if get_future() itf is used.
then_callback_list_t complete_callbacks;
};
/**************************************************************************************
* class: event_handler_t<Args...>
* 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 <typename EventType>
class event_handler_t
{
public:
using callback_id_t = typename callback_group_t<EventType>::callback_id_t;
template <typename Proc, typename ResultType>
callback_id_t on_next_trigger(proc_t<Proc, ResultType>& p)
{
return callbacks.on_next_call([&p](EventType&& ev) { p.trigger(std::forward<EventType>(ev)); });
}
template <typename Proc, typename ResultType>
callback_id_t on_every_trigger(proc_t<Proc, ResultType>& p)
{
return callbacks.on_every_call([&p](EventType&& ev) { p.trigger(std::forward<EventType>(ev)); });
}
void trigger(EventType&& ev) { callbacks(std::forward<EventType>(ev)); }
private:
callback_group_t<EventType> 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<proc_outcome_t()> 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<proc_outcome_t()> 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<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<void(proc_base_t*)>;
using proc_obj_t = std::unique_ptr<proc_base_t, proc_deleter_t>;
public:
template <typename T, typename ResultType>
void add_proc(proc_t<T, ResultType>& 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 <class T, typename ResultType>
void add_proc(proc_t<T, ResultType>&& proc)
{
if (proc.is_idle()) {
return;
}
proc_obj_t ptr(new proc_t<T, ResultType>(std::move(proc)), std::default_delete<proc_base_t>());
proc_list.push_back(std::move(ptr));
}
bool add_task(std::function<proc_outcome_t()> step_func)
{
proc_t<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_obj_t> proc_list;
};
} // namespace srsran
#endif // SRSRAN_RESUMABLE_PROCEDURES_H