rotor/0.36: Introduction and Practice of a C++ Actor Microframework
1. Introduction to rotor
rotor is an event loop-friendly C++ Actor microframework designed for building efficient asynchronous messaging systems. The framework is inspired by The Reactive Manifesto and SObjectizer, aiming to provide a simple yet powerful implementation of the Actor model.
1.1 Core Features
rotor has the following notable features:
- Multiple event loop support: Seamlessly integrates with various event loops such as wx, boost-asio, ev, etc.
- Erlang-style supervision mechanism: Provides a hierarchical supervision tree to ensure system reliability.
- High-performance messaging: Supports asynchronous request-response and publish-subscribe patterns.
- Cross-platform support: Compatible with multiple operating systems including Windows, macOS, and Linux.
- Lightweight design: The core library is simple and resource-efficient, suitable for embedded and high-performance computing environments.
1.2 Application Scenarios
rotor is particularly suitable for the following scenarios:
- Network servers: High-performance messaging mechanisms and event loop support.
- Distributed systems: Ensures automatic recovery of components in case of failure through the supervision tree.
- Embedded systems: Lightweight implementation with low resource consumption.
- Real-time systems: Low-latency messaging, suitable for real-time data processing.
2. Environment Configuration and Installation
2.1 Environment Preparation
Before using rotor, ensure that the following tools and libraries are installed on your system:
- CMake (version 3.15 or higher)
- C++ compiler (GCC or Clang)
- Boost library (version 1.83.0 or higher)
2.2 Getting rotor
Clone the rotor project to your local machine:
git clone https://github.com/basiliscos/cpp-rotor.git
cd cpp-rotor
2.3 Building the Project
Use CMake to build the project:
mkdir build
cd build
cmake ..
make
3. Core Concepts and Basic Usage
3.1 Basics of Actor
In rotor, an Actor is the basic unit of concurrent computation, with its own state and behavior, communicating with other Actors through message passing.
Here is a simple example of creating an Actor:
#include <rotor/rotor.h>
#include <iostream>
namespace r = rotor;
// Define a simple ping Actor
class ping_actor : public r::actor_base_t {
public:
using r::actor_base_t::actor_base_t;
void set_pong_addr(const r::address_ptr_t &addr) {
pong_addr = addr;
}
void on_start() noexcept override {
r::actor_base_t::on_start();
// Send the first message after starting
send<ping_message>(pong_addr);
}
void on_pong_message(pong_message &msg) {
std::cout << "ping: received pong" << std::endl;
// Reply with ping message
send<ping_message>(pong_addr);
}
private:
r::address_ptr_t pong_addr;
};
// Define message types
struct ping_message {};
struct pong_message {};
3.2 Supervision Mechanism
rotor provides an Erlang-style supervision tree to ensure system reliability. The following example shows how to create a supervisor Actor:
#include <rotor/supervisor.h>
class my_supervisor : public r::supervisor_t {
public:
using r::supervisor_t::supervisor_t;
void on_start() noexcept override {
r::supervisor_t::on_start();
// Create child Actors
auto ping_actor = spawn_actor<ping_actor>();
auto pong_actor = spawn_actor<pong_actor>();
// Configure Actor relationships
ping_actor->set_pong_addr(pong_actor->get_address());
// Start all child Actors
start_children();
}
};
4. Practical Case: Building a Ping-Pong Application
4.1 Complete Example
Here is a complete Ping-Pong example implemented using the rotor framework:
#include <rotor/rotor.h>
#include <iostream>
#include <chrono>
#include <thread>
namespace r = rotor;
// Define message types
struct ping_message { int count = 0; };
struct pong_message { int count = 0; };
// pong Actor implementation
class pong_actor : public r::actor_base_t {
public:
using r::actor_base_t::actor_base_t;
void on_ping_message(ping_message &msg) {
std::cout << "pong: received ping, count = " << msg.count << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (msg.count < 5) {
send<pong_message>(get_message_source(), {msg.count + 1});
} else {
std::cout << "pong: finished" << std::endl;
supervisor->do_shutdown();
}
}
};
// ping Actor implementation
class ping_actor : public r::actor_base_t {
public:
using r::actor_base_t::actor_base_t;
void set_pong_addr(const r::address_ptr_t &addr) {
pong_addr = addr;
}
void on_start() noexcept override {
r::actor_base_t::on_start();
// Send initial ping message
send<ping_message>(pong_addr, {0});
}
void on_pong_message(pong_message &msg) {
std::cout << "ping: received pong, count = " << msg.count << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (msg.count < 5) {
send<ping_message>(pong_addr, {msg.count});
} else {
std::cout << "ping: finished" << std::endl;
}
}
private:
r::address_ptr_t pong_addr;
};
// Supervisor implementation
class ping_pong_supervisor : public r::supervisor_t {
public:
using r::supervisor_t::supervisor_t;
void on_start() noexcept override {
r::supervisor_t::on_start();
// Create ping and pong actors
auto ping = spawn_actor<ping_actor>();
auto pong = spawn_actor<pong_actor>();
// Set Actor relationships
ping->set_pong_addr(pong->get_address());
// Start all Actors
start_children();
}
};
int main() {
// Create and start the system
auto supervisor = std::make_shared<ping_pong_supervisor>();
supervisor->start();
// Wait for the system to finish running
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
4.2 Integration with Boost.Asio
rotor can be integrated with the Boost.Asio event loop. The following example demonstrates how to use them together:
#include <rotor/asio.hpp>
#include <boost/asio.hpp>
namespace asio = boost::asio;
namespcae ra = rotor::asio;
class timer_actor : public ra::supervisor_asio_t {
public:
using ra::supervisor_asio_t::supervisor_asio_t;
void on_start() noexcept override {
ra::supervisor_asio_t::on_start();
// Create a timer
timer = std::make_shared<asio::steady_timer>(get_asio_executor());
timer->expires_after(std::chrono::seconds(1));
timer->async_wait([this](const boost::system::error_code &ec) {
if (!ec) {
std::cout << "Timer fired!" << std::endl;
this->do_shutdown();
}
});
}
private:
std::shared_ptr<asio::steady_timer> timer;
};
int main() {
asio::io_context io_context;
auto supervisor = std::make_shared<timer_actor>(io_context);
supervisor->start();
io_context.run();
return 0;
}
5. Advanced Features and Best Practices
5.1 Request-Response Pattern
rotor supports complex request-response communication patterns:
#include <rotor/request.hpp>
class worker_actor : public r::actor_base_t {
public:
using r::actor_base_t::actor_base_t;
void on_calculation_request(calculation_request &req) {
// Process the request
int result = req.value * 2;
// Send response
send<calculation_response>(req.source, {result}, req.request_id);
}
};
class client_actor : public r::actor_base_t {
public:
using r::actor_base_t::actor_base_t;
void set_worker_addr(const r::address_ptr_t &addr) {
worker_addr = addr;
}
void on_start() noexcept override {
r::actor_base_t::on_start();
// Send request
request<calculation_request>(worker_addr, {42}).then(
[this](calculation_response &resp) {
std::cout << "Received response: " << resp.result << std::endl;
}
);
}
private:
r::address_ptr_t worker_addr;
};
// Define request-response message types
struct calculation_request { int value; };
struct calculation_response { int result; };
5.2 Error Handling and Fault Tolerance
Powerful error handling is achieved through the supervision mechanism:
class resilient_supervisor : public r::supervisor_t {
public:
using r::supervisor_t::supervisor_t;
customization::restart_policy_t on_child_failed(r::actor_base_t *actor, const std::error_code &ec) noexcept override {
std::cout << "Actor failed: " << ec.message() << std::endl;
// Decide restart policy based on error type
if (ec == r::error_code_t::actor_crashed) {
return customization::restart_policy_t::restart;
} else {
return customization::restart_policy_t::shutdown;
}
}
void on_start() noexcept override {
r::supervisor_t::on_start();
// Create resilient child Actors
spawn_actor<critical_service_actor>();
spawn_actor<monitoring_actor>();
start_children();
}
};
6. Performance Optimization Tips
6.1 Event Loop Optimization
In actual deployment, attention should be paid to the performance characteristics of the event loop. Based on experience, in some embedded systems, it may be necessary to limit the frequency of the main event loop to avoid excessive CPU load. For example:
// In some cases, it is necessary to limit the event loop frequency
while (running) {
handle_periodic_tasks();
main_event();
// Limit loop frequency to avoid 100% CPU usage
chThdSleepMicroseconds(1000); // 1ms delay
}
6.2 Message Passing Optimization
To achieve optimal performance, consider the following optimization strategies:
- Use move semantics to avoid message copying.
- Use publish-subscribe patterns wisely to reduce message redundancy.
- Choose synchronous or asynchronous message passing based on the scenario.
- Utilize message batching to improve throughput.
7. Conclusion
As a powerful and flexible C++ Actor microframework, rotor/0.36 provides a comprehensive solution for building high-concurrency, distributed systems. Its event loop-friendly design, Erlang-style supervision tree, and high-performance messaging mechanisms enable it to perform excellently in various application scenarios.