|
|
|
/**
|
|
|
|
* 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
|