mc::app service 应用服务框架

Header: #include <mc/app.h>   Namespace: mc::app

概述

mc::app::service 是应用层服务框架的核心类,继承自引擎层 mc::engine::service,在其基础上增加了面向业务的完整生命周期管理、D-Bus 传输自动搭建、MicroComponent 标准 D-Bus 对象、IPMI 命令注册以及 service_context 驱动的配置注入。

mc::app::service 的核心职责是在 openUBMC 架构中作为“微组件”宿主——每个微组件对应一个服务实例,框架通过 service_registry 管理服务类型注册与工厂创建,通过 service_plan / service_definition 描述部署拓扑,并通过 dependency_tracker 管理服务间的启动依赖顺序。

与引擎层 mc::engine::service 的核心差异在于:

维度 mc::engine::service mc::app::service
生命周期 init → start → stop → cleanup,无显式状态机 configure → start → stop,7 态显式状态机
传输层 依赖外部注入 service_proto/service_backend 内置 D-Bus connection,configure 阶段自动搭建
路径 无 path 概念 持有 m_path(D-Bus 对象路径)
扩展能力 无 持有 micro_component_object、remote_persist_bridge、task::TaskMgmt、IPMI 命令注册
配置注入 init(dict) 直接传参 configure(service_context, dict) 注入完整上下文

主要特性

  • 7 态显式生命周期:created → configured → starting → running → stopping → stopped → failed,框架严格校验状态迁移
  • 生命周期钩子:on_start() 和 on_stop() 为虚函数(默认实现返回 true),子类按需覆盖
  • D-Bus 传输自动管理:configure 阶段自动调用 bring_up_dbus_transport(),stop 阶段调用 tear_down_dbus_transport()
  • MicroComponent 标准对象:每个服务自动挂载 micro_component_object,对外暴露健康检查、配置管理、调试、重启/重置等标准 D-Bus 接口
  • IPMI 命令注册:通过 register_ipmi_cmd / unregister_ipmi_cmd 动态添加/移除 IPMI 命令处理
  • 跨服务查找:通过 service_context::find_service() 在同应用内按名称查找其他服务实例
  • 类型安全注册:service_registry 模板方法 register_service<ServiceType>() 自动推导工厂、校验器和配置描述符
  • 全局静态注册宏:MC_APP_REGISTER_GLOBAL_SERVICE(ServiceType, "TypeName") 编译期注册,无需显式调用
  • 依赖跟踪:dependency_tracker 提供 required/optional 依赖记账与就绪回调调度,支持拓扑排序

基本用法

示例 1:定义自定义服务

继承 mc::app::service,按需覆盖 on_start() 和 on_stop():

#include <mc/app.h>
#include <iostream>

class SensorService : public mc::app::service {
public:
    explicit SensorService(mc::string name) : mc::app::service(std::move(name)) {}

protected:
    bool on_start() override {
        // 初始化硬件、注册 D-Bus 对象等
        std::cout << "SensorService[" << name() << "] started, path="
                  << context().definition().path << std::endl;
        return true;
    }

    bool on_stop() override {
        // 清理资源、解注册 D-Bus 对象等
        std::cout << "SensorService[" << name() << "] stopped" << std::endl;
        return true;
    }

    bool on_configure() override {
        // 可选:校验配置属性
        const auto& props = properties();
        if (!props.contains("poll_interval_ms")) {
            return false;
        }
        return true;
    }
};

// 静态全局注册,使框架能通过类型名 "SensorService" 创建实例
MC_APP_REGISTER_GLOBAL_SERVICE(SensorService, "SensorService")

示例 2:通过 service_plan 配置并启动服务

#include <mc/app.h>
#include <iostream>

int main()
{
    mc::app::service_plan plan;
    plan.application.name = "bmc-app";

    mc::app::service_definition sensor_def;
    sensor_def.name       = "sensor";
    sensor_def.path       = "/bmc/kepler/sensor";
    sensor_def.type       = "SensorService";
    sensor_def.properties = mc::dict{{"poll_interval_ms", int32_t{500}}};
    plan.services.push_back(sensor_def);

    mc::app::service_registry registry;
    registry.import_global_services();

    for (const auto& def : plan.services) {
        if (!def.enabled) continue;
        auto svc = registry.create_service(def.type, def.name);
        if (!svc) continue;

        mc::app::service_context ctx(/* application& */ /* ... */, plan, def);
        if (!svc->configure(ctx, def.properties)) continue;
        if (!svc->start()) continue;

        std::cout << "Service " << def.name << " state: "
                  << static_cast<int>(svc->state()) << std::endl;
    }
    return 0;
}

示例 3:注册 IPMI 命令

#include <mc/app.h>
#include <iostream>

class IpmiService : public mc::app::service {
public:
    explicit IpmiService(mc::string name) : mc::app::service(std::move(name)) {}

protected:
    bool on_start() override {
        mc::app::ipmi_cmd cmd{
            .netfn = 0x30, .cmd = 0x96, .name = "GetCustomInfo",
            .decode = "b", .encode = "{s:s, s:i}",
        };
        register_ipmi_cmd(cmd, [](mc::dict request, mc::dict ipmi_ctx) -> mc::dict {
            mc::dict response;
            response["status"] = mc::string("ok");
            response["value"]  = int32_t{42};
            return response;
        });
        return true;
    }

    bool on_stop() override { return true; }
};

MC_APP_REGISTER_GLOBAL_SERVICE(IpmiService, "IpmiService")

示例 4:使用 dependency_tracker 管理服务启动依赖

#include <mc/app/dependency_tracker.h>
#include <mc/app.h>
#include <iostream>

class DependentService : public mc::app::service {
public:
    explicit DependentService(mc::string name) : mc::app::service(std::move(name)) {}

protected:
    bool on_start() override {
        auto entry_id = m_tracker.add_entry(
            {"/bmc/kepler/sensor"}, {},
            [this](const std::vector<mc::string>&) {
                std::cout << "Dependency satisfied" << std::endl;
            });
        std::cout << "Entry registered, id=" << entry_id << std::endl;
        return true;
    }

    bool on_stop() override { return true; }

    void on_dependency_ready(mc::string_view ref) {
        m_tracker.mark_satisfied(ref);
    }

private:
    mc::app::dependency_tracker m_tracker;
};

MC_APP_REGISTER_GLOBAL_SERVICE(DependentService, "DependentService")

API 详细说明

service 类

// service.h:38
enum class service_state { created, configured, starting, running, stopping, stopped, failed };

// service.h:40
class MC_API service : public mc::engine::service {
public:
    static constexpr mc::string_view bus_name_prefix() noexcept;            // "bmc.kepler."
    static mc::string_view           to_short_name(mc::string_view) noexcept;
    mc::string_view                  short_name() const noexcept;

    explicit service(mc::string name);
    ~service() override;
    // 禁止拷贝/移动
    service(const service&)            = delete;
    service& operator=(const service&) = delete;
    service(service&&)                 = delete;
    service& operator=(service&&)      = delete;
    // ...
};
using service_ptr = mc::shared_ptr<service>;
方法 描述 异常
explicit service(mc::string name) 构造服务,初始状态为 created,name 作为服务标识。允许空 name(两阶段构造、后续定名,进入 start() 时才要求合法);非空但非法的 D-Bus 名抛异常 mc::exception(名称非法);std::bad_alloc
static constexpr mc::string_view bus_name_prefix() noexcept app 层 D-Bus 服务名前缀 "bmc.kepler."(命名空间契约,集中维护) 不抛出
static mc::string_view to_short_name(mc::string_view full_name) noexcept 从完整 D-Bus 名提取组件短名:"bmc.kepler.network_adapter" → "network_adapter";未带前缀或空串原样返回 不抛出
mc::string_view short_name() const noexcept 等价 to_short_name(name()),用于 hwdiscovery owner、日志模块名等以组件名为粒度的场景 不抛出
~service() 析构,清理 D-Bus 传输和微组件对象 不抛出
const mc::string& path() const noexcept 返回 D-Bus 对象路径,由 set_path 或 configure 设置 不抛出
service_state state() const noexcept 返回当前生命周期状态 不抛出
const mc::dict& properties() const noexcept 返回 configure 时注入的配置属性 不抛出
bool configure(const service_context& context, mc::dict properties) 注入服务上下文和配置属性,自动搭建 D-Bus 传输;状态从 created → configured。context 为空(operator bool() == false)时失败 std::bad_alloc(D-Bus 连接创建失败)
bool start() 启动服务,依次调用 bring_up_dbus_transport()(name() 非空时)和 on_start();状态 configured → starting → running。非 configured 状态调用返回 false on_start() 中异常向外传播;D-Bus 传输搭建失败时状态进入 failed
bool stop() 停止服务,调用 tear_down_dbus_transport() 和 on_stop();状态 running → stopping → stopped。非 running 状态调用返回 false on_stop() 中异常向外传播
void set_path(mc::string path) 设置 D-Bus 对象路径;通常在 configure 前由框架调用 std::bad_alloc(字符串内部分配失败)
mc::dbus::connection& connection() noexcept 获取 D-Bus 连接引用;仅在 D-Bus 传输搭建后有效 不抛出;D-Bus 未搭建时行为未定义
app_proto* proto() noexcept 获取协议对象指针 不抛出
bool has_dbus() const noexcept 检查 D-Bus 传输是否已建立 不抛出
micro_component_object* micro_component() noexcept 获取微组件对象(可变指针) 不抛出
const micro_component_object* micro_component() const noexcept 获取微组件对象(只读指针) 不抛出
void set_remote_persist_bridge(std::unique_ptr<remote_persist_bridge> bridge) 注入远程持久化桥接对象;同一 class 的对象共享不可变持久化配置 std::bad_alloc(移动赋值内部分配失败)
remote_persist_bridge* get_remote_persist_bridge() noexcept 获取远程持久化桥接对象指针 不抛出
task::TaskMgmt& task_mgmt() 获取本 service 的任务管理组件(惰性创建,生命周期随 service,stop() 时连同全部任务释放);详见 task.md std::bad_alloc(首次创建分配失败)
void register_ipmi_cmd(const ipmi_cmd& cmd, ipmi_process_handler handler) 注册 IPMI 命令:创建 ipmi_cmd_object 并注册到服务对象树 std::bad_alloc(对象创建失败);命令已存在时行为取决于实现
void unregister_ipmi_cmd(const ipmi_cmd& cmd) 注销 IPMI 命令:从对象树移除对应 ipmi_cmd_object 命令不存在时静默返回

受保护方法(子类钩子):

方法 描述 异常
virtual bool on_init(mc::dict properties) override 初始化钩子,先调用引擎层 on_init 再进行 D-Bus 传输搭建。子类通常无需覆盖 引擎层初始化或 D-Bus 搭建失败时返回 false
virtual bool on_configure() 配置校验钩子,在 configure() 末尾调用。默认返回 true。子类覆盖用于校验 properties() 的完整性 校验失败返回 false 会导致 configure() 失败
virtual bool on_start() override 服务启动钩子,默认实现返回 true。子类覆盖执行启动逻辑(初始化硬件、注册对象等) 异常向外传播,导致 start() 失败
virtual bool on_stop() override 服务停止钩子,默认实现返回 true。子类覆盖执行停止逻辑(清理资源、解注册对象等) 异常向外传播,导致 stop() 失败
const service_context& context() const 获取服务上下文引用;仅在 configure() 成功调用后有效 configure() 未调用时行为未定义
void set_state(service_state state) noexcept 子类内部状态迁移;通常由 start()/stop() 自动管理,子类仅在异常路径使用 不抛出

service_context 类

// service_context.h:29
class MC_API service_context {
public:
    service_context() = default;
    service_context(application& app, const service_plan& plan,
                    const service_definition& definition);
    explicit operator bool() const noexcept;
    // ...
};

service_context 封装服务运行时依赖:所属 application、service_plan 和 service_definition。通过 operator bool() 校验完整性。

方法 描述 异常
service_context() 默认构造空上下文,operator bool() 返回 false 不抛出
service_context(application&, const service_plan&, const service_definition&) 完整上下文构造,绑定三项依赖 不抛出
explicit operator bool() const noexcept 当 m_app、m_plan、m_definition 均为非空时返回 true 不抛出
application& app() const 获取所属 application 引用 上下文为空时行为未定义
mc::runtime::runtime_context& runtime() const 获取运行时上下文 上下文为空时行为未定义
const service_plan& plan() const 获取服务计划 上下文为空时行为未定义
const application_definition& application_config() const 获取应用级配置(线程数、模块列表等) 上下文为空时行为未定义
const service_definition& definition() const 获取当前服务定义 上下文为空时行为未定义
service_ptr find_service(mc::string_view name) const 跨服务查找:在同应用内按服务名查找另一服务实例 未找到返回 nullptr
mc::runtime::any_executor get_default_executor() const 获取默认执行器(基于 application_config 的线程配置) std::bad_alloc(执行器创建失败)
mc::runtime::any_executor create_io_strand() const 创建 IO 串行执行器(strand) std::bad_alloc(执行器创建失败)
mc::runtime::any_executor create_work_strand() const 创建工作串行执行器(strand) std::bad_alloc(执行器创建失败)
void quit(int exit_code = 0) const 请求整个应用退出 不抛出

service_registry 类

// service_registry.h:174
class MC_API service_registry {
public:
    void register_descriptor(service_descriptor, service_creator,
                             service_property_validator = {});
    template<typename ServiceType>
    void register_service(mc::string type_name, bool enabled_by_default = false);
    // ...
};
方法 描述 异常
register_descriptor(descriptor, creator, validator) 原始注册:手动传入描述符、工厂函数和校验器 std::bad_alloc(内部 map 插入分配失败)
register_service<ServiceType>(type_name, enabled_by_default) 模板注册:从 ServiceType 推导工厂(make_shared<ServiceType>(name))、校验器(反射检查 config_type)和配置描述符(反射推导默认属性和 schema) std::bad_alloc;ServiceType 不是 service 的子类时编译失败
bool has_service(mc::string_view type_name) const 检查是否已注册该类型 不抛出
const service_descriptor* find(mc::string_view type_name) const 按类型名查找描述符 未找到返回 nullptr
service_ptr create_service(type_name, name) 创建服务实例,调用对应工厂函数 std::bad_alloc;类型未注册时返回 nullptr
bool validate_service_properties(type_name, dict&) 通过反射校验属性是否匹配 config_type 的字段签名 类型注册了 config_type 且属性不匹配时返回 false
mc::array<mc::string> get_service_types() const 列出所有已注册服务类型名 std::bad_alloc(返回数组内部分配失败)
void import_global_services() 导入所有通过 MC_APP_REGISTER_GLOBAL_SERVICE 静态注册的服务 std::bad_alloc

全局注册宏:

#define MC_APP_REGISTER_GLOBAL_SERVICE(ServiceType, ServiceName)

在编译单元中声明后,ServiceName 类型的服务在 import_global_services() 调用时自动注册。宏展开为匿名命名空间内的静态初始化 lambda。

关联类型:

类型 描述
service_descriptor { type_name, enabled_by_default, config } — 服务类型元信息
service_config_descriptor { version, default_properties, schema } — 配置元信息(反射推导)
service_creator mc::small_function<service_ptr(mc::string), 64> — 工厂函数
service_property_validator mc::small_function<bool(const mc::dict&), 64> — 属性校验器

service_plan / service_definition / application_definition

// service_plan.h
struct application_definition {
    mc::string            name;
    mc::string            config_file;
    mc::array<mc::string> modules;
    std::size_t           io_threads{2};
    std::size_t           work_threads{2};
};

struct rpc_admission_config {
    std::size_t max_inflight_per_pool{32};
    std::size_t max_queued_per_pool{0};
    std::size_t default_budget_ms{0};
    std::size_t max_budget_ms{0};
};

struct service_definition {
    mc::string name;       // 服务实例名称
    mc::string path;       // D-Bus 对象路径
    mc::string type;       // 服务类型名(对应 service_registry 中的 type_name)
    bool       enabled{true};
    mc::dict   properties; // 服务属性
};

struct service_plan {
    const service_definition* find_service(mc::string_view service_name) const;
    application_definition        application;
    mc::array<service_definition> services;
    bool has_logging_config{false};
};

升级兼容性:rpc_entry_max_inflight_per_pool 默认值为 32,应用未配置该字段时会为每个实际线程池启用入口准入, 容量耗尽的外部 RPC 将在对应池的有界队列中等待并按预算过期。IO 与 Work 使用独立线程池时,进程总入口额度是两池额度之和; work_threads 为 0 时 Work 复用 IO 池,不会重复计算。依赖旧版直接派发语义的应用必须显式配置 rpc_entry_max_inflight_per_pool: 0;这会关闭准入保护,同时恢复未配置准入时的派发路径。生产 环境未设置 rpc_entry_max_queued_per_pool,或该值为 0、负数时,按并发额度推导排队上限,默认额度为 512。需要其他上限时再显式配置正整数。

rpc_admission_config 独立于 service_plan,用于编程式调用 initialize_with_plan(plan, config);配置文件中的 同名 rpc_* 键由 mcapp 在启动时解析并放入该独立配置。这样新增准入字段不会扩展既有计划对象布局。

当应用接管一个已经由宿主启动的 mc::runtime::runtime_context 时,宿主拥有线程池和准入配置, application 计划中的上述字段不会覆盖它;应用只使用宿主提供的运行时。需要应用计划生效时, 应由宿主在 application 启动前完成相同配置,或在 application 启动前停止并重置 runtime,再调用 initialize_with_plan()。

方法/字段 描述 异常
service_plan::find_service(name) 线性扫描 services 数组,按名称查找服务定义 不抛出;未找到返回 nullptr

dependency_tracker 类

注意:dependency_tracker 不在 mc/app.h 中,需单独 #include <mc/app/dependency_tracker.h>。

// dependency_tracker.h:35
class MC_API dependency_tracker {
public:
    using ready_callback = std::function<void(const std::vector<mc::string>& satisfied_optional)>;

    dependency_tracker();
    ~dependency_tracker();
    // 禁止拷贝/移动
    dependency_tracker(const dependency_tracker&)            = delete;
    dependency_tracker& operator=(const dependency_tracker&) = delete;
    dependency_tracker(dependency_tracker&&)                 = delete;
    dependency_tracker& operator=(dependency_tracker&&)      = delete;
    // ...
};

dependency_tracker 提供 required/optional 依赖记账与就绪回调调度。不直接触达 IPC,依赖标记由外部驱动(如 D-Bus InterfacesAdded 信号或对象表 on_object_added)。

方法 描述 异常
dependency_tracker() 构造空的依赖跟踪器 std::bad_alloc(内部实现分配失败)
~dependency_tracker() 析构,清理所有未触发的 entry 不抛出
std::uint64_t add_entry(required, optional, callback) 登记依赖:全部 required 满足后触发 callback(单次调用后自动移除);optional 仅回吐不阻塞。required 为空时同步触发 callback。返回非零 entry id std::bad_alloc(内部分配失败)
void cancel(std::uint64_t entry_id) 取消尚未触发的 entry 不抛出;id 无效时静默忽略
void mark_satisfied(mc::string_view ref) 标记引用 ref 已满足,驱动所有相关 entry 的依赖检查 不抛出
void mark_unsatisfied(mc::string_view ref) 反向标记:将 ref 重新加入尚未触发 entry 的 required 集合。已触发的 entry 不受影响 不抛出
std::size_t satisfied_size() const 当前已满足的 ref 数量快照(诊断/测试用) 不抛出
std::size_t pending_size() const 当前未触发的 entry 数量快照(诊断/测试用) 不抛出

