CGraph Message Passing Pub/Sub Mechanism Across Pipelines: A Complete Guide
CGraph implements topic-based publish/subscribe message passing across pipelines through a singleton message manager that binds topics to unique connection IDs, fans out published messages to all bound subscribers, and retrieves messages via those connection IDs.
The chunelfeng/cgraph repository provides a sophisticated message passing pub/sub mechanism that enables loose coupling between distinct processing pipelines. This system allows any number of publisher pipelines to broadcast typed messages to multiple subscriber pipelines without direct references, using a centralized topic manager and unique connection identifiers.
Core Architecture of CGraph Pub/Sub
The pub/sub system rests on three foundational components implemented in the GraphMessage module.
Topic Management
A topic is a logical string identifier that groups publishers and subscribers. The GMessageManager class maintains separate storage for pub/sub and point-to-point communication:
pub_sub_message_map_stores sets of message queues keyed by topic name (prefixed withinternal::PUB_SUB_PREFIX)send_recv_message_map_handles direct send/receive patterns (prefixed withinternal::SEND_RECV_PREFIX)
Connection ID (connId)
When a subscriber binds to a topic, the system returns a unique integer connection ID (connId). This identifier serves as a private handle to a specific message queue instance:
- Generated monotonically in
GMessageManager::bindTopic(seesrc/GraphCtrl/GraphMessage/GMessageManager.hlines 39-59) - Stored in
conn_message_map_to mapconnIdto the specificGMessage<TImpl>queue - Enables multiple subscribers to the same topic to receive independent message streams
Singleton Manager
The GMessageManagerSingleton class (defined in src/GraphCtrl/GraphMessage/GMessageManagerSingleton.h) provides global access to the message manager through the USingleton template. This ensures all pipelines interact with the same topic registry and message queues.
Binding Topics and Creating Subscribers
Before receiving messages, a pipeline must bind to a topic using the CGRAPH_BIND_MESSAGE_TOPIC macro. This macro expands to GMessageManager::bindTopic and handles type safety through templates.
// Bind to topic "sensor-data" with queue size 1024
// Returns unique connId for this subscriber
int connId = CGRAPH_BIND_MESSAGE_TOPIC(SensorMessage, "sensor-data", 1024);
The binding process performs three critical operations:
- Type validation: Ensures the template parameter derives from
GMessageParam - Queue creation: Instantiates a
GMessage<SensorMessage>with the specified capacity - Registration: Inserts the queue into both
pub_sub_message_map_(under the topic key) andconn_message_map_(under the newconnId)
Publishing Messages Across Pipelines
Publishers use the CGRAPH_PUB_MPARAM macro to broadcast messages to all bound subscribers. This macro invokes GMessageManager::pubTopicValue (lines 70-86 in GMessageManager.h).
SensorMessage msg;
msg.timestamp = getCurrentTime();
msg.value = sensor.read();
// Publish to all subscribers of "sensor-data"
CStatus status = CGRAPH_PUB_MPARAM(SensorMessage, "sensor-data", msg,
CGraph::GMessagePushStrategy::WAIT);
The publishing mechanism implements a fan-out pattern:
- Topic lookup: Retrieves the set of all
GMessagequeues associated with the topic frompub_sub_message_map_ - Iteration: Loops through every bound queue (each representing a different subscriber's
connId) - Delivery: Calls
msg->send(value, strategy)on each queue, pushing the message into each subscriber's independent buffer
This ensures that multiple subscriber pipelines receive identical message copies without interfering with each other's consumption rates.
Subscribing and Receiving Messages
Subscribers retrieve messages using the CGRAPH_SUB_MPARAM macro, which maps to GMessageManager::subTopicValue (lines 98-106).
SensorMessage receivedMsg;
// Receive from the specific queue identified by connId
CStatus status = CGRAPH_SUB_MPARAM(SensorMessage, connId, receivedMsg);
The subscription process:
- Connection lookup: Uses the
connIdto retrieve the specificGMessagequeue fromconn_message_map_ - Blocking receive: Calls
message->recv(value, timeout)to pop the next available message from that subscriber's private queue - Type safety: Template specialization ensures the received message matches the bound type
Each subscriber maintains independent consumption state through its unique connId, allowing different pipelines to process messages at different speeds without message loss.
Thread Safety and Resource Management
The GMessageManager implements comprehensive thread safety for concurrent pipeline access.
Synchronization Primitives
pub_sub_mutex_: Protectspub_sub_message_map_during bind and publish operationssend_recv_mutex_: Protectssend_recv_message_map_for point-to-point communication- Queue-level locking: Individual
GMessageinstances use internal mutexes for thread-safesendandrecvoperations
All public methods (bindTopic, pubTopicValue, subTopicValue, createTopic, removeTopic) acquire the appropriate mutex before modifying shared state, ensuring safe concurrent usage across multiple threads and pipelines.
Cleanup and Lifecycle
When pipelines shut down, call CGRAPH_CLEAR_MESSAGES() to release all resources:
// Release all topics, queues, and connection mappings
CGRAPH_CLEAR_MESSAGES();
This macro expands to GMessageManagerSingleton::get()->clear(), which:
- Destroys every
GMessageobject in both maps - Clears
pub_sub_message_map_,send_recv_message_map_, andconn_message_map_ - Resets the monotonic
connIdcounter to zero
Complete Working Example
The tutorial file tutorial/T17-MessagePubSub.cpp demonstrates a full pub/sub workflow with multiple subscriber pipelines.
#include "CGraph.h"
using namespace CGraph;
void publishPipeline() {
GPipelinePtr pubPipe = GPipelineFactory::create();
GElementPtr pubNode;
// Register publisher node that sends messages
pubPipe->registerGElement<MyPubMessageNode>(&pubNode, {});
pubPipe->process(5); // Run 5 iterations
}
void subscriberPipeline() {
GPipelinePtr subPipe = GPipelineFactory::create();
GElementPtr node;
GTemplateNodePtr<int> subNode;
// Each subscriber gets a unique connId for the same topic
int connId = CGRAPH_BIND_MESSAGE_TOPIC(MyMessageParam, "pub-sub", 1024);
subPipe->registerGElement<MyNode2>(&node, {}, "myNode2");
subPipe->registerGElement<MySubMessageNode<int>>(&subNode, {node}, connId);
subNode->setName("MySubMessageNode");
subPipe->process(5);
}
int main() {
std::thread pub(publishPipeline);
std::thread sub1(subscriberPipeline);
std::thread sub2(subscriberPipeline);
std::thread sub3(subscriberPipeline);
pub.join();
sub1.join();
sub2.join();
sub3.join();
CGRAPH_CLEAR_MESSAGES();
GPipelineFactory::clear();
return 0;
}
Key implementation files referenced:
- Publisher node:
tutorial/MyGNode/MyPubMessageNode.h(lines 24-30) – constructsMyMessageParamand callsCGRAPH_PUB_MPARAM - Subscriber node:
tutorial/MyGNode/MySubMessageNode.h(lines 28-31) – receives message viaconnIdusingCGRAPH_SUB_MPARAM
Summary
CGraph's message passing pub/sub mechanism across pipelines provides a robust, decoupled communication layer through these key design elements:
- Topic-based addressing using string identifiers managed by
GMessageManagerinsrc/GraphCtrl/GraphMessage/GMessageManager.h - Unique connection IDs (
connId) generated byCGRAPH_BIND_MESSAGE_TOPICto isolate subscriber queues - Fan-out publishing via
CGRAPH_PUB_MPARAMthat delivers copies to all bound subscribers concurrently - Thread-safe singleton architecture using
GMessageManagerSingletonwith mutex-protected maps (pub_sub_mutex_,send_recv_mutex_) - Type-safe message parameters requiring all messages to derive from
GMessageParam
This architecture allows independent pipelines to exchange typed data without shared memory or direct references, scaling from single-threaded applications to complex multi-pipeline workflows.
Frequently Asked Questions
How does CGraph ensure that multiple subscribers receive the same message independently?
When a publisher calls CGRAPH_PUB_MPARAM, the underlying GMessageManager::pubTopicValue retrieves the set of all queues bound to that topic from pub_sub_message_map_. It then iterates through every subscriber queue and calls send() on each, pushing a copy of the message into each subscriber's independent buffer. Because each subscriber maintains its own connId-mapped queue in conn_message_map_, consumption speed and timing do not affect other subscribers.
What is the difference between CGRAPH_CREATE_MESSAGE_TOPIC and CGRAPH_BIND_MESSAGE_TOPIC?
CGRAPH_CREATE_MESSAGE_TOPIC establishes a point-to-point (send/receive) communication channel, storing the queue in send_recv_message_map_ with the SEND_RECV_PREFIX. In contrast, CGRAPH_BIND_MESSAGE_TOPIC creates a publish/subscribe relationship, inserting the queue into both pub_sub_message_map_ (for fan-out delivery) and conn_message_map_ (for unique subscriber identification). Only CGRAPH_BIND_MESSAGE_TOPIC returns a connId required for receiving messages.
How does CGraph handle thread safety when pipelines publish and subscribe concurrently?
The GMessageManager class protects shared state with two primary mutexes: pub_sub_mutex_ guards pub_sub_message_map_ during topic binding and publishing operations, while send_recv_mutex_ protects send_recv_message_map_. Additionally, individual GMessage queue instances implement internal locking for their send and recv methods. This multi-layered locking ensures that publishers can fan out messages to multiple subscribers, and subscribers can pull from their private queues, all without data races or corruption.
Can different message types be used on the same topic in CGraph?
No, CGraph enforces type safety through templates. When you bind a topic with CGRAPH_BIND_MESSAGE_TOPIC(MyMessageParam, "topic-name", size), the manager creates a GMessage<MyMessageParam> queue. If a publisher attempts to send a different message type to the same topic using CGRAPH_PUB_MPARAM, the template system will catch the type mismatch at compile time. This design prevents type confusion and ensures that all subscribers receive the expected message structure.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →