engine 消息派发与编解码 C++ API 文档

头文件: #include <mc/engine.h> / #include <mc/engine/dispatcher.h>   命名空间: mc::engine

概述

动态库链接:消息头、消息及 payload 类型通过 MC_API 显式导出,支持调用方在默认隐藏内部符号的构建中跨动态库编解码消息。

mc::engine 的消息派发子系统是 engine 层进程内 / 跨进程通信的核心通道,由三层协作完成:

层 核心类型 职责
入口层 dispatch(), service::send(), service::send_with_reply(), service::call() 构造 message 并提交到 engine 内部路由
消息层 message, message_header, abstract_payload, make_payload() 统一消息载体,header 携带路由元数据,body 承载四种标准负载
编解码层 encode_message/decode_message 函数组, engine_codec message ↔ dict/bytes 序列化/反序列化

核心入口 dispatch() 是所有消息发送的统一门面:接收来自 service 的请求消息,通过 engine::route_inbound() 完成内部路由,返回 mc::future<message> 供调用方非阻塞等待响应。service_proto 面向网络协议适配,endpoint_service 面向 SHM 进程间通信。

主要特性

  • 明确入口来源:dispatch()/dispatch_method() 保留内部派发语义;D-Bus、SHM、MQ 和 harbor 入口必须调用带 external 后缀的 API,准入判断不依赖目标 executor 类型
  • 统一异步入口:dispatch() 将所有消息提交到 engine 路由,返回 future 实现非阻塞响应等待
  • 传输无关方法入口:dispatch_method() 供协议适配层直接提交方法调用,复用完整 engine 语义且不引入协议消息类型
  • 统一 D-Bus 错误映射:进程内、D-Bus、旧共享内存及 MQ 共用兼容的 D-Bus 错误名,内部诊断正文保留原样;映射和新通道接入规范见 dbus_error_mapping
  • 四类标准负载:method_call、method_return、error、signal,通过 payload<> CRTP 基类提供编译期静态类型标识
  • 类型安全转换:try_as<T>()(nullptr on mismatch)和 as<T>()(throw on mismatch)双重保障
  • 双轨序列化:函数式 API 适用一次性编解码;engine_codec 提供有状态、可复用的编解码器
  • Builder 模式:make_payload<Payload>(args...) 一行构造 typed payload
  • 消息订阅:add_match/remove_match 支持 match_rule + filter_spec 过滤,match_callback 处理匹配消息
  • 进程间路由:endpoint_service 在 SHM 模式下按 (endpoint_id, instance_id) 精准投递
  • 协议接入:service_proto 提供 inbound_handler + engine_codec 自动编解码链路
  • 已接受调用保活:method_call 在对象表锁内取得 target 强引用,跨 executor 投递及实际调用期间持续保活;并发 unregister/stop 只阻止后续查找,不会让已接受调用访问悬垂对象
  • 日志归属一致:内联、跨 executor 与目标缺失(信号广播)三条派发路径均安装目标服务短名的日志模块作用域,派发执行期日志归属不随执行路径漂移
  • 预算配置可复位:reset_rpc_config() 在应用释放 runtime 所有权时恢复进程级 RPC 默认预算,避免多个 application 依次启动时互相泄漏配置

基本用法

1. 创建 service,发送 method_call 并通过 send_with_reply 同步获取响应

#include <mc/engine.h>
#include <iostream>
#include <cassert>
using namespace mc::engine;

int main()
{
    service svc("client.service");
    assert(svc.init() && svc.start() && svc.is_healthy());

    message req;
    req.header.type = message_type::method_call;
    req.header.destination = "calculator.service";
    req.header.path = "/org/example/calc";
    req.header.interface_name = "org.example.Calculator";
    req.header.member_name = "Add";
    req.header.serial = 1;
    req.body = make_payload<method_call_payload>("ii", mc::variants{7, 8});

    message reply = svc.send_with_reply(req, mc::milliseconds(3000));
    if (reply.header.type == message_type::error) {
        auto& err = reply.as<error_payload>();
        std::cerr << "RPC error: " << err.name << " — " << err.message << std::endl;
        return 1;
    }
    auto& ret = reply.as<method_return_payload>();
    std::cout << "7 + 8 = " << ret.value.as<int>() << std::endl;

    svc.stop(); svc.cleanup();
    return 0;
}