最佳实践

✅ 使用 MC_APP_REGISTER_GLOBAL_SERVICE 静态注册

在服务实现文件的命名空间作用域使用宏注册,确保 import_global_services() 能自动发现:

// sensor_service.cpp
#include <mc/app.h>

class SensorService : public mc::app::service { /* ... */ };

MC_APP_REGISTER_GLOBAL_SERVICE(SensorService, "SensorService")

优点:无需在 main() 中手动调用 registry.register_service<SensorService>(...),减少启动代码耦合。

✅ 在 on_configure 中校验必需配置项

on_configure() 在 configure() 末尾调用,此时 properties() 已填充。在此校验配置完整性是最佳时机:

class MyService : public mc::app::service {
public:
    explicit MyService(mc::string name) : mc::app::service(std::move(name)) {}
protected:
    bool on_configure() override {
        const auto& props = properties();
        if (!props.contains("interval_ms")) {
            return false; // configure() 将返回 false
        }
        if (props["interval_ms"].as<int32_t>() <= 0) {
            return false;
        }
        return true;
    }
    bool on_start() override  { return true; }
    bool on_stop() override { return true; }
};

✅ 通过 service_context 获取执行器而非自行创建线程

使用 context().create_io_strand() / create_work_strand() 创建执行器,享受框架统一的线程池管理:

bool on_start() override {
    auto strand = context().create_work_strand();
    mc::runtime::post(strand, [this]() { do_work(); });
    return true;
}

❌ 不要在 configure() 之前调用 start()

start() 要求当前状态为 configured。若在 configure() 之前调用,状态检查失败返回 false:

auto svc = mc::make_shared<MyService>("mysvc");
// 错误:状态为 created,start() 返回 false
bool ok = svc->start(); // ok == false

// 正确流程
mc::app::service_context ctx(/* ... */);
svc->configure(ctx, mc::dict{{"interval_ms", int32_t{100}}});
svc->start(); // 状态迁移:configured → starting → running

❌ 不要忘记 on_start() 和 on_stop() 必须返回 bool

on_start() 和 on_stop() 签名返回 bool。返回 false 将导致 start() / stop() 返回 false,且框架不会自动将状态置为 running / stopped:

// 错误:返回 void
bool on_start() override {
    init_hardware();         // 假设此函数返回 void
    // 忘记 return true; —— 编译警告,运行时未定义
}

// 正确:显式返回
bool on_start() override {
    if (!init_hardware()) {
        return false;
    }
    return true;
}

❌ 不要在 on_start() 中执行长时间阻塞操作

on_start() 由框架在主线程或控制线程同步调用,长时间阻塞会延迟整体启动。应将阻塞操作投递到 work strand 异步执行:

// 错误:阻塞启动
bool on_start() override {
    std::this_thread::sleep_for(std::chrono::seconds(5)); // 阻塞整个启动流程
    return true;
}

// 正确:异步初始化
bool on_start() override {
    auto strand = context().create_work_strand();
    mc::runtime::post(strand, [this]() {
        heavy_init(); // 在 work 线程上执行
    });
    return true;
}

❌ 不要在服务析构后继续持有 context() 返回的引用

service_context 持有的 application&、service_plan&、service_definition& 均为引用语义,生命周期由调用方管理。服务析构后继续使用这些引用导致悬垂引用:

const mc::app::service_definition* g_def = nullptr;

void bad_capture(mc::app::service& svc) {
    g_def = &svc.context().definition(); // 危险:svc 析构后 g_def 悬垂
}