Match/Routing 系统 API 文档

Header: #include <mc/engine.h>   Namespace: mc::engine::match

概述

mc::engine::match 提供消息订阅与路由分发的核心基础设施。系统基于 match_rule(六字段匹配规则)进行消息筛选,支持 path_pattern(路径模式匹配)和 filter_backend(可扩展过滤后端),通过 table(订阅表)统一管理订阅生命周期,并与 mc::engine::engine 深度集成。

典型流程:service 通过 add_match() 注册匹配规则(规则进 table,回调进 callback_registry)→ 引擎在 route_inbound()/publish() 时调用 table::find_targets() 查找目标订阅 → 派发执行点经 callback_registry::lookup(match_id) 解析并触发 match_callback。回调在执行点解析(而非派发入队时绑定),remove_match 后已排队但未执行的回调会被跳过(弱保证语义)。

主要特性

  • 六字段匹配规则:match_rule 包含 type、sender、destination、path、interface_name、member_name;空字符串自动视为通配,匹配任意值
  • 路径模式匹配:path_pattern 支持全通配(空串 "")、精确匹配("/foo/bar")、前缀双星号("/foo/")、单段通配("*")和任意子路径("**")
  • 可扩展过滤系统:filter_backend 抽象接口通过 filter_backend_registry 单例注册,支持编译→缓存→评估管线;内置 condition_filter_backend(类型标识 condition_filter = 1)
  • 统一回调解析:callback_registry 进程级单例维护 match_id → match_callback 映射,table 只管理匹配规则;三种 table 形态(local/shared/旧 SHM 后端)共用同一回调存储
  • 日志归属:service::add_match() 包装的订阅回调在订阅服务 executor 上执行时自动安装订阅服务短名的日志模块作用域,回调内全局日志宏归属订阅方
  • 端点存活清理:table::sweep_dead_endpoint() 定期回收已断开端点的所有订阅,防止资源泄漏
  • 引擎级集成:engine 提供静态方法 get_match_table() / set_match_table() / add_filter_backend() 管理全局路由表和过滤后端
  • service 便捷接口:add_match() / remove_match() 将订阅生命周期绑定到 service;on_match_added / on_match_removed 信号暴露订阅变更事件

基本用法

示例 1:table 订阅与回调触发

#include <mc/engine.h>
#include <iostream>
#include <cassert>

int main()
{
    using namespace mc::engine;

    auto table = engine::get_match_table();

    match_rule rule;
    rule.type = "signal";
    rule.path = "/org/example/sensors/";

    // table 只登记匹配规则;回调绑定到 callback_registry
    match_id id = table->subscribe("my_service", rule, filter_spec{});
    assert(id != 0);
    assert(table->size() == 1);

    match::callback_registry::instance().bind(id, [](const message& msg) {
        std::cout << "收到信号: path=" << msg.header.path
                  << " member=" << msg.header.member_name << std::endl;
    });

    message msg;
    msg.header.type = message_type::signal;
    msg.header.path = "/org/example/sensors/temperature";
    msg.header.member_name = "PropertiesChanged";

    auto targets = table->find_targets(msg);
    assert(!targets.empty());

    // 执行点解析:经 callback_registry 按 match_id 查找回调
    match_callback cb = match::callback_registry::instance().lookup(targets[0].id);
    assert(cb);
    cb(msg);  // 输出: 收到信号: path=/org/example/sensors/temperature member=PropertiesChanged

    match::callback_registry::instance().unbind(id);
    table->unsubscribe(id);
    assert(table->size() == 0);
    return 0;
}

示例 2:match_rule 与 path_pattern 匹配模式

#include <mc/engine.h>
#include <iostream>
#include <cassert>

int main()
{
    using namespace mc::engine::match;

    // full_wildcard: 空字符串匹配任意路径
    path_pattern p_empty("");
    assert(p_empty.get_kind() == path_pattern::kind::full_wildcard);
    assert(p_empty.matches("/any/path"));
    assert(p_empty.matches("/deeply/nested/path/here"));

    // exact: 不含通配符,仅精确匹配
    path_pattern p_exact("/org/example/sensor");
    assert(p_exact.get_kind() == path_pattern::kind::exact);
    assert(p_exact.matches("/org/example/sensor"));
    assert(!p_exact.matches("/org/example/sensor/temp"));

    // prefix_doublestar: 末尾 '/' 表示前缀匹配所有子路径
    path_pattern p_prefix("/org/example/");
    assert(p_prefix.get_kind() == path_pattern::kind::prefix_doublestar);
    assert(p_prefix.matches("/org/example/sensor"));
    assert(p_prefix.matches("/org/example/sensor/temp/value"));
    assert(!p_prefix.matches("/other/path"));

    // 单段通配 '*'
    path_pattern p_seg("/org/*/status");
    assert(p_seg.is_well_formed("/org/*/status"));
    assert(p_seg.matches("/org/example/status"));
    assert(!p_seg.matches("/org/example/sub/status"));

    // 复合:match_rule + matches 自由函数
    match_rule rule;
    rule.type = "signal";
    rule.path = "/com/example/";

    mc::engine::message_header hdr;
    hdr.type = mc::engine::message_type::signal;
    hdr.path = "/com/example/device/status";
    assert(matches(rule, hdr));

    hdr.type = mc::engine::message_type::method_call;
    assert(!matches(rule, hdr));

    return 0;
}

示例 3:自定义 filter_backend 注册与评估

#include <mc/engine.h>
#include <iostream>
#include <cassert>

constexpr uint32_t kPathLenFilter = 100;

class path_len_compiled : public mc::engine::filter_compiled {
public:
    explicit path_len_compiled(int min_len) : m_min_len(min_len) {}
    int min_len() const { return m_min_len; }
private:
    int m_min_len;
};

class path_len_backend : public mc::engine::filter_backend {
public:
    uint32_t backend_type() const noexcept override { return kPathLenFilter; }
    mc::string_view name() const noexcept override { return "path_len"; }

    mc::engine::filter_compiled_ptr compile(mc::string_view text) const override {
        int min_len = std::stoi(std::string(text));
        return mc::make_shared<path_len_compiled>(min_len);
    }

    bool evaluate(const mc::engine::filter_compiled& compiled,
                  const mc::engine::message& msg) const override {
        auto& c = dynamic_cast<const path_len_compiled&>(compiled);
        return static_cast<int>(msg.header.path.size()) > c.min_len();
    }
};

int main()
{
    using namespace mc::engine;

    auto backend = mc::make_shared<path_len_backend>();
    filter_backend_registry::instance().register_backend(backend);

    auto found = filter_backend_registry::instance().find(kPathLenFilter);
    assert(found != nullptr);
    assert(found->name() == "path_len");

    auto compiled = filter_backend_registry::instance()
                        .get_or_compile(kPathLenFilter, "10");
    assert(compiled != nullptr);

    message msg;
    msg.header.path = "/org/example/sensors/temperature";  // 长度 > 10
    assert(filter_backend_registry::instance()
               .evaluate(kPathLenFilter, "10", msg));

    msg.header.path = "/ab";  // 长度 ≤ 10
    assert(!filter_backend_registry::instance()
                .evaluate(kPathLenFilter, "10", msg));

    filter_backend_registry::instance().clear_cache();
    assert(filter_backend_registry::instance().cache_size() == 0);

    return 0;
}

示例 4:find_targets 多服务分发

#include <mc/engine.h>
#include <iostream>
#include <cassert>

int main()
{
    using namespace mc::engine;

    auto table = engine::get_match_table();
    size_t base = table->size();

    match_rule rule;
    rule.type = "signal";
    rule.path = "/com/example/events/";

    match_id id_a = table->subscribe("svc_a", rule, filter_spec{});
    match_id id_b = table->subscribe("svc_b", rule, filter_spec{});
    assert(table->size() == base + 2);

    match_rule rule2;
    rule2.type = "signal";
    rule2.path = "/other/path/";
    match_id id_c = table->subscribe("svc_c", rule2, filter_spec{});

    message msg;
    msg.header.type = message_type::signal;
    msg.header.path = "/com/example/events/temperature";

    auto targets = table->find_targets(msg);
    assert(targets.size() == 2);  // 仅 svc_a 和 svc_b 命中

    for (const auto& t : targets) {
        std::cout << "target: service=" << t.service_name
                  << " endpoint=" << t.endpoint_id
                  << " match_id=" << t.id << std::endl;
    }

    table->unsubscribe(id_a);
    table->unsubscribe(id_b);
    table->unsubscribe(id_c);
    assert(table->size() == base);
    return 0;
}

API 详细说明

match_rule 结构体

六字段匹配规则。空字符串字段自动视为通配,匹配任意值。path 字段额外支持 path_pattern 语法。

字段 类型 描述
type mc::string 消息类型字符串,如 "signal"、"method_call",空串匹配任意类型
sender mc::string 发送者过滤,空串匹配任意发送者
destination mc::string 目标过滤,空串匹配任意目标
path mc::string 路径过滤,支持 path_pattern 语法(前缀 /、*、**)
interface_name mc::string D-Bus 接口名过滤
member_name mc::string 方法/信号/属性名过滤
自由函数 描述 异常
bool matches(const match_rule& rule, const message_header& header) noexcept 检查消息头是否匹配规则,path 字段使用 path_pattern::matches 不抛出

path_pattern 类

match_rule::path 的模式表达式。支持空串全通配、精确路径、前缀双星号、单段通配 *、任意子路径 **。非法语法容错降级为 full_wildcard。

方法 描述 异常
static bool is_well_formed(mc::string_view raw) noexcept 检查路径字符串格式是否合法 不抛出
path_pattern() 默认构造,kind 为 full_wildcard 不抛出
explicit path_pattern(mc::string_view raw) 解析路径模式,非法语法降级为 full_wildcard std::bad_alloc(segments 分配失败)
kind get_kind() const noexcept 返回模式类型 不抛出
bool matches(mc::string_view path) const noexcept 检查给定路径是否匹配当前模式 不抛出
const std::vector<mc::string>& segments() const noexcept 返回解析后的路径段列表 不抛出
kind 枚举 典型输入 匹配行为
full_wildcard "" 匹配任意路径
exact "/foo/bar" 仅精确匹配 /foo/bar
prefix_doublestar "/foo/" 匹配 /foo 及其所有子路径

filter_spec 结构体

字段 类型 描述
backend_type uint32_t 过滤后端类型标识,默认 no_filter(=0) 表示不过滤
text mc::string 过滤表达式文本,由对应 backend 解释
方法 描述 异常
bool empty() const noexcept backend_type == no_filter 时返回 true 不抛出

filter_backend 与 filter_compiled

filter_backend 是可扩展的过滤后端基类。compile() 返回 filter_compiled_ptr(mc::shared_ptr<filter_compiled>),编译后对象可跨消息复用。

方法 描述 异常
virtual uint32_t backend_type() const noexcept = 0 返回全局唯一的后端类型标识 不抛出
virtual mc::string_view name() const noexcept = 0 返回后端可读名称 不抛出
virtual filter_compiled_ptr compile(mc::string_view text) const = 0 编译 filter_spec::text 为编译后对象 实现定义的异常向上传播
virtual bool evaluate(const filter_compiled& compiled, const message& msg) const = 0 使用编译后对象评估消息是否通过过滤 实现定义的异常向上传播

filter_backend_registry 类(单例)

全局过滤后端注册中心和编译缓存。

方法 描述 异常
static filter_backend_registry& instance() 获取单例引用 不抛出
void register_backend(filter_backend_ptr backend) 注册过滤后端,同类型重复注册会覆盖 std::bad_alloc
filter_backend_ptr find(uint32_t backend_type) const 按类型查找后端,未找到返回 nullptr 不抛出
filter_compiled_ptr get_or_compile(uint32_t backend_type, mc::string_view text) 查找缓存或编译过滤表达式,结果自动缓存 后端 compile 异常向上传播
bool evaluate(uint32_t backend_type, mc::string_view text, const message& msg) 查找后端并评估消息是否通过过滤 后端 compile/evaluate 异常向上传播
void set_cache_capacity(std::size_t capacity) 设置编译缓存最大容量 不抛出
std::size_t cache_capacity() const noexcept 返回缓存最大容量 不抛出
std::size_t cache_size() const noexcept 返回当前已缓存条目数 不抛出
void clear_cache() 清空编译缓存 不抛出

table 抽象类

订阅与匹配的统一入口。全局单例由 engine 持有,通过 engine::get_match_table() 获取。

方法 描述 异常
virtual match_id subscribe(mc::string_view service_name, match_rule rule, filter_spec spec) = 0 登记匹配规则,返回唯一 match_id(非零);回调需另行 callback_registry::bind std::bad_alloc
virtual void unsubscribe(match_id id) noexcept = 0 按 id 取消订阅,id 不存在时静默忽略 不抛出
virtual void clear() noexcept = 0 清空所有订阅 不抛出
virtual std::size_t size() const noexcept = 0 返回当前订阅总数 不抛出
virtual std::vector<target> find_targets(const message& msg) const = 0 查找消息匹配的所有目标订阅 std::bad_alloc
virtual std::size_t sweep_dead_endpoint(uint16_t endpoint_id, uint32_t min_alive_instance_id) noexcept 清理已断开端点的所有订阅,返回清理数量 不抛出

callback_registry 类(单例)

Header: #include <mc/engine/match/callback_registry.h>

进程级 match_id → match_callback 映射。回调与匹配规则分离存储:table 只管规则与 match_id 分配,回调在派发执行点经 registry 解析。unbind 后已排队但未执行的派发会因解析失败而跳过(remove_match 弱保证语义的实现基础)。

方法 描述 异常
static callback_registry& instance() 获取进程级单例引用 不抛出
void bind(match_id id, match_callback callback) 绑定回调,同 id 重复绑定覆盖;id 为 0 或回调为空时静默忽略 std::bad_alloc
void unbind(match_id id) noexcept 解绑回调,id 不存在时静默忽略 不抛出
match_callback lookup(match_id id) const 按 id 查找回调,未找到返回空 std::function 不抛出
std::size_t size() const noexcept 返回当前绑定数量 不抛出
void clear() noexcept 清空所有绑定(engine reset 时调用) 不抛出

