Skip to content

Services and Lifecycle

Oleksandr Geronime edited this page Jun 27, 2026 · 2 revisions

Services and Lifecycle

Overview

SERP applications are built from services — self-contained units of functionality that run on dedicated threads. The serp::App singleton owns all event loops, manages service registration, and coordinates the lifecycle of every service in the process.


serp::App

serp::App is the process-level singleton. It:

  • Creates and owns all EventLoop instances
  • Registers services and manages their dependencies
  • Drives the lifecycle state machine for all services
  • Blocks the main thread in run() until shutdown is requested

Startup pattern:

int main(int argc, char** argv) {
    return serp::App::instance().run(argc, argv);
}

Service registration is handled in the generated main.cpp. You do not typically call App APIs directly in service code.


serp::Service

serp::Service is the base class for all application services. Each service is permanently bound to a named EventLoop (a single background thread). All methods and callbacks on a service execute on that thread — no additional locking is required for service-owned state.

Constructor signature:

serp::Service(serp::EventLoop& loop, std::string name);
Parameter Description
loop The EventLoop this service is bound to. Injected by the framework.
name Human-readable name used in logs and diagnostics.

Lifecycle Phases

Every service passes through five phases in order:

Created ──► Initializing ──► Initialized ──► Deinitializing ──► Deinitialized
Phase Description
Created Service object has been constructed. No I/O or cross-service calls.
Initializing onInitialize() is executing. Resources may be acquired; dependencies are available.
Initialized Service is fully operational. Handles calls, fires notifications, processes timers.
Deinitializing onDeinitialize() is executing. Resources should be released.
Deinitialized Service is shut down. The process may exit.

The framework guarantees:

  • onInitialize() is always called before the service handles any external requests.
  • onDeinitialize() is always called before the process exits, even on graceful shutdown signals.
  • Both callbacks execute on the service's own EventLoop.

Key Overrides

onInitialize()

Called when the framework is ready for the service to start. Use this to:

  • Connect to hardware or external resources
  • Register timer callbacks
  • Subscribe to properties and notifications from other services
void onInitialize() override {
    m_timer = serp::Timer::create(eventLoop(), std::chrono::seconds(1), [this]() {
        pollStatus();
    });
    m_timer->start();
}

onDeinitialize()

Called during shutdown. Use this to:

  • Stop timers
  • Close connections
  • Release acquired resources
void onDeinitialize() override {
    if (m_timer) {
        m_timer->stop();
    }
}

Thread Binding

Every service is bound to exactly one EventLoop. The framework enforces this binding:

  • All serp::Call handlers registered on a service execute on its loop.
  • serp::Property::mutate() dispatches writes to the owning service's loop.
  • serp::Notification::connect() delivers events on the subscriber's loop.
  • Timer callbacks fire on the loop they were created with.

This model eliminates data races for service-owned state without requiring explicit mutexes.


Minimal Service Example

#include <serp/service.h>

class MyService : public serp::Service {
public:
    MyService(serp::EventLoop& loop)
        : serp::Service(loop, "MyService") {}

protected:
    void onInitialize() override {
        // Acquire resources, start timers, subscribe to other services
    }

    void onDeinitialize() override {
        // Release resources, stop timers
    }

private:
    // Service-owned state — safe to access without locking
    int m_counter = 0;
};

Service Registration

Services are registered in the generated main.cpp. The framework resolves construction order based on declared dependencies:

// Generated main.cpp (do not edit by hand)
int main(int argc, char** argv) {
    auto& app = serp::App::instance();

    app.registerService<MyService>();
    app.registerService<OtherService>();

    return app.run(argc, argv);
}

The code generator derives this file from the SerpIDL composition definition. Manual edits are overwritten on the next generation pass.


See Also

Clone this wiki locally