How to Use GDaemon for Background Tasks in CGraph: A Complete Guide

Use GDaemon for background tasks in CGraph by inheriting from the GDaemon base class, overriding the daemonTask method, and registering the instance with GPipeline::addGDaemon(intervalMs).

The CGraph repository provides a dedicated daemon subsystem that enables periodic background execution alongside your main pipeline processing. Unlike standard graph nodes that execute within the dataflow, GDaemon instances run on independent timer threads, making them ideal for monitoring, heartbeat signals, or simulated I/O operations.

What Is GDaemon in CGraph?

GDaemon is an abstract base class defined in src/GraphCtrl/GraphDaemon/GDaemon.h that encapsulates a lightweight, timer-driven background worker. It is part of a three-layer architecture:

  • GDaemon – The abstract interface you subclass. It implements timer handling and declares the pure virtual daemonTask method.
  • GDaemonObject – Defined in src/GraphCtrl/GraphDaemon/GDaemonObject.h, provides common utilities including parameter managers and interval setters.
  • GDaemonManager – Implemented in src/GraphCtrl/GraphDaemon/GDaemonManager.cpp, owns daemon instances and manages their init() and destroy() lifecycle.

The subsystem relies on UTimer, an internal utility that spawns a separate thread to trigger your task at a configurable millisecond interval.

GDaemon Architecture and Lifecycle

Core Components

The daemon system integrates tightly with the pipeline execution model. When you call addGDaemon, the pipeline (specifically the __addGDaemon_4py method in src/GraphCtrl/GraphPipeline/GPipeline.cpp) performs three critical actions:

  1. Instantiates your daemon template.
  2. Invokes setInterval(intervalMs) to configure the timer period.
  3. Injects pointers to the pipeline’s GParamManager and GEventManager, enabling the daemon to publish messages or modify shared state.

Lifecycle Stages

A daemon progresses through four distinct states:

  1. Construction – You create the daemon class and pass it to addGDaemon.
  2. Initialization – When pipeline->init() runs, GDaemonManager::init() iterates all daemons and calls GDaemon::init(), which starts the internal UTimer.
  3. Execution – The timer thread invokes daemonTask every interval_ milliseconds until the pipeline stops.
  4. Destructionpipeline->destroy() triggers GDaemonManager::clear(), which stops timers and deletes daemon instances.

Thread Safety Model

Each daemon operates on its own dedicated timer thread, isolated from the pipeline’s worker thread pool. The timer thread exclusively executes daemonTask and the optional modify hook (which can adjust the next interval dynamically).

Because the pipeline injects GParamManager and GEventManager pointers during registration, your daemon can safely call CGRAPH_PUB_MPARAM or modify parameters using the same thread-safe mechanisms available to standard nodes.

Implementing a Custom GDaemon

Basic Monitor Daemon

The simplest daemon overrides daemonTask to perform periodic logging or health checks. Here is a minimal monitor implementation based on tutorial/MyGDaemon/MyMonitorDaemon.h:

// MyMonitorDaemon.h
#ifndef CGRAPH_MYMONITORDAEMON_H
#define CGRAPH_MYMONITORDAEMON_H

#include "CGraph.h"

class MyMonitorDaemon : public CGraph::GDaemon {
public:
    CVoid daemonTask(CGraph::GDaemonParamPtr) override {
        // getInterval() returns the configured period in milliseconds
        CGraph::CGRAPH_ECHO(
            "----> [MyMonitorDaemon] still running, span = %ld ms", 
            getInterval());
    }
};

#endif // CGRAPH_MYMONITORDAEMON_H

Register this daemon with a 4-second interval:

pipeline->addGDaemon<MyMonitorDaemon>(4000);

Daemon with Custom Parameters

For configuration-heavy tasks, subclass GDaemonParam to pass initialization data. This example mirrors tutorial/MyGDaemon/MyParamDaemon.h:

// MyConnParam.h
#ifndef CGRAPH_MYCONNPARAM_H
#define CGRAPH_MYCONNPARAM_H

#include "CGraph.h"

struct MyConnParam : public CGraph::GDaemonParam {
    std::string ip_ = "127.0.0.1";
    int port_ = 8080;
};

#endif
// MyParamDaemon.h
#ifndef CGRAPH_MYPARAMDAEMON_H
#define CGRAPH_MYPARAMDAEMON_H

#include "CGraph.h"
#include "MyConnParam.h"

class MyParamDaemon : public CGraph::GDaemon {
public:
    CVoid daemonTask(CGraph::GDaemonParamPtr param) override {
        auto* p = static_cast<MyConnParam*>(param);
        CGraph::CGRAPH_ECHO(
            "Param daemon: connecting to %s:%d", 
            p->ip_.c_str(), p->port_);
    }
};

#endif

Attach to the pipeline with the custom parameter:

