Examples Documentation Contact Downloads

Building Daemons

The daemon class helps you write applications that should remain resident in the background. Typically daemons have no communication with a terminal.

Using mkproject for Daemons

The grace mkproject tool recognizes the --daemon flag to create a project directory suitable for daemon development. The skeleton cpp file looks like this:

#include "storpeld.h"

$appobject (storpeldApp);

// ==========================================================================
// CONSTRUCTOR storpeldApp
// ==========================================================================
storpeldApp::storpeldApp (void)
    : daemon ("com.openpanel.svc.storpeld"),
      conf (this)
{
}

// ==========================================================================
// DESTRUCTOR storpeldApp
// ==========================================================================
storpeldApp::~storpeldApp (void)
{
}

// ==========================================================================
// METHOD storpeldApp::main
// ==========================================================================
int storpeldApp::main (void)
{
    string conferr; ///< Error return from configuration class.
    
    // Add watcher value for event log. System will daemonize after
    // configuration was validated.
    conf.addwatcher ("system/eventlog", &storpeldApp::confLog);
    
    // Load will fail if watchers did not valiate.
    if (! conf.load ("com.openpanel.svc.storpeld", conferr))
    {
        ferr.writeln ("%% Error loading configuration: %s" %format (conferr));
        return 1;
    }
    
    daemonize ();
    log::write (log::info, "main", "storpeld started");
    value ev;
    
    while (true)
    {
        ev = waitevent ();
        if (ev.type() == "shutdown") break;
    }

    log::write (log::info, "main", "Shutting down");
    stoplog();
    return 0;
}

// ==========================================================================
// METHOD storpeldApp::confLog
// ==========================================================================
bool storpeldApp::confLog (config::action act, keypath &kp,
                              const value &nval, const value &oval)
{
    string tstr;
    
    switch (act)
    {
        case config::isvalid:
            // Check if the path for the event log exists.
            tstr = strutil::makepath (nval.sval());
            if (! tstr.strlen()) return true;
            if (! fs.exists (tstr))
            {
                ferr.writeln ("%% Log path %s does not exist" %format (tstr));
                return false;
            }
            return true;
            
        case config::create:
            // Set the event log target and daemonize.
            fout.writeln ("%% Event log: %s\n" %format (nval));
            addlogtarget (log::file, nval.sval(), log::all, 1024*1024);
            return true;
    }
    
    return false;
}

The main method goes through a number of steps. First it sets up a configuration trigger (see below), then it loads the configuration and spawns to the background using daemonize. This call will also activate the log thread. Then the application waits for a "shutdown" event, which gets triggered by the process receiving a SIGTERM signal.

Configuration Triggers

The addwatcher method to configdb connects a method of your application class to a path within the configuration file. Such a path is separated by slashes, where a path element can also be '*' meaning "each value at this level", causing the passed method to be called for each and every node in the configuration that matches:

int storpeldApp::main (void)
{
    conf.addwatcher ("system/eventlog", &storpeldApp::confLog);
    conf.addwatcher ("servers/*", &storpeldApp::confServer);
    ...
}

bool storpeldApp::confServer (config::action act, keypath &kp,
                              const value &nval, const value &oval)
{
    switch (act)
    {
        case config::isvalid:
            return true;
            
        case config::create:
            createOutboundConnection (nval.id(), nval);
            return true;
    }
    return false;
}

Let's add an example configuration:


  <com.openpanel.svc.storpeld>
    <system>
      <eventlog>/var/log/storpeld.event.log</eventlog>
    </system>
    <servers>
      <server id="germany"><host>www.storpel.de</host></server>
      <server id="italy"><host>www.storpel.it</host></server>
    </servers>
  </com.openpanel.svc.storpeld>

Rigged like this, there will be two calls to createOutboundConnection on application startup.

The Log Thread

The daemon carries a thread object responsible for dispatching log messages. Through the log::write call you can send it log information. Since forking and threads are not good bedfellows, any log information that is dispatched before the call to daemonize is printed to the terminal and queued for later processing. The addlogtarget method can be used to add extra rules to the filter chain:

addlogtarget (log::file, "log:event.log", log::allinfo, 1024*1024);
addlogtarget (log::file, "log:debug.log", log::debug, 1024*1024);
addlogtarget (log::syslog, "storpeld", log::critical);

Delaying Daemonization

Normally, daemonize immediately forks your application to the background, giving the user back control over the terminal. It may be that you can't determine whether your application can start up succesfully without starting threads. In that case, call daemonize with an optional boolean set to true. This will cause the parent process to await confirmation from the child process that initialization succeeded before going back to the console. A call to either delayedexitok or delayedexiterror gives off this signal.

The .pid File

The daemon class will try to write a pid-file named after the application-id passed to its constructor to var:run/${appid}.pid. If such a file already exists and it contains a running pid, your application will refuse to launch.

Specifying Child Process Credentials

If you expect your application to be launched as root, you can specify the daemon to switch to a particular user during daemonize proceedings:

int storpeldApp::main (void)
{
    ...
    // Will look up user/group in /etc/passwd
    settargetuser ("storpel");
    
    // Specific userid
    settargetuser (501);
    
    // Specify group manually
    settargetuser ("storpel","daemon");
    
    // Specify additional group list
    settargetgroups ($("bin") -> $("nobody"));
}

 

Previous Chapter Table of Contents Next Chapter