2. async_send_with_reply 异步调用 + .then() 链式处理

#include <mc/engine.h>
#include <iostream>
using namespace mc::engine;

int main()
{
    service svc("async.client");
    svc.init(); svc.start();

    message req;
    req.header.type = message_type::method_call;
    req.header.destination = "sensor.service";
    req.header.path = "/org/example/sensor";
    req.header.interface_name = "org.example.Sensor";
    req.header.member_name = "GetTemperature";
    req.body = make_payload<method_call_payload>();

    svc.async_send_with_reply(req, mc::milliseconds(3000))
        .then([](message reply) {
            if (auto* ret = reply.try_as<method_return_payload>())
                std::cout << "temperature: " << ret->value.as<double>() << std::endl;
            else if (auto* err = reply.try_as<error_payload>())
                std::cerr << "error: " << err->name << std::endl;
        });

    svc.stop(); svc.cleanup();
    return 0;
}

3. 发射 signal 并通过 add_match 订阅接收

#include <mc/engine.h>
#include <iostream>
#include <cassert>
using namespace mc::engine;

int main()
{
    service pub("publisher.service");
    service sub("subscriber.service");
    pub.init(); pub.start();
    sub.init(); sub.start();

    int received = 0;
    match_rule rule;
    rule.type = "signal";
    rule.path = "/org/example/sensor/";
    rule.interface_name = "org.example.Sensor";
    rule.member_name = "TemperatureChanged";

    match_id id = sub.add_match(rule, filter_spec{},
        [&](const message& msg) {
            auto* sig = msg.try_as<signal_payload>();
            if (sig && !sig->args.empty()) { ++received; }
        });
    assert(id != 0);

    message sig;
    sig.header.type = message_type::signal;
    sig.header.path = "/org/example/sensor/temp1";
    sig.header.interface_name = "org.example.Sensor";
    sig.header.member_name = "TemperatureChanged";
    sig.body = make_payload<signal_payload>("d", mc::variants{36.5});
    pub.emit(sig);

    assert(received == 1);
    sub.remove_match(id);
    pub.stop(); pub.cleanup();
    sub.stop(); sub.cleanup();
    return 0;
}

4. endpoint_service 跨进程 SHM 投递

#include <mc/engine.h>
#include <iostream>
#include <cassert>
using namespace mc::engine;

int main()
{
    endpoint_service eps("host-bridge");
    assert(eps.init() && eps.start());

    uint16_t eid = eps.endpoint_id();
    uint32_t iid = eps.instance_id();

    message req;
    req.header.type = message_type::method_call;
    req.header.path = "/org/example/remote";
    req.header.interface_name = "org.example.Remote";
    req.header.member_name = "Ping";
    req.body = make_payload<method_call_payload>();

    if (eps.send_to_endpoint(2, 1, req))
        std::cout << "message queued" << std::endl;
    else
        std::cerr << "send_to_endpoint failed" << std::endl;

    eps.stop(); eps.cleanup();
    return 0;
}

API 详细说明

rpc_dispatch_origin

rpc_dispatch_origin::internal 仅用于进程内派发;rpc_dispatch_origin::external 只由 transport 适配器对应的 external API 使用。该枚举是 dispatcher 的内部实现契约,调用方不应 根据 executor 类型推导入口来源。

dispatch — 异步派发入口

MC_API mc::future<message> dispatch(const service& svc, message request);
MC_API mc::future<message> dispatch_external(const service& svc, message request,
                                              mc::runtime::entry_gate* admission_gate);
MC_API mc::future<message> dispatch_external(const service& svc, message request,
                                              mc::runtime::entry_gate* admission_gate,
                                              std::chrono::steady_clock::time_point server_ingress);
参数 描述 返回/异常
svc 发送方 service 引用,engine 根据其内部路由表查找目标 —
request 完整的请求消息(含 header + body) 返回 mc::future<message>,就绪时持有响应消息;std::bad_alloc;路由失败异常通过 future 传播

派发先按 path 从 service 对象表取得 mc::shared_ptr<abstract_object>,再读取 target executor 并投递。该强引用会捕获到任务中并传递给派发实现,因此对象在排队后被 unregister 或 service 同时 stop 时,当前调用仍按“查找成功时已接受”的语义完成;拓扑修改不会等待业务调用结束。

外部 method call 找不到目标时,除未注册路径上的 org.freedesktop.DBus.Introspectable.Introspect 外, 会在进入准入队列前立即返回 org.freedesktop.DBus.Error.UnknownObject,不消耗入口额度或队列容量。 未注册路径上的 Introspect 用于中间节点和空 node 探测,仍按外部入口经过准入闸门。 外部 method call 未携带可解码的 method-call payload 时,在进入准入队列前返回 org.freedesktop.DBus.Error.InvalidArgs;闸门关闭时仍返回过载错误。 目标存在但接口或方法未知时,metadata 检查仍在目标执行上下文中完成,以兼容动态对象;请求会快速返回 对应的 UnknownInterface 或 UnknownMethod,但仍受入口有界队列和全局 FIFO 调度约束。

目标已在本 executor 时内联执行:内部 dispatch()/dispatch_method() 若发现目标已在当前 executor,直接在当前线程执行,避免方法体内同步等待自己造成自锁。external 入口即使已经位于 准入 handler 内,也不会因为 TLS 的“已准入”标记而直通;它必须再次通过传输入口提供的闸门。

日志模块名作用域:内联、跨 executor 与目标缺失(信号广播)三条派发路径都会在派发执行期间安装 mc::log::scoped_module_name(值为目标服务短名),作用域内产生的全局日志宏(ilog/wlog/elog 等)输出归属该服务;作用域随派发结束恢复。因此无论调用经过哪条派发路径、落在哪个线程,同一服务的日志归属保持一致(信号广播/目标缺失路径本身无业务 handler,其日志主要来自事件分发与错误回包)。

服务短名语义:作用域取值来自 mc::engine::service_short_name()(<mc/engine/service.h>,取完整服务名最后一个 . 之后的部分,如 bmc.kepler.hwdiscovery → hwdiscovery),返回视图零分配、指向服务名内部存储,末段天然以 \0 结尾可直接作为 C 字符串使用。该实现为引擎通用语义(对任意命名空间适用),与 mc::app::service::to_short_name()(去除固定 bmc.kepler. 前缀)对生产服务名结果一致;mcapp 生命周期(configure/start/stop)与订阅回调的作用域统一使用本实现,保证派发与生命周期两条路径日志归属一致。

异步结果完成:handler 返回的 future 可以绑定其他 executor。dispatcher 使用 immediate_executor 完成内部结果封装,源 future 完成时直接传递值、异常或取消,不再投递到源线程池;因此该池停止后仍能完成响应并释放准入额度。业务自行注册的 continuation 保持其原有调度规则。

dispatch_method 返回的外层 future 使用 immediate_executor 作为完成 executor;目标 executor 负责执行方法。只要 handler 的源 future 最终完成,目标池停止后响应仍可完成,调用方通过 get_executor() 派生的后续 continuation 不会再次依赖目标池存活。

dispatch() 是内部派发入口,不申请入口额度;真实 transport inbound 必须使用 dispatch_external()。admission_gate 是 transport 所属的入口闸门,必须由入口适配器传入, engine 不从目标对象 executor 推断入口来源。未配置闸门时由适配器选择普通派发路径,关闭中的闸门则返回 mc.engine.rpc_overloaded。目标对象绑定 strand 时,准入任务仍回到 strand 执行,额度到实际 future 完成后才释放。入口任务投递到已停止的目标 executor 时,调度层的丢弃守卫会完成 rpc_overloaded 并释放准入额度,避免 future 永久等待。

外部请求从 gate 移交到目标 executor 后,仍可能继续排队。实际执行入口会统一检查 header/context 计算出的 deadline;标准 D-Bus 接口(包括 Properties.Set)和无对象 Introspect 同样适用。 已过期请求返回 rpc_expired,不启动方法或产生属性写入;普通方法识别 wire context 后还会继续收紧预算。

带 server_ingress 的重载供传输适配器传入消息实际到达服务端的单调时刻,适配器可在自己的 通道排队前盖戳;普通重载使用调用时刻。时间戳作为派发过程的内部 sidecar 传递,不写入 message_header 或 method_dispatch_request,因此不会改变既有公共对象布局。


dispatch_method — 协议适配层方法派发入口

struct method_dispatch_request {
    mc::string path;
    mc::string interface_name;
    mc::string method_name;
    mc::string sender;
    mc::dict context;
    mc::variants args;
};

struct method_dispatch_result {
    message_type type;
    const mc::reflect::method_type_info* method;
    mc::variant value;
    mc::string result_signature;
    mc::string value_signature;
    mc::dict value_signatures;
    mc::string error_name;
    mc::string error_message;
    bool is_method_return() const noexcept;
    bool is_error() const noexcept;
};

MC_API mc::future<method_dispatch_result>
dispatch_method(const service& svc, method_dispatch_request request);
MC_API mc::future<method_dispatch_result>
dispatch_method_external(const service& svc, method_dispatch_request request,
                         mc::runtime::entry_gate* admission_gate);
MC_API mc::future<method_dispatch_result>
dispatch_method_external(const service& svc, method_dispatch_request request,
                         mc::runtime::entry_gate* admission_gate,
                         std::chrono::steady_clock::time_point server_ingress);

dispatch_method() 面向内部调用;协议适配层的真实 inbound 应使用 dispatch_method_external()。 两者都不接收 D-Bus、SHM 或其他传输类型;目标查找、标准接口、context 归一化、权限校验、调用事件、 trace、结构化错误、对象 executor 和对象保活语义均共用同一实现。适配层只需把 method_dispatch_result 编码成自己的 wire reply。

in_admitted_entry() 只描述当前已准入执行体的内部派发上下文,不是 external 入口的授权凭据。 external 请求必须每次携带所属 ingress gate,入口闸门关闭或目标 executor 在投递前后停止时均以 rpc_overloaded 完成 future;目标 executor 丢弃已排队闭包的情况也走同一完成契约。 同一 ingress gate 已准入 handler 内再次发起 external 请求会立即返回 rpc_overloaded,避免父请求占用额度并等待同一闸门上的子请求。不同 gate 的请求仍按各自准入策略排队;内部 dispatch_method() 保持当前执行体语义。

#include <mc/engine/dispatcher.h>
#include <mc/engine/service.h>

mc::future<mc::engine::method_dispatch_result>
dispatch_from_transport(mc::engine::service& svc, mc::string path, mc::string interface_name,
                        mc::string method_name, mc::variants args, mc::runtime::entry_gate* ingress_gate)
{
    mc::engine::method_dispatch_request request;
    request.path = std::move(path);
    request.interface_name = std::move(interface_name);
    request.method_name = std::move(method_name);
    request.args = std::move(args);
    return mc::engine::dispatch_method_external(svc, std::move(request), ingress_gate);
}

传输适配器应传入自己所属的入口闸门;内部对象间调用使用 dispatch_method(),不应把目标 executor 当作外部入口判定依据。

API 参数 返回值 异常 线程安全 说明
dispatch_method svc:目标服务;request:传输无关方法请求 future<method_dispatch_result> std::bad_alloc;异步基础设施异常通过 future 传播 可并发调用;具体方法按目标对象 executor 串行语义执行 内部派发,不申请入口额度
dispatch_method_external svc、request、admission_gate:transport 所属入口闸门 future<method_dispatch_result> std::bad_alloc;关闭闸门返回 rpc_overloaded 可并发调用;具体方法按目标对象 executor 串行语义执行 外部 inbound 派发,准入判断由显式入口来源触发
dispatch_method_external(带 ingress) 同上,加服务端单调接收时刻 future<method_dispatch_result> 同上 可并发调用 传输适配器已在更早阶段盖戳时使用;时间戳不写入 request 布局
method_dispatch_result::is_method_return 无 成功结果返回 true noexcept 只读线程安全 判断结果是否为 method_return
method_dispatch_result::is_error 无 错误结果返回 true noexcept 只读线程安全 判断结果是否为结构化错误

method 指针指向 engine 管理的方法元数据,仅用于适配层立即完成当前结果的签名编码;调用方不应释放或跨对象注销长期缓存该指针。错误通过 error_name/error_message 返回,不需要按错误文本猜测类型。


message 与 message_header

RPC 准入预算配置

MC_API void apply_rpc_config(mc::string_view key, const mc::variant& value) noexcept;
MC_API void reset_rpc_config() noexcept;
key 含义 默认
ServerRpcDefaultBudgetMs 调用方未声明 TimeoutMs 时使用的预算(毫秒,取值 1..86400000) 60000
ServerRpcMaxBudgetMs 预算上限;客户端声明值只能被收紧,不能放大(毫秒,取值 1..86400000) 60000

默认预算与预算上限独立保存,修改任一项不会改写另一项;两个配置的最终值不受下发顺序影响。 新请求按 min(调用方声明或配置的默认预算, 当前上限) 计算预算:临时调低上限后再调高, 后续请求会重新使用保留的默认预算并受当前上限约束。已确定的排队 deadline 不随配置更新重算。

reset_rpc_config() 恢复上述两个进程级配置为默认值 60000 毫秒;应用释放自己启动的 runtime 时调用,避免多个 application 依次运行时相互继承预算配置。

越界取值(≤0、非数字字符串、bool/浮点、大于 24 小时)一律忽略并记一条 warning,保留原值: 超出一天的预算没有意义,而且逼近「入站时刻 + 预算」的溢出区——溢出到过去会让入口把所有 请求判成过期。未识别的 key 静默忽略(由调用方自己解释)。

engine::dispatch_external() 在入口取得服务端单调接收时刻;传输适配器也可以通过时间戳重载 提前盖戳,目标方法入口据此落定 deadline = server_ingress + budget。该时间戳只在 dispatcher 内部传递,不参与序列化,也不改变 message_header 的公共布局。 第三方调用(busctl 等不声明 TimeoutMs)回退 ServerRpcDefaultBudgetMs, 因此服务端即使拿不到客户端时间也有自己的接收时间基准。预算只用于排队与准入判定, 不作为执行期的硬截止时间。

受理点使用 header context 和 header.timeout 计算排队 deadline。wire 首参中的 TimeoutMs 只有在 目标 executor 结合方法 metadata 完成参数归一化后才能确认;若此时 deadline 已耗尽,方法入口返回 rpc_expired 且不执行用户 handler。这样入口线程不会读取动态对象的懒重建 metadata;wire-only 短预算 请求可能先占用一个额度再在目标 executor 被拒绝。旧框架的 Timeout 是 legacy 键,不消费;畸形、负值 和 0 按未声明处理。

struct message_header {
    MC_REFLECTABLE("mc.engine.message_header");
    message_type type{message_type::invalid};
    mc::string destination, sender, path, interface_name, member_name, error_name;
    uint64_t serial{0}, reply_serial{0};
    mc::milliseconds timeout{0};
    mc::dict context;
};

struct message {
    MC_REFLECTABLE("mc.engine.message");
    message_header header;
    mc::shared_ptr<const abstract_payload> body;
    bool empty() const noexcept;
    template<typename Payload> const Payload* try_as() const noexcept;
    template<typename Payload> const Payload& as() const;
    mc::string to_bytes() const;
    static message from_bytes(mc::string_view bytes);
};
方法 描述 异常
empty() body 为 nullptr 返回 true noexcept,不抛出
try_as<Payload>() 类型安全转换:payload_id 匹配则返回 Payload 指针,否则 nullptr noexcept,不抛出
as<Payload>() 断言转换,类型不匹配时抛出异常 mc::invalid_arg_exception
to_bytes() 序列化为 wire bytes std::bad_alloc;可能抛 mc::bad_cast_exception
from_bytes(bytes) 从 wire bytes 反序列化 mc::invalid_arg_exception(格式非法)

Payload 类型与 make_payload

enum class message_type : int32_t {
    invalid = 0, method_call = 1, method_return = 2, error = 3, signal = 4,
};

struct method_call_payload : public payload<method_call_payload, 1, message_type::method_call> {
    mc::string signature; mc::variants args;
};
struct method_return_payload : public payload<method_return_payload, 2, message_type::method_return> {
    mc::variant value; mc::string signature;
};
struct error_payload : public payload<error_payload, 3, message_type::error> {
    mc::string name; mc::string message;
};
struct signal_payload : public payload<signal_payload, 4, message_type::signal> {
    mc::string signature; mc::variants args;
};

template<typename Payload, typename... Args>
mc::shared_ptr<const abstract_payload> make_payload(Args&&... args);
类型 描述 构造异常
method_call_payload 方法调用:signature + variant 参数列表 std::bad_alloc
method_return_payload 方法返回:单个 variant 返回值 std::bad_alloc
error_payload 错误响应:错误名称 + 描述消息 std::bad_alloc
signal_payload 信号广播:signature + variant 参数列表 std::bad_alloc
make_payload<Payload>(args...) 工厂函数:完美转发构造 typed payload std::bad_alloc;Payload 构造异常向外传播

消息编解码

函数式 API

MC_API mc::dict         encode_payload(const abstract_payload& payload);
MC_API mc::shared_ptr<const abstract_payload> decode_payload(const mc::dict& dict);
MC_API mc::dict         encode_message(const message& msg);
MC_API message          decode_message(const mc::dict& dict);
MC_API mc::string       encode_message_bytes(const message& msg);
MC_API message          decode_message_bytes(mc::string_view bytes);
函数 描述 异常
encode_payload(p) payload → dict std::bad_alloc
decode_payload(d) dict → payload(按 payload_id 分派类型) mc::invalid_arg_exception
encode_message(m) header + body → dict std::bad_alloc
decode_message(d) dict → header + body mc::invalid_arg_exception(字段缺失或类型错误)
encode_message_bytes(m) message → dict → bytes 全链编码 std::bad_alloc;mc::bad_cast_exception
decode_message_bytes(b) bytes → dict → message mc::invalid_arg_exception(格式非法)

engine_codec

class engine_codec {
public:
    engine_codec() = default;
    explicit engine_codec(message_decode_options opt) noexcept;
    mc::string encode(const message& msg) const;
    message    decode(mc::string_view bytes) const;
    void       set_decode_options(message_decode_options opt) noexcept;
};
方法 描述 异常
engine_codec() 默认构造 noexcept
engine_codec(opt) 带 options 构造 noexcept
encode(msg) message → wire bytes std::bad_alloc;mc::bad_cast_exception
decode(bytes) wire bytes → message(应用 options) mc::invalid_arg_exception
set_decode_options(opt) 替换 options,后续 decode 立即生效 noexcept

service — 服务端核心

class service : public mc::object {
public:
    explicit service(mc::string_view name, mc::object* parent = nullptr);
    mc::string_view name() const;

    bool init(dict args = {});  bool start();  bool stop();
    void cleanup();  bool is_healthy() const;

    void register_object(abstract_object& obj);
    void unregister_object(mc::string_view path);
    void unregister_private_object(abstract_object& obj);

    void reindex_object(abstract_object& obj, const std::function<void()>& mutate);
    void check_object_name_available(const abstract_object& obj, mc::string_view new_name) const;

    void                emit(const message& msg) const;
    void                send(message request) const;
    message             send_with_reply(message request, mc::milliseconds timeout = 5000) const;
    mc::future<message> async_send_with_reply(message request, mc::milliseconds timeout = 5000) const;
    mc::variant         call(mc::string_view path, mc::string_view service_name,
                             mc::string_view interface, mc::string_view method,
                             const mc::variants& args = {}, mc::string_view sig = {},
                             mc::milliseconds timeout = 5000) const;

    match_id add_match(match_rule rule, filter_spec spec, match_callback callback) const;
    void     remove_match(match_id id) const;
};
方法 描述 异常
service(name) 构造,挂载 parent 对象树 std::bad_alloc
init(args) 初始化,调 on_init;成功返回 true on_init 异常向外传播;失败返回 false
start() 启动,调 on_start on_start 异常向外传播
stop() 停止,调 on_stop on_stop 异常向外传播
cleanup() 清理资源,调 on_cleanup hook 异常被 catch 记日志,不向外传播
is_healthy() 健康状态查询 不抛出
register_object(obj) 入服务表(空 path→私有表)并镜像全局表;入表后同步 is_registered=true,再异步 on_register 路径非法 / 索引冲突等
unregister_object(path) 先摘表再清 is_registered,再异步 on_unregister 不抛出(未找到为空操作)
unregister_private_object(obj) 私有对象:先摘表再清 registered 不抛出
reindex_object(obj, mutate) 服务表+全局表嵌套 table::reindex;未绑定本服务时仅 mutate 唯一冲突等;须先 check_object_name_available
check_object_name_available(obj, name) 跨服务/私有/全局表预检 object_name 冲突抛 invalid_arg_exception
emit(msg) 发射信号;先调 backend::emit,返回 true 则走 engine emit backend 异常向外传播
send(request) fire-and-forget 异步发送 路由异常记日志,不向外传播
send_with_reply(req, timeout) 同步阻塞等待响应 超时抛 mc::timeout_exception
async_send_with_reply(req, timeout) 异步发送,返回 future std::bad_alloc;错误通过 future 传播
call(path, service, iface, method, args, sig, timeout) 一站式 RPC:构造→发送→解析 超时 mc::timeout_exception;远端 error 抛 mc::method_call_exception
add_match(rule, spec, cb) 注册匹配规则 std::bad_alloc
remove_match(id) 按 match_id 移除 不抛出(id 不存在静默忽略)

service_proto — 协议适配层

class service_proto : public mc::proto::protocol {
public:
    void set_inbound_handler(std::function<mc::future<message>(message)> handler);
    void clear_inbound_handler();
    bool has_inbound_handler() const noexcept;
    void set_decode_options(message_decode_options opt);
    const engine_codec& codec() const noexcept;

    virtual mc::future<message> async_send_with_reply(message req, mc::milliseconds timeout = 5000);
    virtual message             send_with_reply(message req, mc::milliseconds timeout = 5000);
    void                send(message req);
};
方法 描述 异常
set_inbound_handler(h) 设置入站处理器 std::bad_alloc
clear_inbound_handler() 清除入站处理器 不抛出
codec() 获取内部 engine_codec 引用 noexcept
set_decode_options(opt) 设置内部 codec decode 选项 noexcept
send_with_reply(req, timeout) 同步传输扩展点;默认等待 async_send_with_reply,协议实现可直接完成同步请求 超时 mc::timeout_exception
async_send_with_reply(req, timeout) 异步发送 std::bad_alloc;错误通过 future 传播
send(req) 发送无应答消息 底层 proto 异常向外传播

service_backend 后端事件钩子:emit(msg)(返回 false 拦截分发)、on_object_added(obj)、on_object_removed(obj)、on_match_added(id, rule)、on_match_removed(id)。所有虚方法覆盖异常均向外传播。


endpoint_service — SHM 进程端点

SHM 模式下每进程有且仅有一个 endpoint_service。on_start 中完成 endpoint 注册、mq_channel 打开与绑定。

class endpoint_service : public service {
public:
    endpoint_service(mc::string_view endpoint_name, mc::object* parent = nullptr);
    std::uint16_t endpoint_id() const noexcept;
    std::uint32_t instance_id() const noexcept;
    bool send_to_endpoint(uint16_t eid, uint32_t iid, const message& msg);
    mc::future<message> send_with_reply_to_endpoint(uint16_t eid, uint32_t iid,
                                                     message msg, mc::milliseconds timeout);
};
方法 描述 异常
endpoint_id() SHM endpoint 编号 noexcept
instance_id() SHM 实例编号 noexcept
send_to_endpoint(eid, iid, msg) 向指定 endpoint 投递消息,成功返回 true std::bad_alloc;写失败返回 false
send_with_reply_to_endpoint(eid, iid, msg, timeout) 发送并等待应答 超时 mc::timeout_exception

engine 静态方法