target 结构体

find_targets 返回的匹配目标。关联类型:match_id = std::uint64_t(0 视为未注册),match_callback = std::function<void(const message&)>。

字段 类型 描述
endpoint_id uint16_t 目标端点 ID
instance_id uint32_t 目标实例 ID
service_name mc::string 订阅服务名
id match_id 订阅句柄

engine 静态方法(match 相关)

方法 描述 异常
static match::table_ptr get_match_table() 获取全局 match table 不抛出
static void set_match_table(match::table_ptr table) 替换全局 match table,常用于测试注入 mock 不抛出
static void add_filter_backend(match::filter_backend_ptr backend) 将过滤后端注册到全局 filter_backend_registry std::bad_alloc
static match::filter_backend_ptr find_filter_backend(uint32_t backend_type) 按类型查找已注册的过滤后端 不抛出

service 订阅方法

mc::engine::service 提供的便捷接口,内部委托给全局 match table。

方法 描述 异常
match_id add_match(match_rule rule, filter_spec spec, match_callback callback) const 以当前 service 身份订阅 std::bad_alloc
void remove_match(match_id id) const 取消指定订阅,id 不存在时静默忽略 不抛出
信号 签名 描述
on_match_added mc::signal<void(match_id, const match_rule&)> 订阅添加时发射
on_match_removed mc::signal<void(match_id)> 订阅移除时发射

最佳实践

✅ 使用 path 前缀匹配替代全通配缩小范围

当只关心特定子树的消息时,使用前缀模式(末尾 /)而非空串全通配,避免不相关消息触发回调:

// 好:仅匹配 /org/example/sensors/ 子树下的信号
mc::engine::match_rule rule;
rule.type = "signal";
rule.path = "/org/example/sensors/";
service->add_match(rule, {}, callback);

// 差:空 path 匹配全路径树的信号,回调频次不可控
rule.path = "";

✅ match_rule 粗筛 + filter_spec 细滤组合使用

match_rule 的字段比对是轻量级 O(1) 操作,适合第一级粗筛;filter_spec 在 match_rule 通过后执行,适合条件表达式等细粒度过滤:

mc::engine::match_rule rule;
rule.type = "signal";
rule.path = "/org/example/devices/";
rule.interface_name = "org.example.Device";  // 粗筛:限定接口

mc::engine::filter_spec spec;
spec.backend_type = mc::engine::condition_filter;
spec.text = "severity >= 3";  // 细滤:仅处理高严重度信号

auto id = service->add_match(rule, spec, [](const mc::engine::message& msg) {
    // 仅 severity >= 3 的 Device 接口信号到达此处
});

❌ 未在 service 析构前取消订阅导致悬空回调

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

// 差:service 析构后 match table 持有悬空回调
struct BadService : public mc::engine::service {
    void init() {
        mc::engine::match_rule rule; rule.type = "signal";
        m_id = add_match(rule, {}, [this](const mc::engine::message&) {
            process_event();  // this 可能已析构
        });
    }
    void process_event() {}
    mc::engine::match_id m_id{0};
};

// 好:在停止逻辑中主动取消订阅
struct GoodService : public mc::engine::service {
    void init() {
        mc::engine::match_rule rule; rule.type = "signal";
        m_id = add_match(rule, {}, [this](const mc::engine::message&) {
            process_event();
        });
    }
    void stop() { if (m_id != 0) { remove_match(m_id); m_id = 0; } }
    void process_event() {}
    mc::engine::match_id m_id{0};
};

❌ 在 filter_backend::evaluate 中执行 I/O 或持锁操作

evaluate 在消息分发热路径上被调用,可能对每个候选订阅执行一次。应确保 evaluate 仅为纯计算,无 I/O、无锁竞争:

// 差:evaluate 中执行阻塞 I/O
bool evaluate(const mc::engine::filter_compiled& compiled,
              const mc::engine::message& msg) const override {
    auto remote_state = fetch_remote_config();  // 每次 evaluate 都执行
    return remote_state.matches(msg.header.path);
}

// 好:将外部状态固化到 compiled 对象,compile 阶段完成 I/O
mc::engine::filter_compiled_ptr compile(mc::string_view text) const override {
    auto remote_state = fetch_remote_config();  // 仅编译时执行一次
    return mc::make_shared<my_compiled>(std::move(remote_state));
}

bool evaluate(const mc::engine::filter_compiled& compiled,
              const mc::engine::message& msg) const override {
    auto& c = dynamic_cast<const my_compiled&>(compiled);
    return c.remote_state.matches(msg.header.path);  // 纯内存比较
}