-
-
Notifications
You must be signed in to change notification settings - Fork 12
engine.md
The QB Actor Framework's engine is the runtime environment that brings your actors to life, manages their execution, and facilitates their communication. It's primarily composed of the qb::Main class, which orchestrates multiple qb::VirtualCore instances (worker threads).
(qb/core/Main.h)
Think of qb::Main as the central controller for your entire actor system. It's responsible for setting up the environment, launching worker threads, and managing the overall lifecycle.
Basic Engine Architecture:
+------------------------------------+
| qb::Main |
| (Engine Orchestrator) |
+-----------------|------------------+
| Manages
+-----------+-----------+
| |
+-----v-----------+ +-----v-----------+
| VirtualCore 0 | | VirtualCore 1 | ... (More VirtualCores)
| (Worker Thread)| | (Worker Thread)|
| +-------------+ | | +-------------+ |
| | Actor A | | | | Actor C | |
| +-------------+ | | +-------------+ |
| +-------------+ | | +-------------+ |
| | Actor B | | | | Actor D | |
| +-------------+ | | +-------------+ |
| (Event Loop & | | (Event Loop & |
| Mailbox) | | Mailbox) |
+-----------------+ +-----------------+
-
Instantiate
qb::Main: Your application typically starts by creating an instance ofqb::Main.#include <qb/main.h> // For qb::Main, qb::CoreInitializer, qb::CoreIdSet #include "MyActor.h" // Your actor definitions int main() { qb::Main engine; // ... configuration and actor addition follow ... }
-
(Optional) Configure
VirtualCoreBehavior (Before Starting):qb::Mainallows you to fine-tune the behavior of eachVirtualCorebefore the engine starts. You access a core's configuration throughengine.core(core_id), which returns aqb::CoreInitializer&.-
Set Event Loop Latency:
core_initializer.setLatency(nanoseconds):-
0(default): Lowest latency; theVirtualCorespins actively, consuming 100% CPU. Ideal for highly responsive tasks. -
>0: TheVirtualCoremay sleep for up to this duration if idle, reducing CPU usage at the cost of slightly increased event processing latency.
-
-
Set CPU Affinity:
core_initializer.setAffinity(qb::CoreIdSet{cpu_id1, cpu_id2, ...}):- Pins the
VirtualCorethread to specific physical CPU cores. This can improve performance by enhancing cache locality and reducing thread migration. Requires careful planning.
- Pins the
// Example: Configure core 1 for lower CPU usage, core 2 for high responsiveness qb::CoreIdSet core2_affinity = {2}; // Assuming physical core 2 engine.core(1).setLatency(1000000); // 1ms latency for core 1 engine.core(2).setAffinity(core2_affinity).setLatency(0); // Core 2 on physical CPU 2, no latency // Set a default latency for any other cores that might be implicitly created or used engine.setLatency(500000); // Default 0.5ms for other cores
-
Set Event Loop Latency:
-
Add Actors to Cores: Assign your actor instances to specific
VirtualCores.-
engine.addActor<MyActorType>(core_id, constructor_arg1, ...): Adds a single actor to the specifiedcore_id. -
engine.core(core_id).builder().addActor<ActorA>(...).addActor<ActorB>(...).idList(): A fluent interface for adding multiple actors to the same core and retrieving theirActorIds.
// Add a LoggerService actor to core 0 qb::ActorId logger_id = engine.addActor<LoggerService>(0, "system_log.txt"); if (!logger_id.is_valid()) { /* Handle error, e.g., duplicate service */ } // Add multiple worker actors to core 1 auto worker_ids = engine.core(1).builder() .addActor<DataProcessor>(logger_id) .addActor<ReportGenerator>(logger_id) .idList(); // Returns std::vector<qb::ActorId>
-
-
engine.start(bool async = true): This crucial method launches theVirtualCorethreads and starts their event loops.-
async = true(default):start()returns immediately. Yourmain()thread (or the calling thread) continues execution independently. You'll typically callengine.join()later to wait for the engine to shut down. -
async = false: The calling thread becomes one of theVirtualCoreworker threads (usually the one with the lowest availablecore_idif not explicitly configured, or the last one in a single-threaded setup forstart(false)). This call blocks until the entire engine is stopped.
-
-
engine.join(): If you started the engine withasync = true, calljoin()on theengineobject in your main thread. This will block until allVirtualCorethreads have completed their shutdown and terminated. -
qb::Main::stop()(Static Method): This is the recommended way to initiate a graceful shutdown of the entire actor system. It can be called from any thread, including OS signal handlers. It typically works by sendingqb::KillEventor a similar signal to all actors/cores. -
Signal Handling: By default,
qb::Mainregisters handlers forSIGINTandSIGTERM(on POSIX-like systems) that will callqb::Main::stop(). You can customize this usingqb::Main::registerSignal(int signum)andqb::Main::unregisterSignal(int signum). -
Error Checking (
engine.hasError()): Afterengine.join()returns, callengine.hasError()to check if anyVirtualCoreterminated prematurely due to an unhandled exception or other critical error.
// Typical asynchronous startup and shutdown
int main() {
qb::Main engine;
// ... configure engine and add actors ...
engine.start(); // Start asynchronously
qb::io::cout() << "QB Engine running in background. Main thread can do other work or wait.\n";
// Example: Let the engine run for a while, then stop it
// In a real server, this might be an indefinite wait or controlled by other logic
std::this_thread::sleep_for(std::chrono::seconds(10));
qb::io::cout() << "Requesting engine stop...\n";
qb::Main::stop(); // Signal all cores/actors to stop
engine.join(); // Wait for graceful shutdown
if (engine.hasError()) {
qb::io::cout() << "Engine stopped with an error!\n";
return 1;
}
qb::io::cout() << "Engine stopped successfully.\n";
return 0;
}(Reference: test-main.cpp for various startup/shutdown scenarios. All examples in example/ showcase qb::Main usage.**)
(qb/core/VirtualCore.h)
A qb::VirtualCore represents a single, independent worker thread that is responsible for executing the actors assigned to it. You don't typically interact with VirtualCore objects directly; qb::Main manages them.
-
Execution Model & Event Loop: At its heart, each
VirtualCoreruns an event loop powered byqb::io::async::listener(from theqb-iolibrary). This loop is the engine that drives all activity for the actors on that core. -
Mailbox & Event Queues: To handle messages:
-
Inter-Core Mailbox: Each
VirtualCorehas an incoming MPSC (Multiple-Producer, Single-Consumer) queue. When an actor on another core sends an event to an actor on this core, the event data is placed into this mailbox. -
Local Event Queue: Events sent between actors residing on the same
VirtualCore(e.g., viaactor.push()) are typically handled through a more direct local queueing mechanism within that core.
-
Inter-Core Mailbox: Each
-
Sequential Actor Processing (The Key Guarantee): A
VirtualCoreprocesses one event for one actor to completion before moving to the next event or any other task (like callbacks) for any actor on that same core. This sequential execution of an actor's event handlers is what provides inherent thread safety for an actor's internal state, eliminating the need for manual locking within an actor for its own data. -
Scheduling Cycle (Conceptual): In each iteration of its main processing loop, a
VirtualCoregenerally performs these steps:-
Process I/O Events: Polls its
qb::io::async::listenerfor any pending I/O events (socket readiness, file changes, timer expirations fromqb::io::async::callbackorqb::io::async::with_timeout) and dispatches them. -
Process Inter-Core Mailbox: Dequeues and processes events that have arrived from other
VirtualCores. - Process Local Event Queue: Dequeues and processes events sent between actors on this same core.
-
Execute Callbacks: Invokes the
onCallback()method for all actors on this core that have registered viaqb::ICallback. -
Flush Outgoing Pipes: Sends any buffered outgoing events to their destination
VirtualCoremailboxes. -
Idle Behavior: If configured with a
latency > 0and there's no immediate work, theVirtualCoremay briefly sleep to conserve CPU resources.
-
Process I/O Events: Polls its
(qb/core/Main.h - SharedCoreCommunication, qb/system/lockfree/mpsc.h)
While largely transparent to the application developer, understanding the basics of inter-core messaging can be insightful:
-
SharedCoreCommunication: An internal component managed byqb::Main. It owns and provides access to the MPSC mailboxes for allVirtualCores. -
Mailboxes (MPSC Queues): Each
VirtualCorehas one such incoming mailbox. Events destined for actors on this core from other cores are enqueued here. The use of lock-free MPSC queues (qb::lockfree::mpsc::ringbuffer) is critical for minimizing contention and maximizing throughput when multiple cores are sending events to a single target core. -
VirtualPipe: When an actor callspush()orgetPipe().push(), events are initially buffered in per-destinationVirtualPipeobjects within the sendingVirtualCore. TheVirtualCorethen flushes these pipes at an appropriate point in its loop, transferring the event data to the destination core's MPSC mailbox.
Inter-Core Event Flow (Actor A on VC0 sends to Actor C on VC1):
+-----------------+ +-----------------+ +----------------------+
| VirtualCore 0 | | Shared MPSC | | VirtualCore 1 |
| +-----------+ | | Mailbox for VC1 | | +-----------+ |
| | Actor A |-->Event->| (from VC0) |--->Event---->| Actor C | |
| | (Sender) | | +-----------------+ | | (Receiver)| |
| +-----------+ | | +-----------+ |
+-----------------+ +----------------------+
1. Actor A (on VC0) calls push<Event>(actor_c_id, ...)
2. VC0 places event data into VC1's MPSC Mailbox.
3. VC1, during its event loop, dequeues event from its Mailbox.
4. VC1 dispatches event to Actor C's on(Event&) handler.
This architecture is designed to ensure efficient, low-contention message passing, forming the backbone of QB's scalable actor communication.
(Next: Explore QB-Core: Actor Patterns & Utilities or review Core Concepts: Concurrency and Parallelism in QB for a higher-level view.)
(Reference Examples: test-actor-event.cpp (Multi core, High Latency tests), test-actor-service-event.cpp for inter-core messaging demonstrations.)