class engine {
public:
    static void route_inbound(const message& msg);
    static void publish(const message& msg);
    static void register_service(service* svc);
    static void unregister_service(service* svc);
    static service* find_service(mc::string_view name);
    static bool is_service_registered(const service* svc);
    static endpoint_service* get_endpoint_service() noexcept;
};
方法 描述 异常
route_inbound(msg) 将入站消息路由到目标 service 或匹配订阅 路由异常记日志
publish(msg) 向所有 match 订阅者广播消息 match_callback 异常向外传播
register_service(svc) 向 engine 注册 service std::bad_alloc
unregister_service(svc) 从 engine 注销 service 不抛出
find_service(name) 按名称查找 service,未找到返回 nullptr 不抛出
is_service_registered(svc) 按指针校验 service 是否仍在本进程注册表中(延迟清理回调用) 不抛出
get_endpoint_service() 获取当前进程的 endpoint_service,无则返回 nullptr noexcept

最佳实践

✅ 使用 make_payload + try_as/as 模式安全访问 payload

try_as<T>() 返回 nullptr on mismatch,适合分支派发;as<T>() 在类型已确认后使用,避免重复检查:

#include <mc/engine.h>
#include <iostream>
using namespace mc::engine;

void handle_response(const message& reply)
{
    if (auto* err = reply.try_as<error_payload>()) {
        std::cerr << "RPC error: " << err->name << " — " << err->message << std::endl;
        return;
    }
    auto& ret = reply.as<method_return_payload>();
    std::cout << "result: " << ret.value.as<int>() << std::endl;
}

✅ 消息 header.type 必须与 body payload 类型严格一致

header.type 是路由和序列化的第一级判别依据。类型不匹配时 try_as 全部返回 nullptr,接收方无法解析:

#include <mc/engine.h>
using namespace mc::engine;

void consistent_message()
{
    message sig;
    sig.header.type = message_type::signal;
    sig.body = make_payload<signal_payload>("d", mc::variants{36.5});  // ✅

    message req;
    req.header.type = message_type::method_call;
    req.header.interface_name = "org.example.Calc";
    req.header.member_name = "Add";
    req.body = make_payload<method_call_payload>("ii", mc::variants{7, 8});  // ✅
}

❌ 未在 service 析构前移除 match 订阅导致悬空回调

add_match 将回调注册到全局 match table,service 析构后若未调用 remove_match,match table 仍持有指向已析构对象的回调引用:

#include <mc/engine.h>
using namespace mc::engine;

// ❌ 错误:service 析构后 match table 持有悬空 this 回调
struct BadService : public service {
    BadService(mc::string_view n) : service(n) {}
    void on_init(dict) override {
        m_id = add_match(match_rule{}, {}, [this](const message&) { do_work(); });
    }
    void do_work() {}
    match_id m_id{0};
};

// ✅ 正确:在 stop 钩子中主动取消订阅
struct GoodService : public service {
    GoodService(mc::string_view n) : service(n) {}
    void on_init(dict) override {
        m_id = add_match(match_rule{}, {}, [this](const message&) { do_work(); });
    }
    bool on_stop() override {
        if (m_id != 0) { remove_match(m_id); m_id = 0; }
        return true;
    }
    void do_work() {}
    match_id m_id{0};
};

❌ 不要在 dispatch 返回的 future 就绪前销毁 service

dispatch() 持有 service 的内部路由信息,future 未就绪时 service 析构会导致 engine 访问悬空引用:

#include <mc/engine.h>
#include <memory>
using namespace mc::engine;

void bad_early_destroy()
{
    auto svc = std::make_unique<service>("temp.service");
    svc->init(); svc->start();

    message req;
    req.header.type = message_type::method_call;
    req.header.destination = "target.service";
    req.body = make_payload<method_call_payload>();

    mc::future<message> fut = dispatch(*svc, req);
    svc.reset();  // ❌ future 可能尚未就绪,engine 持有指向已析构 svc 的引用
}

void correct_lifetime()
{
    service svc("stable.service");
    svc.init(); svc.start();

    message req;
    req.header.type = message_type::method_call;
    req.header.destination = "target.service";
    req.body = make_payload<method_call_payload>();

    mc::future<message> fut = dispatch(svc, req);
    message reply = fut.get();  // ✅ 阻塞等待,确保 svc 在 future 就绪前存活

    if (auto* ret = reply.try_as<method_return_payload>())
        std::cout << "ok" << std::endl;
    svc.stop(); svc.cleanup();
}