MyConnParam connParam;
connParam.ip_ = "192.168.1.100";
connParam.port_ = 6666;

pipeline->addGDaemon<MyParamDaemon, MyConnParam>(3500, &connParam);

Real-World Example: Camera Simulation Daemon

In production pipelines, daemons often simulate hardware inputs. The CameraGDaemon from example/E01-AutoPilot.cpp demonstrates publishing messages to the pipeline’s event bus:

#include "CGraph.h"

class CameraGDaemon : public CGraph::GDaemon {
public:
    CVoid daemonTask(CGraph::GDaemonParamPtr) override {
        auto image = std::make_shared<ImageMParam>();
        image->frame_id_ = cur_index_;
        std::string info = "this is " + std::to_string(cur_index_) + " image";
        memcpy(image->image_buf_, info.c_str(), info.length());
        cur_index_++;

        // Publish to the pipeline's message topic
        CGRAPH_PUB_MPARAM(ImageMParam, EXAMPLE_IMAGE_TOPIC,
                          image, CGraph::GMessagePushStrategy::WAIT);
    }

private:
    int cur_index_ = 0;
};

Register with a 1-second interval:

pipeline->addGDaemon<CameraGDaemon>(1000);

Downstream nodes (LaneDetectorGNode, CarDetectorGNode) subscribe to EXAMPLE_IMAGE_TOPIC and process each frame independently of the daemon’s timer thread.

Registering Daemons with GPipeline

The GPipeline class exposes the addGDaemon template method (internally __addGDaemon_4py in src/GraphCtrl/GraphPipeline/GPipeline.cpp) to bind daemons to the pipeline lifecycle.

Syntax:

template<typename TDaemon, typename TParam = GDaemonDefaultParam>
GPipelinePtr addGDaemon(CMSec interval, TParam* param = nullptr);

Parameters:

  • TDaemon – Your subclass of GDaemon.
  • TParam – Optional custom parameter type derived from GDaemonParam.
  • interval – Timer period in milliseconds.
  • param – Pointer to the custom parameter instance (can be nullptr).

Complete Example:

void demo_pipeline() {
    auto pipeline = CGraph::GPipelineFactory::create();

    // Register a processing node
    CGraph::GElementPtr node = nullptr;
    pipeline->registerGElement<MyNode>(&node, {}, "processor");

    // Attach two daemons
    pipeline->addGDaemon<MyMonitorDaemon>(4000)
            ->addGDaemon<MyParamDaemon, MyConnParam>(3500, &connParam);

    // Execute 20 pipeline iterations
    pipeline->process(20);
    CGraph::GPipelineFactory::remove(pipeline);
}

Summary

  • GDaemon is an abstract base class in src/GraphCtrl/GraphDaemon/GDaemon.h designed for periodic background tasks that run independently of the main pipeline execution flow.
  • Implement background work by overriding the daemonTask method and registering the daemon via GPipeline::addGDaemon<T>(intervalMs, param).
  • Daemons are managed by GDaemonManager, which handles initialization and cleanup, while UTimer provides the underlying thread-per-daemon scheduling.
  • Each daemon receives access to the pipeline’s GParamManager and GEventManager, enabling safe interaction with shared state and message publishing.
  • Use custom GDaemonParam subclasses to inject configuration data into the daemon’s execution context.

Frequently Asked Questions

What is the difference between GDaemon and a regular GNode?

A GNode executes within the pipeline’s dataflow graph, triggered by dependencies and scheduled on the pipeline’s thread pool. A GDaemon runs on its own independent timer thread, executing periodically regardless of the pipeline’s processing state. Daemons are ideal for monitoring, heartbeat signals, or simulating external hardware inputs, while nodes perform the actual data transformation.

How do I pass configuration data to a GDaemon?

Create a struct that inherits from CGraph::GDaemonParam, populate it with your configuration values, and pass a pointer to it as the second argument to addGDaemon. Inside daemonTask, cast the GDaemonParamPtr back to your concrete type. See the MyConnParam example in tutorial/MyGDaemon/MyParamDaemon.h for a working implementation.

Is GDaemon thread-safe for accessing pipeline parameters?

Yes. When you register a daemon via addGDaemon, the pipeline automatically injects pointers to its GParamManager and GEventManager into the daemon instance. The daemon can safely read or modify shared parameters and publish messages using the same thread-safe mechanisms available to standard nodes, because the underlying managers handle synchronization internally.

How do I stop or modify a running daemon's interval?

The daemon's interval is stored in the interval_ member variable (milliseconds). You can override the modify method in your subclass to dynamically adjust the next interval based on runtime conditions. To stop a daemon permanently, you must destroy the pipeline or manually manage the daemon's lifecycle through GDaemonManager::clear(), as there is no public stop() method exposed for individual daemons.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →