Deferred methods example¶
This example shows how the deferred methods work. The idea is to model a Manager who will receive
the workload from clients and forward it along all the available Workers published in
a bus called workersBus. If there are no available workers, the manager will perform the task.
Interface¶
The fibonacci package contains the fibonacci.stl file, which holds two interface classes:
Manager and Worker. Manager inherits from Worker for allowing him to behave as a worker as
well.
package fibonacci;
// Example class that does some work. In this case, we
// illustrate it by computing the fibonacci sequence.
class Worker
{
// Returns the Fibonacci number of the element "n" of the Fibonacci sequence.
//
// @param n index of the Fibonacci sequence.
fn computeFibonacci(n: u32) -> u64;
}
// A special kind of worker that forwards the
// incoming work to other workers.
class Manager : extends Worker
{
// Where to look for the workers.
var workersBus : string [static];
}
Implementation¶
To mark methods as deferred, we need to tell the Sen code generator that we don't want to (or can't) provide a result straight away. To do so, we specify our code generation settings:
The codegen_settings.json file is passed to the Sen code generator via the CODEGEN_SETTINGS
option in add_sen_package. It is a JSON object keyed by class name under classes, each listing
the methods to change. Here fibonacci.Worker lists computeFibonacci under deferredMethods.
This will force us to implement a method that, instead of returning a value, receives an std::promise that we can set whenever we want. In the case of the regular worker, we do the following:
class FibonacciWorker: public WorkerBase
{
public:
SEN_NOCOPY_NOMOVE(FibonacciWorker)
public:
using WorkerBase::WorkerBase;
~FibonacciWorker() override = default;
public:
void computeFibonacciImpl(uint32_t n, std::promise<uint64_t>&& promise) override
{
std::this_thread::sleep_for(std::chrono::seconds(5)); // simulate heavy workload
promise.set_value(::fibonacci::computeFibonacci(n));
}
};
In the case of the manager, our implementation is a bit different:
/// Forwards the call to a random worker, or does the work if none is found.
void computeFibonacciImpl(uint32_t n, std::promise<uint64_t>&& promise) override
{
if (workers_->list.getObjects().empty())
{
std::cout << "No workers available, manager is performing the task" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5)); // simulate heavy workload
promise.set_value(::fibonacci::computeFibonacci(n));
}
else
{
auto& selectedWorker = selectRandomWorker();
std::cout << "Sending task to " << selectedWorker.asObject().getName() << std::endl;
selectedWorker.computeFibonacci(
n, {this, [p = std::move(promise)](const auto& result) mutable { p.set_value(result.getValue()); }});
}
}
Here we have a list of workers. If we don't find any worker we perform the job on our own.
Otherwise, we select a random worker from the list and call its computeFibonacci function. When we
get the result, we set the value of the promise we made to the user. Note that we need to
std::move the promise into the callback so that we can use it from within that context.
In Sen, each Component runs in an execution thread. If the Workers are defined in the same
component they will share the same thread and won't be able to do work in parallel. In this example,
each Worker lives in a different component. Same thing with the Manager. Each implementation of
computeFibonacciImpl sleeps for five seconds before computing, to make this threading behavior
visible. The computeFibonacci function itself has no side effects.
NOTE: Since Manager extends Worker, a query for workers on the workersBus would match the
manager itself. The manager's interest excludes it by name, so it never appears in its own worker
list.
# $schema: ../base/schema.json
include:
- ../base/term.yaml
build:
- name: myComponent
freqHz: 30
imports: [fibonacci]
group: 3
objects:
- class: fibonacci.FibonacciManager
workersBus: my.tutorial
name: fibManager
bus: my.tutorial
- name: myComponentA
freqHz: 30
imports: [fibonacci]
group: 3
objects:
- class: fibonacci.FibonacciWorker
name: fibWorkerA
bus: my.tutorial
- name: myComponentB
freqHz: 30
imports: [fibonacci]
group: 3
objects:
- class: fibonacci.FibonacciWorker
name: fibWorkerB
bus: my.tutorial
- name: myComponentC
freqHz: 30
imports: [fibonacci]
group: 3
objects:
- class: fibonacci.FibonacciWorker
name: fibWorkerC
bus: my.tutorial
- name: myComponentD
freqHz: 30
imports: [fibonacci]
group: 3
objects:
- class: fibonacci.FibonacciWorker
name: fibWorkerD
bus: my.tutorial
- name: myComponentE
freqHz: 30
imports: [fibonacci]
group: 3
objects:
- class: fibonacci.FibonacciWorker
name: fibWorkerE
bus: my.tutorial
How to run it¶
This will open a shell and tell Sen to instantiate the implementations in the my.tutorial bus.
You ask the Manager for computing n Fibonacci number by:
Note that the potential of this example is to ask the Manager for as many petitions as desired, while crosschecking that the workload is constantly being distributed and simultaneously performed by the different workers without delaying the workload distribution.
You can also ask one of the workers directly. The configuration publishes five of them, fibWorkerA
through fibWorkerE:
Testing package internals with TEST_TARGET¶
By default, Sen packages compile with hidden symbol visibility, so unit tests cannot link directly
against the package and call internal functions. The TEST_TARGET option creates an additional
STATIC library with all symbols exposed, which you can link your test executables against.
The fibonacci package uses this to expose and test the pure computeFibonacci algorithm
independently of the runtime. In the package's CMakeLists.txt:
add_sen_package(
TARGET fibonacci
TEST_TARGET fibonacci_test_lib # static library with all symbols, used by unit tests
MAINTAINER "Sen Core Team (enrique.parodi@airbus.com)"
VERSION "0.0.1"
DESCRIPTION "Illustrates the usage of deferred functions"
SOURCES src/fibonacci.cpp
STL_FILES stl/fibonacci.stl
CODEGEN_SETTINGS src/codegen_settings.json
SCHEMA_PATH ${CMAKE_CURRENT_BINARY_DIR}
)
add_sen_unit_test_suite(
fibonacci_test
test/fibonacci_algorithm_test.cpp
LINK_DEPS
fibonacci_test_lib
)
set_target_properties(fibonacci_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
TEST_TARGET names the static library, fibonacci_test_lib. The unit test suite is a separate
target, registered with add_sen_unit_test_suite and linked against that static library rather than
against the shared package.
Any modifications to the target (e.g. target_include_directories) must be applied to
fibonacci_obj, the intermediate OBJECT target, not to fibonacci or fibonacci_test_lib directly
(see the CMake integration page of the manual for details).