Merged
【84cb464e7da8942fc069941149941e40】fix(csr1): retain task owners only during active runs #1329
liuxingsenAdd on 25 days ago
【84cb464e7da8942fc069941149941e40】fix(csr1): retain task owners only during active runs #1329
Merged
Total: 13 files changed+706-58
| @@ -1,5 +1,8 @@ | |||
| 1 | # Changelog | 1 | # Changelog |
| 2 | All notable changes to this project will be documented in this file. | 2 | All notable changes to this project will be documented in this file. |
| 3 | +## [1.2.531] - 2026-10-14 | ||
| 4 | +- 修复 CSR1 调度任务空闲强引用环、异步注销与重注册边界,并通过弱引用保护 Aggregate 宿主最终释放。 | ||
| 5 | + | ||
| 3 | ## [1.2.530] - 2026-10-10 | 6 | ## [1.2.530] - 2026-10-10 |
| 4 | fix: 修复 os下电时误清理mac地址等静态信息 | 7 | fix: 修复 os下电时误清理mac地址等静态信息 |
| 5 | 8 | ||
| @@ -64,16 +64,13 @@ void NormalDispatcher::add_scanner(const AggregateScannerInfo& info) | |||
| 64 | mc::shared_ptr<mc::object_base> NormalDispatcher::owner_guard() | 64 | mc::shared_ptr<mc::object_base> NormalDispatcher::owner_guard() |
| 65 | { | 65 | { |
| 66 | // Scheduler 持锁调用的纯查询契约(同 period_ms):只借引用、无重入。 | 66 | // Scheduler 持锁调用的纯查询契约(同 period_ms):只借引用、无重入。 |
| 67 | - // 经 object_base 视图调用,返回类型精确匹配;未托管宿主(不应出现在生产) | 67 | + // 调用方仍须保证 owner/dispatcher 存储有效;不允许与宿主析构并发注册。 |
| 68 | - // 与正在析构的宿主(add_ref/shared_from_this 抛)返回空。 | 68 | + // 在调用时借引用,保留 set_owner_object 早于托管的顺序。未托管宿主不能 |
| 69 | + // 被 shared_from_this 收养;已进入最终释放的托管宿主由 lock() 返回空。 | ||
| 69 | if (m_owner_object == nullptr || !m_owner_object->is_managed()) { | 70 | if (m_owner_object == nullptr || !m_owner_object->is_managed()) { |
| 70 | return {}; | 71 | return {}; |
| 71 | } | 72 | } |
| 72 | - try { | 73 | + return m_owner_object->weak_from_this().lock(); |
| 73 | - return m_owner_object->shared_from_this(); | ||
| 74 | - } catch (const std::exception&) { | ||
| 75 | - return {}; | ||
| 76 | - } | ||
| 77 | } | 74 | } |
| 78 | 75 | ||
| 79 | void NormalDispatcher::run() | 76 | void NormalDispatcher::run() |
| @@ -427,6 +424,12 @@ Interface_Aggregate::~Interface_Aggregate() | |||
| 427 | void Interface_Aggregate::set_owner_object(mc::object_base* owner) | 424 | void Interface_Aggregate::set_owner_object(mc::object_base* owner) |
| 428 | { | 425 | { |
| 429 | m_owner_object = owner; | 426 | m_owner_object = owner; |
| 427 | + m_owner_weak.reset(); | ||
| 428 | + // mc::weak_ptr keeps its counter in the object. Do not retain a weak | ||
| 429 | + // reference to an unmanaged owner whose storage is controlled by its caller. | ||
| 430 | + if (owner != nullptr && owner->is_managed()) { | ||
| 431 | + m_owner_weak = owner->weak_from_this(); | ||
| 432 | + } | ||
| 430 | if (m_dispatcher) { | 433 | if (m_dispatcher) { |
| 431 | m_dispatcher->set_owner_object(owner); | 434 | m_dispatcher->set_owner_object(owner); |
| 432 | } | 435 | } |
| @@ -486,14 +489,7 @@ void Interface_Aggregate::init(uint32_t base_offset, uint32_t length, uint32_t p | |||
| 486 | // 宿主对象强引用保活:跨对象使用 master 指针期间借住宿主,防止宿主并发析构导致 UAF | 489 | // 宿主对象强引用保活:跨对象使用 master 指针期间借住宿主,防止宿主并发析构导致 UAF |
| 487 | mc::shared_ptr<mc::object_base> Interface_Aggregate::host_guard() const | 490 | mc::shared_ptr<mc::object_base> Interface_Aggregate::host_guard() const |
| 488 | { | 491 | { |
| 489 | - if (m_owner_object == nullptr || !m_owner_object->is_managed()) { | 492 | + return m_owner_weak.lock(); |
L | |||
| 490 | - return {}; | ||
| 491 | - } | ||
| 492 | - try { | ||
| 493 | - return m_owner_object->shared_from_this(); | ||
| 494 | - } catch (const std::exception&) { | ||
| 495 | - return {}; | ||
| 496 | - } | ||
| 497 | } | 493 | } |
| 498 | 494 | ||
| 499 | // 存活的共享 master 引用:注册表核对 + 持注册表锁取得宿主保活 guard(防悬挂转发) | 495 | // 存活的共享 master 引用:注册表核对 + 持注册表锁取得宿主保活 guard(防悬挂转发) |
| @@ -511,7 +507,7 @@ Interface_Aggregate::MasterRef Interface_Aggregate::active_master_ref() const | |||
| 511 | return ref; // 已注销/被替换 | 507 | return ref; // 已注销/被替换 |
| 512 | } | 508 | } |
| 513 | // 持注册表锁取得 guard:master 析构的 erase 需同一把锁,核对与取 guard 之间 | 509 | // 持注册表锁取得 guard:master 析构的 erase 需同一把锁,核对与取 guard 之间 |
| 514 | - // 不可能插入注销;宿主析构中 shared_from_this 抛出 → guard 为空 → 视同无 master | 510 | + // 不可能插入注销;宿主进入 final release 后弱引用升级失败 → guard 为空 → 视同无 master |
| 515 | ref.host = m_master_aggregate->host_guard(); | 511 | ref.host = m_master_aggregate->host_guard(); |
| 516 | if (ref.host != nullptr) { | 512 | if (ref.host != nullptr) { |
| 517 | ref.master = m_master_aggregate; | 513 | ref.master = m_master_aggregate; |
| @@ -14,6 +14,7 @@ | |||
| 14 | #define CSR1_INTERFACE_AGGREGATE_H | 14 | #define CSR1_INTERFACE_AGGREGATE_H |
| 15 | 15 | ||
| 16 | #include <mc/future.h> | 16 | #include <mc/future.h> |
| 17 | +#include <mc/memory/weak_ptr.h> | ||
| 17 | #include <resource_tree/interface/Chip/Aggregate.h> | 18 | #include <resource_tree/interface/Chip/Aggregate.h> |
| 18 | 19 | ||
| 19 | #include "i_scheduler.h" | 20 | #include "i_scheduler.h" |
| @@ -242,6 +243,9 @@ private: | |||
| 242 | 243 | ||
| 243 | // 宿主芯片对象,传递给 dispatcher 的 owner_guard | 244 | // 宿主芯片对象,传递给 dispatcher 的 owner_guard |
| 244 | mc::object_base* m_owner_object = nullptr; | 245 | mc::object_base* m_owner_object = nullptr; |
| 246 | + // 在宿主仍存活时建立弱引用。host_guard() 必须通过非抛异常的 lock() | ||
| 247 | + // 升级,不能在 final-release 窗口对裸指针调用 noexcept shared_ptr 构造。 | ||
| 248 | + mc::weak_ptr<mc::object_base> m_owner_weak; | ||
| 245 | 249 | ||
| 246 | // 芯片去重:避免同一芯片创建多个 Aggregate。 | 250 | // 芯片去重:避免同一芯片创建多个 Aggregate。 |
| 247 | // 声明 key = 物理芯片身份 + 声明窗口(BaseOffset+Length):窗口不同不共享 | 251 | // 声明 key = 物理芯片身份 + 声明窗口(BaseOffset+Length):窗口不同不共享 |
| @@ -52,6 +52,13 @@ public: | |||
| 52 | { | 52 | { |
| 53 | return {}; | 53 | return {}; |
| 54 | } | 54 | } |
| 55 | + // Opt in only when retaining the owner also guarantees that this task | ||
| 56 | + // cannot be replaced/destroyed. Replaceable child dispatchers must wait | ||
| 57 | + // for their in-flight run before unregister_task returns. | ||
| 58 | + virtual bool lifetime_bound_to_owner() const | ||
| 59 | + { | ||
| 60 | + return false; | ||
| 61 | + } | ||
| 55 | }; | 62 | }; |
| 56 | 63 | ||
| 57 | class SchedulerTestHelper; // 测试辅助类,friend 访问私有成员 | 64 | class SchedulerTestHelper; // 测试辅助类,friend 访问私有成员 |
| @@ -78,10 +85,16 @@ private: | |||
| 78 | 85 | ||
| 79 | struct RunState { | 86 | struct RunState { |
| 80 | explicit RunState(ITask* task, mc::shared_ptr<mc::object_base> owner_guard = {}) | 87 | explicit RunState(ITask* task, mc::shared_ptr<mc::object_base> owner_guard = {}) |
| 81 | - : task(task), owner_guard(std::move(owner_guard)) | 88 | + : task(task), owner(owner_guard), has_owner(owner_guard != nullptr), |
| 89 | + can_retire(has_owner && task->lifetime_bound_to_owner()) | ||
| 82 | {} | 90 | {} |
| 83 | 91 | ||
| 84 | - ITask* task; | 92 | + ITask* task; |
| 93 | + mc::weak_ptr<mc::object_base> owner; | ||
| 94 | + bool has_owner; | ||
| 95 | + bool can_retire; | ||
| 96 | + // Only an in-flight task owns its host. Idle registration must not | ||
| 97 | + // prevent the host destructor from unregistering the task. | ||
| 85 | mc::shared_ptr<mc::object_base> owner_guard; | 98 | mc::shared_ptr<mc::object_base> owner_guard; |
| 86 | std::atomic<bool> running{false}; | 99 | std::atomic<bool> running{false}; |
| 87 | std::mutex mutex; | 100 | std::mutex mutex; |
| @@ -101,8 +114,11 @@ private: | |||
| 101 | void complete_task(const std::shared_ptr<RunState>& state); | 114 | void complete_task(const std::shared_ptr<RunState>& state); |
| 102 | static void wait_for_tasks(const std::vector<std::shared_ptr<RunState>>& states); | 115 | static void wait_for_tasks(const std::vector<std::shared_ptr<RunState>>& states); |
| 103 | 116 | ||
| 104 | - std::vector<Entry> m_entries; | 117 | + std::vector<Entry> m_entries; |
| 105 | - mc::shared_ptr<mc::timer> m_timer; | 118 | + // Managed tasks may unregister from their own asynchronous completion. |
| 119 | + // Keep their state until completion for shutdown and re-registration. | ||
| 120 | + std::vector<std::shared_ptr<RunState>> m_retired_states; | ||
| 121 | + mc::shared_ptr<mc::timer> m_timer; | ||
| 106 | 122 | ||
| 107 | // 时间零点(steady) | 123 | // 时间零点(steady) |
| 108 | std::chrono::steady_clock::time_point m_epoch = std::chrono::steady_clock::now(); | 124 | std::chrono::steady_clock::time_point m_epoch = std::chrono::steady_clock::now(); |
| @@ -336,12 +336,12 @@ mc::future<void> SmcDfxDispatcher::run_async() | |||
| 336 | mc::shared_ptr<mc::object_base> SmcDfxDispatcher::owner_guard() | 336 | mc::shared_ptr<mc::object_base> SmcDfxDispatcher::owner_guard() |
| 337 | { | 337 | { |
| 338 | // Scheduler 持锁调用的纯查询契约:只借引用、无重入。 | 338 | // Scheduler 持锁调用的纯查询契约:只借引用、无重入。 |
| 339 | - // 经 object_base 视图调用(返回类型精确匹配,不依赖 engine 层 shared_from_this | 339 | + // 调用方仍须保证 owner/dispatcher 存储有效;不允许与宿主析构并发注册。 |
| 340 | - // 的遮蔽版本): | 340 | + // 经 object_base 视图在调用时借引用,保留 set_owner 早于托管的顺序: |
| 341 | // - owner 未设置(init 前/测试直接构造 dispatcher):返回空; | 341 | // - owner 未设置(init 前/测试直接构造 dispatcher):返回空; |
| 342 | // - 未托管对象(测试夹具的 unique_ptr/栈对象):shared_from_this 的收养语义 | 342 | // - 未托管对象(测试夹具的 unique_ptr/栈对象):shared_from_this 的收养语义 |
| 343 | // 会在临时引用释放时销毁对象,必须拒绝借出; | 343 | // 会在临时引用释放时销毁对象,必须拒绝借出; |
| 344 | - // - owner 正在析构(add_ref 抛异常):返回空,其任务已/将被 teardown 注销。 | 344 | + // - owner 已进入最终释放:lock() 返回空,其任务已/将被 teardown 注销。 |
| 345 | if (m_owner == nullptr) { | 345 | if (m_owner == nullptr) { |
| 346 | return {}; | 346 | return {}; |
| 347 | } | 347 | } |
| @@ -349,11 +349,7 @@ mc::shared_ptr<mc::object_base> SmcDfxDispatcher::owner_guard() | |||
| 349 | if (!owner_base->is_managed()) { | 349 | if (!owner_base->is_managed()) { |
| 350 | return {}; | 350 | return {}; |
| 351 | } | 351 | } |
| 352 | - try { | 352 | + return owner_base->weak_from_this().lock(); |
| 353 | - return owner_base->shared_from_this(); | ||
| 354 | - } catch (const std::exception&) { | ||
| 355 | - return {}; | ||
| 356 | - } | ||
| 357 | } | 353 | } |
| 358 | 354 | ||
| 359 | bool SmcDfxDispatcher::process_tick(mc::engine::service* svc, const std::vector<uint8_t>& data) | 355 | bool SmcDfxDispatcher::process_tick(mc::engine::service* svc, const std::vector<uint8_t>& data) |
| @@ -120,6 +120,11 @@ public: | |||
| 120 | // poll runtime 时卸载可能重入并释放 owner,guard 使 owner 在整个执行批次 | 120 | // poll runtime 时卸载可能重入并释放 owner,guard 使 owner 在整个执行批次 |
| 121 | // 内存活。owner 非托管(测试夹具)或正在析构时返回空。 | 121 | // 内存活。owner 非托管(测试夹具)或正在析构时返回空。 |
| 122 | mc::shared_ptr<mc::object_base> owner_guard() override; | 122 | mc::shared_ptr<mc::object_base> owner_guard() override; |
| 123 | + bool lifetime_bound_to_owner() const override | ||
| 124 | + { | ||
| 125 | + // SmcDfxInfo_object owns this dispatcher as a non-replaceable member. | ||
| 126 | + return true; | ||
| 127 | + } | ||
| 123 | 128 | ||
| 124 | // 设置 owner(SmcDfxInfo_object 在 init 早期调用;follower 亦需设置—— | 129 | // 设置 owner(SmcDfxInfo_object 在 init 早期调用;follower 亦需设置—— |
| 125 | // promotion 后其任务注册时 guard 必须可用) | 130 | // promotion 后其任务注册时 guard 必须可用) |
| @@ -2,7 +2,7 @@ | |||
| 2 | "name": "component_drivers", | 2 | "name": "component_drivers", |
| 3 | "type": "application", | 3 | "type": "application", |
| 4 | "deployConfig": "", | 4 | "deployConfig": "", |
| 5 | - "version": "1.2.530", | 5 | + "version": "1.2.531", |
| 6 | "license": "", | 6 | "license": "", |
| 7 | "description": "", | 7 | "description": "", |
| 8 | "options": { | 8 | "options": { |
| @@ -163,6 +163,48 @@ if (owner != nullptr && owner->is_managed()) { | |||
| 163 | 163 | ||
| 164 | 调度器的弱引用让空闲注册不再阻止宿主销毁;运行期间持有宿主则保护尚未完成的工作。相当于 C 中在提交异步操作前增加 ctx 引用计数,完成后再减回去。若操作永不完成,不能仅因超时就释放仍可能被访问的 ctx。`wait_for_tasks()` 还会跳过当前同步执行状态,避免等待自身;从当前回调内注销返回,并不代表当前回调已经结束。 允许异步注销后保留运行状态的前提是任务受托管且其寿命完全绑定宿主(`lifetime_bound_to_owner()`),不能推广为任意异步回调都可安全注销;可替换子任务和未托管任务从任务执行上下文之外注销时仍需等待。 | 164 | 调度器的弱引用让空闲注册不再阻止宿主销毁;运行期间持有宿主则保护尚未完成的工作。相当于 C 中在提交异步操作前增加 ctx 引用计数,完成后再减回去。若操作永不完成,不能仅因超时就释放仍可能被访问的 ctx。`wait_for_tasks()` 还会跳过当前同步执行状态,避免等待自身;从当前回调内注销返回,并不代表当前回调已经结束。 允许异步注销后保留运行状态的前提是任务受托管且其寿命完全绑定宿主(`lifetime_bound_to_owner()`),不能推广为任意异步回调都可安全注销;可替换子任务和未托管任务从任务执行上下文之外注销时仍需等待。 |
| 165 | 165 | ||
| 166 | +主题3还处理宿主最终释放到注销任务之间的窗口。`Scheduler::on_timer()` 原来发现宿主弱引用已失效就跳过任务,却留下已到期的 `next_run_ms`;后面的 `arm_next()` 仍把它当成最早到期项,以最小间隔 10ms 反复唤醒。修改如下: | ||
| 167 | + | ||
| 168 | +```cpp | ||
| 169 | +// 修改前 | ||
| 170 | +if (entry.run_state->has_owner && !owner) { | ||
| 171 | + continue; | ||
| 172 | +} | ||
| 173 | +// 修改后 | ||
| 174 | +if (entry.run_state->has_owner && !owner) { | ||
| 175 | + entry.next_run_ms = UINT64_MAX; | ||
| 176 | + continue; | ||
| 177 | +} | ||
| 178 | +``` | ||
| 179 | + | ||
| 180 | +可以把它理解为 C 事件循环中的 `struct task { void *ctx; uint64_t deadline; }`:跳过 `callback(ctx)` 并没有取消下次唤醒;将 deadline 设成这里已有的“不参与排期”标记,才停止为它重新设定 timer。这个分支不解引用宿主;条目保留到析构中的 `unregister_task()` 删除,其他任务仍照常按到期时间排期。 | ||
| 181 | + | ||
| 182 | +两个 dispatcher 的 `owner_guard()` 也改成调用时尝试取得强引用。以 `NormalDispatcher::owner_guard()` 为例,`SmcDfxDispatcher` 对应使用 `owner_base`: | ||
| 183 | + | ||
| 184 | +```cpp | ||
| 185 | +// 修改前:nullptr / is_managed 检查之后 | ||
| 186 | +try { | ||
| 187 | + return m_owner_object->shared_from_this(); | ||
| 188 | +} catch (const std::exception&) { | ||
| 189 | + return {}; | ||
| 190 | +} | ||
| 191 | +// 修改后:仍先保留 nullptr / is_managed 检查 | ||
| 192 | +return m_owner_object->weak_from_this().lock(); | ||
| 193 | +``` | ||
| 194 | + | ||
| 195 | +本项目 `mc::shared_ptr` 的裸指针构造函数带 `noexcept`,即不允许异常离开;`shared_from_this()` 会走这个构造函数,再调用增加强引用计数的 `add_ref()`。宿主最后一次释放把计数置为 0 后,`add_ref()` 会抛异常,越过 `noexcept` 时程序终止,外层 catch 接不到。`is_managed()` 只排除从未纳入引用计数管理的对象,计数已归零仍可能返回 true,因此它不能防住这个窗口。 | ||
| 196 | + | ||
| 197 | +新语句先产生临时的 `mc::weak_ptr`,不增加强引用,再由 `lock()` 原子地尝试增加强引用:成功返回可保活宿主的句柄,失败返回空;语句结束会释放临时弱引用。用 C 类比,就是从强制 `retain(ctx)` 改为允许返回 NULL 的 `try_retain(ctx)`。这些是项目 `mc` 的具体规则,不代表所有智能指针都有相同实现。 | ||
| 198 | + | ||
| 199 | +这里没有在 `set_owner` 时缓存弱引用:先设置裸 owner、以后才建立 managed 所有权仍然有效;未托管对象仍不借出引用,避免被错误收养。调用方仍须保证 owner 与 dispatcher 的存储有效,也仍须在正常初始化/启动期间注册、销毁前注销;弱升级不能验证任意悬空指针,不能授权在宿主析构中并发注册。对应回归通过对象外的同步信号暂停析构,保证 dispatcher 和基类尚未结束生命周期,仅查询 guard,不在析构中注册。 | ||
| 200 | + | ||
| 201 | +```text | ||
| 202 | +原调度路径:宿主计数归零 → 跳过任务但保留旧 deadline → 每 10ms 再次唤醒 | ||
| 203 | +修复调度路径:宿主计数归零 → deadline = UINT64_MAX → 等既有注销删除条目 | ||
| 204 | +原借引用路径(存储仍有效):计数已归零 → add_ref 抛出 → noexcept 触发终止 | ||
| 205 | +修复借引用路径(同一前提):计数已归零 → try_add_ref 失败 → 返回空 guard | ||
| 206 | +``` | ||
| 207 | + | ||
| 166 | 主题6的 `drivers/pcie_nvme/huawei/interface/upgrade.cpp` 将等待升级线程提取为: | 208 | 主题6的 `drivers/pcie_nvme/huawei/interface/upgrade.cpp` 将等待升级线程提取为: |
| 167 | 209 | ||
| 168 | ```cpp | 210 | ```cpp |
| @@ -37,7 +37,9 @@ public: | |||
| 37 | // 返回当前持有的 timer 指针(用于验证 arm_next 跳过优化不重建 timer) | 37 | // 返回当前持有的 timer 指针(用于验证 arm_next 跳过优化不重建 timer) |
| 38 | static mc::shared_ptr<mc::timer> get_timer() | 38 | static mc::shared_ptr<mc::timer> get_timer() |
| 39 | { | 39 | { |
| 40 | - return Scheduler::get_instance().m_timer; | 40 | + auto& sched = Scheduler::get_instance(); |
| 41 | + std::lock_guard<std::recursive_mutex> lock(sched.m_mutex); | ||
| 42 | + return sched.m_timer; | ||
| 41 | } | 43 | } |
| 42 | 44 | ||
| 43 | // 添加一个已到期(next_run_ms=0)的 task entry,使 on_timer() 在下一次调用时立即执行它。 | 45 | // 添加一个已到期(next_run_ms=0)的 task entry,使 on_timer() 在下一次调用时立即执行它。 |
| @@ -81,6 +83,18 @@ public: | |||
| 81 | return 0; | 83 | return 0; |
| 82 | } | 84 | } |
| 83 | 85 | ||
| 86 | + static bool task_is_idle(ITask* task) | ||
| 87 | + { | ||
| 88 | + auto& sched = Scheduler::get_instance(); | ||
| 89 | + std::lock_guard<std::recursive_mutex> lock(sched.m_mutex); | ||
| 90 | + for (const auto& entry : sched.m_entries) { | ||
| 91 | + if (entry.task == task) { | ||
| 92 | + return entry.run_state && !entry.run_state->running.load(std::memory_order_acquire); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + return false; | ||
| 96 | + } | ||
| 97 | + | ||
| 84 | // 将某 task 的 entry 重新置为到期(next_run_ms=0),驱动下一轮 on_timer 执行 | 98 | // 将某 task 的 entry 重新置为到期(next_run_ms=0),驱动下一轮 on_timer 执行 |
| 85 | static void make_task_due(ITask* task) | 99 | static void make_task_due(ITask* task) |
| 86 | { | 100 | { |
| @@ -165,9 +179,9 @@ public: | |||
| 165 | } | 179 | } |
| 166 | 180 | ||
| 167 | private: | 181 | private: |
| 168 | - uint64_t m_period; | 182 | + uint64_t m_period; |
| 169 | - uint32_t m_run_count; | 183 | + std::atomic<uint32_t> m_run_count; |
| 170 | - bool m_throw_std; | 184 | + bool m_throw_std; |
| 171 | }; | 185 | }; |
| 172 | 186 | ||
| 173 | // ===== Task that registers another task during run: 验证锁外执行 ===== | 187 | // ===== Task that registers another task during run: 验证锁外执行 ===== |
| @@ -349,6 +363,104 @@ private: | |||
| 349 | std::atomic<uint32_t> m_run_count{0}; | 363 | std::atomic<uint32_t> m_run_count{0}; |
| 350 | }; | 364 | }; |
| 351 | 365 | ||
| 366 | +// The completion promise lives outside the task so a pending hardware result | ||
| 367 | +// does not itself keep the host alive and hide Scheduler ownership mistakes. | ||
| 368 | +struct owned_task_signals { | ||
| 369 | + mc::promise<void> completion{mc::runtime::immediate_executor{}}; | ||
| 370 | + std::atomic<bool> entered{false}; | ||
| 371 | + std::atomic<uint32_t> run_count{0}; | ||
| 372 | + std::atomic<bool> destroyed{false}; | ||
| 373 | +}; | ||
| 374 | + | ||
| 375 | +class owned_async_task : public mc::object_base, public dev::csr1::ITask { | ||
| 376 | +public: | ||
| 377 | + explicit owned_async_task(std::shared_ptr<owned_task_signals> signals) : m_signals(std::move(signals)) | ||
| 378 | + {} | ||
| 379 | + | ||
| 380 | + ~owned_async_task() override | ||
| 381 | + { | ||
| 382 | + dev::csr1::Scheduler::get_instance().unregister_task(this); | ||
| 383 | + m_signals->destroyed.store(true, std::memory_order_release); | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + void run() override | ||
| 387 | + { | ||
| 388 | + ADD_FAILURE() << "Scheduler must use run_async"; | ||
| 389 | + } | ||
| 390 | + | ||
| 391 | + mc::future<void> run_async() override | ||
| 392 | + { | ||
| 393 | + if (m_signals->run_count.fetch_add(1, std::memory_order_acq_rel) != 0) { | ||
| 394 | + return mc::resolve(); | ||
| 395 | + } | ||
| 396 | + auto completion = m_signals->completion.get_future(); | ||
| 397 | + m_signals->entered.store(true, std::memory_order_release); | ||
| 398 | + return completion; | ||
| 399 | + } | ||
| 400 | + | ||
| 401 | + uint64_t period_ms() const override | ||
| 402 | + { | ||
| 403 | + return 60000; | ||
| 404 | + } | ||
| 405 | + | ||
| 406 | + mc::shared_ptr<mc::object_base> owner_guard() override | ||
| 407 | + { | ||
| 408 | + return shared_from_this(); | ||
| 409 | + } | ||
| 410 | + bool lifetime_bound_to_owner() const override | ||
| 411 | + { | ||
| 412 | + return true; | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | +private: | ||
| 416 | + std::shared_ptr<owned_task_signals> m_signals; | ||
| 417 | +}; | ||
| 418 | + | ||
| 419 | +// Unlike owned_async_task, keeping this task's host alive does not prevent | ||
| 420 | +// reinitialization from replacing its unique_ptr child. | ||
| 421 | +class replaceable_async_task : public dev::csr1::ITask { | ||
| 422 | +public: | ||
| 423 | + replaceable_async_task(mc::object_base* owner, std::shared_ptr<owned_task_signals> signals) | ||
| 424 | + : m_owner(owner), m_signals(std::move(signals)) | ||
| 425 | + {} | ||
| 426 | + | ||
| 427 | + ~replaceable_async_task() override | ||
| 428 | + { | ||
| 429 | + m_signals->destroyed.store(true, std::memory_order_release); | ||
| 430 | + } | ||
| 431 | + | ||
| 432 | + void run() override | ||
| 433 | + { | ||
| 434 | + ADD_FAILURE() << "Scheduler must use run_async"; | ||
| 435 | + } | ||
| 436 | + | ||
| 437 | + mc::future<void> run_async() override | ||
| 438 | + { | ||
| 439 | + auto completion = m_signals->completion.get_future(); | ||
| 440 | + m_signals->entered.store(true, std::memory_order_release); | ||
| 441 | + return completion; | ||
| 442 | + } | ||
| 443 | + | ||
| 444 | + uint64_t period_ms() const override | ||
| 445 | + { | ||
| 446 | + return 60000; | ||
| 447 | + } | ||
| 448 | + | ||
| 449 | + mc::shared_ptr<mc::object_base> owner_guard() override | ||
| 450 | + { | ||
| 451 | + return m_owner->shared_from_this(); | ||
| 452 | + } | ||
| 453 | + | ||
| 454 | +private: | ||
| 455 | + mc::object_base* m_owner; | ||
| 456 | + std::shared_ptr<owned_task_signals> m_signals; | ||
| 457 | +}; | ||
| 458 | + | ||
| 459 | +class replaceable_task_host : public mc::object_base { | ||
| 460 | +public: | ||
| 461 | + std::unique_ptr<replaceable_async_task> task; | ||
| 462 | +}; | ||
| 463 | + | ||
| 352 | template <typename Predicate> | 464 | template <typename Predicate> |
| 353 | bool wait_until(Predicate&& predicate, std::chrono::milliseconds timeout) | 465 | bool wait_until(Predicate&& predicate, std::chrono::milliseconds timeout) |
| 354 | { | 466 | { |
| @@ -381,6 +493,269 @@ protected: | |||
| 381 | } | 493 | } |
| 382 | }; | 494 | }; |
| 383 | 495 | ||
| 496 | +TEST_F(SchedulerTest, idle_registration_does_not_retain_owner) | ||
| 497 | +{ | ||
| 498 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 499 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 500 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 501 | + mc::weak_ptr<owned_async_task> tracked = owner; | ||
| 502 | + auto* task = owner.get(); | ||
| 503 | + sched.register_task(task); | ||
| 504 | + | ||
| 505 | + owner.reset(); | ||
| 506 | + | ||
| 507 | + EXPECT_TRUE(tracked.expired()); | ||
| 508 | + EXPECT_TRUE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 509 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 510 | + EXPECT_FALSE(signals->entered.load(std::memory_order_acquire)); | ||
| 511 | +} | ||
| 512 | + | ||
| 513 | +TEST_F(SchedulerTest, dying_owner_does_not_rearm_an_expired_task) | ||
| 514 | +{ | ||
| 515 | + class delayed_destroy_task : public owned_async_task { | ||
| 516 | + public: | ||
| 517 | + delayed_destroy_task(std::shared_ptr<owned_task_signals> signals, std::promise<void>& entered, | ||
| 518 | + std::shared_future<void> release) | ||
| 519 | + : owned_async_task(std::move(signals)), m_entered(entered), m_release(std::move(release)) | ||
| 520 | + {} | ||
| 521 | + | ||
| 522 | + ~delayed_destroy_task() override | ||
| 523 | + { | ||
| 524 | + m_entered.set_value(); | ||
| 525 | + m_release.wait(); // Keep the task subobject alive until base teardown unregisters it. | ||
| 526 | + } | ||
| 527 | + | ||
| 528 | + private: | ||
| 529 | + std::promise<void>& m_entered; | ||
| 530 | + std::shared_future<void> m_release; | ||
| 531 | + }; | ||
| 532 | + | ||
| 533 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 534 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 535 | + std::promise<void> destructor_entered; | ||
| 536 | + auto entered = destructor_entered.get_future(); | ||
| 537 | + std::promise<void> release_destructor; | ||
| 538 | + auto owner = | ||
| 539 | + mc::make_shared<delayed_destroy_task>(signals, destructor_entered, release_destructor.get_future().share()); | ||
| 540 | + auto* task = static_cast<dev::csr1::ITask*>(owner.get()); | ||
| 541 | + sched.register_task(task); | ||
| 542 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 543 | + auto destroyer = std::async(std::launch::async, [owner = std::move(owner)]() mutable { | ||
| 544 | + owner.reset(); | ||
| 545 | + }); | ||
| 546 | + | ||
| 547 | + const auto status = entered.wait_for(std::chrono::seconds(2)); | ||
| 548 | + EXPECT_EQ(status, std::future_status::ready); | ||
| 549 | + if (status == std::future_status::ready) { | ||
| 550 | + // Only pass the task identity; do not dereference the dying owner. | ||
| 551 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 552 | + EXPECT_TRUE(sched.has_task(task)); | ||
| 553 | + EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_timer(), nullptr); | ||
| 554 | + EXPECT_EQ(signals->run_count.load(std::memory_order_acquire), 0u); | ||
| 555 | + } | ||
| 556 | + | ||
| 557 | + release_destructor.set_value(); | ||
| 558 | + destroyer.get(); | ||
| 559 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 560 | +} | ||
| 561 | + | ||
| 562 | +TEST_F(SchedulerTest, async_owner_is_retained_until_future_completion_then_released) | ||
| 563 | +{ | ||
| 564 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 565 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 566 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 567 | + mc::weak_ptr<owned_async_task> tracked = owner; | ||
| 568 | + auto* task = owner.get(); | ||
| 569 | + sched.register_task(task); | ||
| 570 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 571 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 572 | + const bool entered = wait_until([&] { | ||
| 573 | + return signals->entered.load(std::memory_order_acquire); | ||
| 574 | + }, std::chrono::seconds(2)); | ||
| 575 | + EXPECT_TRUE(entered); | ||
| 576 | + | ||
| 577 | + // Release from another thread so a broken implementation that enters the | ||
| 578 | + // destructor early can still be unblocked by settling the future below. | ||
| 579 | + auto released = std::async(std::launch::async, [owner = std::move(owner)]() mutable { | ||
| 580 | + owner.reset(); | ||
| 581 | + }); | ||
| 582 | + EXPECT_EQ(released.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 583 | + EXPECT_FALSE(tracked.expired()); | ||
| 584 | + EXPECT_FALSE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 585 | + | ||
| 586 | + signals->completion.set_value(); | ||
| 587 | + released.wait(); | ||
| 588 | + EXPECT_TRUE(wait_until([&] { | ||
| 589 | + return signals->destroyed.load(std::memory_order_acquire); | ||
| 590 | + }, std::chrono::seconds(2))); | ||
| 591 | + EXPECT_TRUE(tracked.expired()); | ||
| 592 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 593 | +} | ||
| 594 | + | ||
| 595 | +TEST_F(SchedulerTest, guarded_async_callback_can_unregister_before_completing_its_future) | ||
| 596 | +{ | ||
| 597 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 598 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 599 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 600 | + mc::weak_ptr<owned_async_task> tracked = owner; | ||
| 601 | + auto* task = owner.get(); | ||
| 602 | + sched.register_task(task); | ||
| 603 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 604 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 605 | + EXPECT_TRUE(wait_until([&] { | ||
| 606 | + return signals->entered.load(std::memory_order_acquire); | ||
| 607 | + }, std::chrono::seconds(2))); | ||
| 608 | + | ||
| 609 | + // Model a hardware completion on a different worker than run_async(). | ||
| 610 | + // Unregistration happens inside that callback, before its future settles. | ||
| 611 | + // No external owner reference remains; the scheduler guard must protect it. | ||
| 612 | + owner.reset(); | ||
| 613 | + std::atomic<bool> settled{false}; | ||
| 614 | + auto callback = std::async(std::launch::async, [&] { | ||
| 615 | + sched.unregister_task(task); | ||
| 616 | + if (!settled.exchange(true)) { | ||
| 617 | + signals->completion.set_value(); | ||
| 618 | + } | ||
| 619 | + }); | ||
| 620 | + const auto status = callback.wait_for(std::chrono::seconds(2)); | ||
| 621 | + // Break the deadlock on a failing implementation so the regression has a | ||
| 622 | + // bounded failure and can still clean up its worker and runtime safely. | ||
| 623 | + if (!settled.exchange(true)) { | ||
| 624 | + signals->completion.set_value(); | ||
| 625 | + } | ||
| 626 | + callback.wait(); | ||
| 627 | + EXPECT_EQ(status, std::future_status::ready); | ||
| 628 | + EXPECT_TRUE(wait_until([&] { | ||
| 629 | + return signals->destroyed.load(std::memory_order_acquire); | ||
| 630 | + }, std::chrono::seconds(2))); | ||
| 631 | + EXPECT_TRUE(tracked.expired()); | ||
| 632 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 633 | +} | ||
| 634 | + | ||
| 635 | +TEST_F(SchedulerTest, managed_unregister_reregister_preserves_pending_single_flight) | ||
| 636 | +{ | ||
| 637 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 638 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 639 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 640 | + auto* task = owner.get(); | ||
| 641 | + sched.register_task(task); | ||
| 642 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 643 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 644 | + EXPECT_TRUE(wait_until([&] { | ||
| 645 | + return signals->entered.load(std::memory_order_acquire); | ||
| 646 | + }, std::chrono::seconds(2))); | ||
| 647 | + | ||
| 648 | + auto removed = std::async(std::launch::async, [&] { | ||
| 649 | + sched.unregister_task(task); | ||
| 650 | + }); | ||
| 651 | + const auto status = removed.wait_for(std::chrono::seconds(2)); | ||
| 652 | + if (status != std::future_status::ready) { | ||
| 653 | + signals->completion.set_value(); | ||
| 654 | + } | ||
| 655 | + removed.wait(); | ||
| 656 | + ASSERT_EQ(status, std::future_status::ready); | ||
| 657 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 658 | + | ||
| 659 | + sched.register_task(task); | ||
| 660 | + EXPECT_TRUE(sched.has_task(task)); | ||
| 661 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 662 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 663 | + EXPECT_FALSE(wait_until([&] { | ||
| 664 | + return signals->run_count.load(std::memory_order_acquire) > 1; | ||
| 665 | + }, std::chrono::milliseconds(100))); | ||
| 666 | + EXPECT_EQ(signals->run_count.load(std::memory_order_acquire), 1u); | ||
| 667 | + | ||
| 668 | + signals->completion.set_value(); | ||
| 669 | + EXPECT_TRUE(wait_until([&] { | ||
| 670 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 671 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 672 | + return signals->run_count.load(std::memory_order_acquire) >= 2; | ||
| 673 | + }, std::chrono::seconds(2))); | ||
| 674 | + sched.clear(); | ||
| 675 | +} | ||
| 676 | + | ||
| 677 | +TEST_F(SchedulerTest, clear_waits_for_unregistered_managed_pending_future) | ||
| 678 | +{ | ||
| 679 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 680 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 681 | + auto owner = mc::make_shared<owned_async_task>(signals); | ||
| 682 | + auto* task = owner.get(); | ||
| 683 | + sched.register_task(task); | ||
| 684 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 685 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 686 | + EXPECT_TRUE(wait_until([&] { | ||
| 687 | + return signals->entered.load(std::memory_order_acquire); | ||
| 688 | + }, std::chrono::seconds(2))); | ||
| 689 | + | ||
| 690 | + auto removed = std::async(std::launch::async, [&] { | ||
| 691 | + sched.unregister_task(task); | ||
| 692 | + }); | ||
| 693 | + const auto status = removed.wait_for(std::chrono::seconds(2)); | ||
| 694 | + if (status != std::future_status::ready) { | ||
| 695 | + signals->completion.set_value(); | ||
| 696 | + } | ||
| 697 | + removed.wait(); | ||
| 698 | + ASSERT_EQ(status, std::future_status::ready); | ||
| 699 | + EXPECT_FALSE(sched.has_task(task)); | ||
| 700 | + | ||
| 701 | + std::promise<void> clear_started; | ||
| 702 | + auto started = clear_started.get_future(); | ||
| 703 | + auto cleared = std::async(std::launch::async, [&] { | ||
| 704 | + clear_started.set_value(); | ||
| 705 | + sched.clear(); | ||
| 706 | + }); | ||
| 707 | + EXPECT_EQ(started.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 708 | + EXPECT_EQ(cleared.wait_for(std::chrono::milliseconds(100)), std::future_status::timeout); | ||
| 709 | + signals->completion.set_value(); | ||
| 710 | + EXPECT_EQ(cleared.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 711 | + cleared.wait(); | ||
| 712 | + owner.reset(); | ||
| 713 | + EXPECT_TRUE(wait_until([&] { | ||
| 714 | + return signals->destroyed.load(std::memory_order_acquire); | ||
| 715 | + }, std::chrono::seconds(2))); | ||
| 716 | +} | ||
| 717 | + | ||
| 718 | +TEST_F(SchedulerTest, managed_host_unregister_waits_before_replacing_child_task) | ||
| 719 | +{ | ||
| 720 | + auto& sched = dev::csr1::Scheduler::get_instance(); | ||
| 721 | + auto signals = std::make_shared<owned_task_signals>(); | ||
| 722 | + auto owner = mc::make_shared<replaceable_task_host>(); | ||
| 723 | + mc::weak_ptr<replaceable_task_host> tracked = owner; | ||
| 724 | + owner->task = std::make_unique<replaceable_async_task>(owner.get(), signals); | ||
| 725 | + auto* task = owner->task.get(); | ||
| 726 | + sched.register_task(task); | ||
| 727 | + dev::csr1::SchedulerTestHelper::make_task_due(task); | ||
| 728 | + dev::csr1::SchedulerTestHelper::invoke_on_timer(); | ||
| 729 | + EXPECT_TRUE(wait_until([&] { | ||
| 730 | + return signals->entered.load(std::memory_order_acquire); | ||
| 731 | + }, std::chrono::seconds(2))); | ||
| 732 | + | ||
| 733 | + std::promise<void> unregister_started; | ||
| 734 | + auto started = unregister_started.get_future(); | ||
| 735 | + auto replaced = std::async(std::launch::async, [&] { | ||
| 736 | + unregister_started.set_value(); | ||
| 737 | + sched.unregister_task(task); | ||
| 738 | + // Model Interface_Aggregate::init replacing its dispatcher after stop. | ||
| 739 | + owner->task.reset(); | ||
| 740 | + }); | ||
| 741 | + EXPECT_EQ(started.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 742 | + EXPECT_TRUE(wait_until([&] { | ||
| 743 | + return !sched.has_task(task); | ||
| 744 | + }, std::chrono::seconds(2))); | ||
| 745 | + EXPECT_EQ(replaced.wait_for(std::chrono::milliseconds(100)), std::future_status::timeout); | ||
| 746 | + EXPECT_FALSE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 747 | + EXPECT_FALSE(tracked.expired()); | ||
| 748 | + | ||
| 749 | + // Always settle the operation, including on the broken implementation, | ||
| 750 | + // so the failure does not leave a blocked worker or outstanding future. | ||
| 751 | + signals->completion.set_value(); | ||
| 752 | + EXPECT_EQ(replaced.wait_for(std::chrono::seconds(2)), std::future_status::ready); | ||
| 753 | + replaced.wait(); | ||
| 754 | + EXPECT_TRUE(signals->destroyed.load(std::memory_order_acquire)); | ||
| 755 | + EXPECT_FALSE(tracked.expired()); | ||
| 756 | + EXPECT_EQ(owner->task, nullptr); | ||
| 757 | +} | ||
| 758 | + | ||
| 384 | // ===== 场景五(P0):task 异常逃逸不应阻止后续 task 与重新 arm ===== | 759 | // ===== 场景五(P0):task 异常逃逸不应阻止后续 task 与重新 arm ===== |
| 385 | 760 | ||
| 386 | TEST_F(SchedulerTest, throwing_task_run_throws_expected_exception) | 761 | TEST_F(SchedulerTest, throwing_task_run_throws_expected_exception) |
| @@ -866,8 +1241,8 @@ public: | |||
| 866 | } | 1241 | } |
| 867 | 1242 | ||
| 868 | private: | 1243 | private: |
| 869 | - uint64_t m_period; | 1244 | + std::atomic<uint64_t> m_period; |
| 870 | - uint32_t m_run_count; | 1245 | + std::atomic<uint32_t> m_run_count; |
| 871 | }; | 1246 | }; |
| 872 | 1247 | ||
| 873 | } // namespace | 1248 | } // namespace |
| @@ -883,9 +1258,16 @@ TEST_F(SchedulerTest, period_change_takes_effect_on_next_round) | |||
| 883 | 1258 | ||
| 884 | // 第一轮:按注册周期执行 | 1259 | // 第一轮:按注册周期执行 |
| 885 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); | 1260 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); |
| 886 | - ASSERT_TRUE(wait_until([&task] { | 1261 | + // run_count changes before complete_task() publishes the next deadline. |
| 887 | - return task.run_count() == 1u; | 1262 | + // Wait for that completion under the scheduler lock before forcing due. |
| 888 | - }, std::chrono::milliseconds(300))); | 1263 | + const bool completed = wait_until([&task] { |
| 1264 | + return task.run_count() == 1u && dev::csr1::SchedulerTestHelper::task_is_idle(&task); | ||
| 1265 | + }, std::chrono::milliseconds(300)); | ||
| 1266 | + EXPECT_TRUE(completed); | ||
| 1267 | + if (!completed) { | ||
| 1268 | + sched.unregister_task(&task); | ||
| 1269 | + return; | ||
| 1270 | + } | ||
| 889 | EXPECT_EQ(task.run_count(), 1u); | 1271 | EXPECT_EQ(task.run_count(), 1u); |
| 890 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 2000u); | 1272 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 2000u); |
| 891 | 1273 | ||
| @@ -895,8 +1277,8 @@ TEST_F(SchedulerTest, period_change_takes_effect_on_next_round) | |||
| 895 | 1277 | ||
| 896 | // 第二轮:entry.period_ms 刷新为新周期 | 1278 | // 第二轮:entry.period_ms 刷新为新周期 |
| 897 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); | 1279 | dev::csr1::SchedulerTestHelper::invoke_on_timer(); |
| 898 | - ASSERT_TRUE(wait_until([&task] { | 1280 | + EXPECT_TRUE(wait_until([&task] { |
| 899 | - return task.run_count() == 2u; | 1281 | + return task.run_count() == 2u && dev::csr1::SchedulerTestHelper::task_is_idle(&task); |
| 900 | }, std::chrono::milliseconds(300))); | 1282 | }, std::chrono::milliseconds(300))); |
| 901 | EXPECT_EQ(task.run_count(), 2u); | 1283 | EXPECT_EQ(task.run_count(), 2u); |
| 902 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 400u); | 1284 | EXPECT_EQ(dev::csr1::SchedulerTestHelper::get_entry_period(&task), 400u); |
| @@ -31,6 +31,7 @@ | |||
| 31 | #include "../../../../../../drivers/internal/hasdef.h" | 31 | #include "../../../../../../drivers/internal/hasdef.h" |
| 32 | 32 | ||
| 33 | #include <atomic> | 33 | #include <atomic> |
| 34 | +#include <condition_variable> | ||
| 34 | #include <deque> | 35 | #include <deque> |
| 35 | #include <thread> | 36 | #include <thread> |
| 36 | 37 | ||
| @@ -97,6 +98,35 @@ public: | |||
| 97 | dev::csr1::Interface_Aggregate aggregate; | 98 | dev::csr1::Interface_Aggregate aggregate; |
| 98 | }; | 99 | }; |
| 99 | 100 | ||
| 101 | +struct AggregateHostDestructionGate { | ||
| 102 | + std::mutex mutex; | ||
| 103 | + std::condition_variable cv; | ||
| 104 | + bool entered = false; | ||
| 105 | + bool release = false; | ||
| 106 | +}; | ||
| 107 | + | ||
| 108 | +// Keep the Aggregate member alive after the host's final strong reference has | ||
| 109 | +// reached zero. This makes the otherwise narrow final-release/destructor | ||
| 110 | +// window deterministic for host_guard() regression coverage. | ||
| 111 | +class BlockingAggregateHostObject : public mc::engine::object<BlockingAggregateHostObject> { | ||
| 112 | +public: | ||
| 113 | + MC_OBJECT(BlockingAggregateHostObject, "BlockingAggregateHost", "/bmc/test/BlockingAggregateHost/${object_name}", | ||
| 114 | + (dev::csr1::Interface_Aggregate)) | ||
| 115 | + | ||
| 116 | + ~BlockingAggregateHostObject() | ||
| 117 | + { | ||
| 118 | + std::unique_lock<std::mutex> lock(gate->mutex); | ||
| 119 | + gate->entered = true; | ||
| 120 | + gate->cv.notify_all(); | ||
| 121 | + gate->cv.wait(lock, [this] { | ||
| 122 | + return gate->release; | ||
| 123 | + }); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + std::shared_ptr<AggregateHostDestructionGate> gate; | ||
| 127 | + dev::csr1::Interface_Aggregate aggregate; | ||
| 128 | +}; | ||
| 129 | + | ||
| 100 | // 真实互斥的总线 mock:bus_lock/bus_unlock 背后是真 mutex(与生产总线一致), | 130 | // 真实互斥的总线 mock:bus_lock/bus_unlock 背后是真 mutex(与生产总线一致), |
| 101 | // 同时暴露锁深度计数,供用例断言"读期间持锁、读后释放" | 131 | // 同时暴露锁深度计数,供用例断言"读期间持锁、读后释放" |
| 102 | class counted_locking_bus : public dev::bus_base { | 132 | class counted_locking_bus : public dev::bus_base { |
| @@ -284,6 +314,7 @@ MC_REFLECT(dev::csr1::test::WindowScannerInterface, | |||
| 284 | (ReplyFromDfx)(ReplyFromDfxFailure)(FallbackToIndependent)(ReplyFromDfxSlow)) | 314 | (ReplyFromDfx)(ReplyFromDfxFailure)(FallbackToIndependent)(ReplyFromDfxSlow)) |
| 285 | MC_REFLECT(dev::csr1::test::WindowScannerObject, ((scanner, "bmc.kepler.WindowScanner"))) | 315 | MC_REFLECT(dev::csr1::test::WindowScannerObject, ((scanner, "bmc.kepler.WindowScanner"))) |
| 286 | MC_REFLECT(dev::csr1::test::AggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate"))) | 316 | MC_REFLECT(dev::csr1::test::AggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate"))) |
| 317 | +MC_REFLECT(dev::csr1::test::BlockingAggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate"))) | ||
| 287 | 318 | ||
| 288 | namespace { | 319 | namespace { |
| 289 | 320 | ||
| @@ -297,6 +328,18 @@ std::vector<uint8_t> pattern_bytes(uint32_t size) | |||
| 297 | return data; | 328 | return data; |
| 298 | } | 329 | } |
| 299 | 330 | ||
| 331 | +bool wait_for_host_release(const mc::weak_ptr<dev::csr1::test::AggregateHostObject>& host) | ||
| 332 | +{ | ||
| 333 | + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); | ||
| 334 | + while (!host.expired()) { | ||
| 335 | + if (std::chrono::steady_clock::now() >= deadline) { | ||
| 336 | + return false; | ||
| 337 | + } | ||
| 338 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| 339 | + } | ||
| 340 | + return true; | ||
| 341 | +} | ||
| 342 | + | ||
| 300 | class window_regression_service : public mc::engine::service { | 343 | class window_regression_service : public mc::engine::service { |
| 301 | public: | 344 | public: |
| 302 | explicit window_regression_service(const std::string& name) : mc::engine::service(name) | 345 | explicit window_regression_service(const std::string& name) : mc::engine::service(name) |
| @@ -397,6 +440,36 @@ protected: | |||
| 397 | 440 | ||
| 398 | // ===== 8.1 不同声明窗口:声明身份独立、绝对地址换算 ===== | 441 | // ===== 8.1 不同声明窗口:声明身份独立、绝对地址换算 ===== |
| 399 | 442 | ||
| 443 | +TEST_F(AggregateWindowRegressionTest, unmanaged_owner_does_not_acquire_weak_reference) | ||
| 444 | +{ | ||
| 445 | + mc::object_base owner; | ||
| 446 | + ASSERT_FALSE(owner.is_managed()); | ||
| 447 | + const auto weak_count = owner.weak_count(); | ||
| 448 | + { | ||
| 449 | + dev::csr1::Interface_Aggregate aggregate; | ||
| 450 | + aggregate.set_owner_object(&owner); | ||
| 451 | + EXPECT_EQ(owner.weak_count(), weak_count); | ||
| 452 | + EXPECT_EQ(aggregate.host_guard(), nullptr); | ||
| 453 | + } | ||
| 454 | + EXPECT_EQ(owner.weak_count(), weak_count); | ||
| 455 | +} | ||
| 456 | + | ||
| 457 | +TEST_F(AggregateWindowRegressionTest, dispatcher_owner_guard_supports_owner_adopted_after_assignment) | ||
| 458 | +{ | ||
| 459 | + dev::csr1::NormalDispatcher dispatcher(60000); | ||
| 460 | + auto owner = std::make_unique<mc::object_base>(); | ||
| 461 | + dispatcher.set_owner_object(owner.get()); | ||
| 462 | + const auto weak_count = owner->weak_count(); | ||
| 463 | + EXPECT_EQ(dispatcher.owner_guard(), nullptr); | ||
| 464 | + EXPECT_EQ(owner->weak_count(), weak_count); | ||
| 465 | + | ||
| 466 | + // Match ABI ownership: assign the raw owner before the engine adopts it. | ||
| 467 | + mc::shared_ptr<mc::object_base> managed(owner.release()); | ||
| 468 | + EXPECT_EQ(dispatcher.owner_guard().get(), managed.get()); | ||
| 469 | + dispatcher.set_owner_object(nullptr); | ||
| 470 | + EXPECT_EQ(dispatcher.owner_guard(), nullptr); | ||
| 471 | +} | ||
| 472 | + | ||
| 400 | TEST_F(AggregateWindowRegressionTest, different_windows_are_not_deduped) | 473 | TEST_F(AggregateWindowRegressionTest, different_windows_are_not_deduped) |
| 401 | { | 474 | { |
| 402 | setup_field_declarations(); | 475 | setup_field_declarations(); |
| @@ -826,12 +899,15 @@ TEST_F(AggregateWindowRegressionTest, follower_degrades_after_master_destroyed) | |||
| 826 | m_agg_b->start(); | 899 | m_agg_b->start(); |
| 827 | EXPECT_EQ(m_agg_b->m_master_aggregate, agg_a); | 900 | EXPECT_EQ(m_agg_b->m_master_aggregate, agg_a); |
| 828 | 901 | ||
| 902 | + mc::weak_ptr<dev::csr1::test::AggregateHostObject> master_host = m_hosts.front(); | ||
| 829 | { | 903 | { |
| 830 | // 宿主连同聚合一起析构并注销(模拟芯片卸载):本用例的首个宿主即 master 宿主 | 904 | // 宿主连同聚合一起析构并注销(模拟芯片卸载):本用例的首个宿主即 master 宿主 |
| 831 | auto host_a = m_hosts.front(); | 905 | auto host_a = m_hosts.front(); |
| 832 | m_service->unregister_object(std::string(host_a->get_object_path())); | 906 | m_service->unregister_object(std::string(host_a->get_object_path())); |
| 833 | m_hosts.erase(m_hosts.begin()); | 907 | m_hosts.erase(m_hosts.begin()); |
| 834 | } // master 析构并注销 | 908 | } // master 析构并注销 |
| 909 | + // Engine unregister callbacks can briefly retain the host asynchronously. | ||
| 910 | + ASSERT_TRUE(wait_for_host_release(master_host)); | ||
| 835 | 911 | ||
| 836 | // follower 不再向已析构 master 转发:回放迁移旧 entry + 新注册落自身 dispatcher | 912 | // follower 不再向已析构 master 转发:回放迁移旧 entry + 新注册落自身 dispatcher |
| 837 | m_agg_b->AddScanner("AfterDeathScanner", 0, 1, 0, 0xFF, 0); | 913 | m_agg_b->AddScanner("AfterDeathScanner", 0, 1, 0, 0xFF, 0); |
| @@ -843,6 +919,52 @@ TEST_F(AggregateWindowRegressionTest, follower_degrades_after_master_destroyed) | |||
| 843 | EXPECT_EQ(m_chip_b->read_records().size(), 1u); | 919 | EXPECT_EQ(m_chip_b->read_records().size(), 1u); |
| 844 | } | 920 | } |
| 845 | 921 | ||
| 922 | +TEST_F(AggregateWindowRegressionTest, host_guard_refuses_owner_during_final_release) | ||
| 923 | +{ | ||
| 924 | + auto gate = std::make_shared<dev::csr1::test::AggregateHostDestructionGate>(); | ||
| 925 | + auto host = mc::make_shared<dev::csr1::test::BlockingAggregateHostObject>(); | ||
| 926 | + host->gate = gate; | ||
| 927 | + host->set_object_name("FinalReleaseHost"); | ||
| 928 | + | ||
| 929 | + m_chip_a = std::make_shared<dev::csr1::test::windowed_mock_chip>("Chip_FPGA_1_01010315", 0x70); | ||
| 930 | + auto* aggregate = &host->aggregate; | ||
| 931 | + aggregate->m_chip = m_chip_a.get(); | ||
| 932 | + aggregate->set_owner_object(host.get()); | ||
| 933 | + m_service->register_object(host); | ||
| 934 | + aggregate->init(336, 16, 60000); | ||
| 935 | + aggregate->AddScanner("FinalReleaseScanner", 4, 2, 0, 0xFFFF, 0); | ||
| 936 | + aggregate->start(); | ||
| 937 | + | ||
| 938 | + m_service->unregister_object(std::string(host->get_object_path())); | ||
| 939 | + std::thread destroyer([host = std::move(host)]() mutable { | ||
| 940 | + host.reset(); | ||
| 941 | + }); | ||
| 942 | + | ||
| 943 | + bool destructor_entered = false; | ||
| 944 | + { | ||
| 945 | + std::unique_lock<std::mutex> lock(gate->mutex); | ||
| 946 | + destructor_entered = gate->cv.wait_for(lock, std::chrono::seconds(5), [&gate] { | ||
| 947 | + return gate->entered; | ||
| 948 | + }); | ||
| 949 | + } | ||
| 950 | + | ||
| 951 | + if (destructor_entered) { | ||
| 952 | + // The raw owner still points at live storage, but its strong count is | ||
| 953 | + // already zero. Retention must use a non-throwing weak upgrade. | ||
| 954 | + EXPECT_EQ(aggregate->host_guard(), nullptr); | ||
| 955 | + // Its dispatcher is still a live member during the blocked destructor. | ||
| 956 | + EXPECT_EQ(aggregate->dispatcher()->owner_guard(), nullptr); | ||
| 957 | + } | ||
| 958 | + | ||
| 959 | + { | ||
| 960 | + std::lock_guard<std::mutex> lock(gate->mutex); | ||
| 961 | + gate->release = true; | ||
| 962 | + } | ||
| 963 | + gate->cv.notify_all(); | ||
| 964 | + destroyer.join(); | ||
| 965 | + ASSERT_TRUE(destructor_entered); | ||
| 966 | +} | ||
| 967 | + | ||
| 846 | // ===== 8.6 读通道:Aggregate 共享读与 Scanner 独立读共用总线互斥锁 ===== | 968 | // ===== 8.6 读通道:Aggregate 共享读与 Scanner 独立读共用总线互斥锁 ===== |
| 847 | 969 | ||
| 848 | TEST_F(AggregateWindowRegressionTest, aggregate_tick_holds_bus_lock_during_read_and_releases) | 970 | TEST_F(AggregateWindowRegressionTest, aggregate_tick_holds_bus_lock_during_read_and_releases) |
| @@ -936,11 +1058,13 @@ TEST_F(AggregateWindowRegressionTest, master_destroyed_existing_scanners_migrate | |||
| 936 | EXPECT_EQ(m_agg_b->dispatcher()->entry_count(), 0u); | 1058 | EXPECT_EQ(m_agg_b->dispatcher()->entry_count(), 0u); |
| 937 | 1059 | ||
| 938 | // master 宿主析构(模拟芯片卸载) | 1060 | // master 宿主析构(模拟芯片卸载) |
| 1061 | + mc::weak_ptr<dev::csr1::test::AggregateHostObject> master_host = m_hosts.front(); | ||
| 939 | { | 1062 | { |
| 940 | auto host_a = m_hosts.front(); | 1063 | auto host_a = m_hosts.front(); |
| 941 | m_service->unregister_object(std::string(host_a->get_object_path())); | 1064 | m_service->unregister_object(std::string(host_a->get_object_path())); |
| 942 | m_hosts.erase(m_hosts.begin()); | 1065 | m_hosts.erase(m_hosts.begin()); |
| 943 | } | 1066 | } |
| 1067 | + ASSERT_TRUE(wait_for_host_release(master_host)); | ||
| 944 | 1068 | ||
| 945 | // follower 继续 tick:已转发的 entry 迁移回自身 dispatcher,原 Scanner 仍被供数 | 1069 | // follower 继续 tick:已转发的 entry 迁移回自身 dispatcher,原 Scanner 仍被供数 |
| 946 | m_agg_b->start(); | 1070 | m_agg_b->start(); |
| @@ -28,7 +28,22 @@ | |||
| 28 | #include <string> | 28 | #include <string> |
| 29 | #include <vector> | 29 | #include <vector> |
| 30 | 30 | ||
| 31 | -using dev::test::Csr1ChipObjectTestBase; | 31 | +// Only the complex chip owns aggregate scheduler tasks. Keep this cleanup in |
| 32 | +// its fixture so other chip tests need not link the complex scheduler library. | ||
| 33 | +class Csr1ChipObjectTestBase : public dev::test::Csr1ChipObjectTestBase { | ||
| 34 | +protected: | ||
| 35 | + static void TearDownTestSuite() | ||
| 36 | + { | ||
| 37 | + dev::csr1::Scheduler::get_instance().clear(); | ||
| 38 | + dev::test::Csr1ChipObjectTestBase::TearDownTestSuite(); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + void TearDown() override | ||
| 42 | + { | ||
| 43 | + dev::csr1::Scheduler::get_instance().clear(); | ||
| 44 | + dev::test::Csr1ChipObjectTestBase::TearDown(); | ||
| 45 | + } | ||
| 46 | +}; | ||
| 32 | 47 | ||
| 33 | class mock_chip_bus : public dev::bus_base { | 48 | class mock_chip_bus : public dev::bus_base { |
| 34 | public: | 49 | public: |
| @@ -1694,6 +1694,36 @@ TEST_F(Csr1SmcDfxInfoTest, promotion_picks_first_non_torn_member) | |||
| 1694 | EXPECT_NO_THROW(healthy.reset()); | 1694 | EXPECT_NO_THROW(healthy.reset()); |
| 1695 | } | 1695 | } |
| 1696 | 1696 | ||
| 1697 | +TEST_F(Csr1SmcDfxInfoTest, dispatcher_owner_guard_refuses_final_release_after_constructor_assignment) | ||
| 1698 | +{ | ||
| 1699 | + class owner_guard_probe : public dev::csr1::SmcDfxInfo_object { | ||
| 1700 | + public: | ||
| 1701 | + explicit owner_guard_probe(std::shared_ptr<std::atomic<bool>> refused) : m_refused(std::move(refused)) | ||
| 1702 | + { | ||
| 1703 | + get_dispatcher().set_owner(this); // Before make_shared establishes managed ownership. | ||
| 1704 | + } | ||
| 1705 | + | ||
| 1706 | + ~owner_guard_probe() override | ||
| 1707 | + { | ||
| 1708 | + // The dispatcher and object_base subobjects are still alive here. | ||
| 1709 | + m_refused->store(get_dispatcher().owner_guard() == nullptr, std::memory_order_release); | ||
| 1710 | + } | ||
| 1711 | + | ||
| 1712 | + private: | ||
| 1713 | + std::shared_ptr<std::atomic<bool>> m_refused; | ||
| 1714 | + }; | ||
| 1715 | + | ||
| 1716 | + auto refused = std::make_shared<std::atomic<bool>>(false); | ||
| 1717 | + auto owner = mc::make_shared<owner_guard_probe>(refused); | ||
| 1718 | + EXPECT_EQ(owner->get_dispatcher().owner_guard().get(), static_cast<mc::object_base*>(owner.get())); | ||
| 1719 | + owner.reset(); | ||
| 1720 | + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); | ||
| 1721 | + while (!refused->load(std::memory_order_acquire) && std::chrono::steady_clock::now() < deadline) { | ||
| 1722 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| 1723 | + } | ||
| 1724 | + EXPECT_TRUE(refused->load(std::memory_order_acquire)); | ||
| 1725 | +} | ||
| 1726 | + | ||
| 1697 | // 回归(Scheduler due 快照保活):tick 执行期间 owner 被并发注销且其它强引用 | 1727 | // 回归(Scheduler due 快照保活):tick 执行期间 owner 被并发注销且其它强引用 |
| 1698 | // 全部释放(模拟 tick 内同步调用递归 poll runtime、卸载在 poll 中重入的场景), | 1728 | // 全部释放(模拟 tick 内同步调用递归 poll runtime、卸载在 poll 中重入的场景), |
| 1699 | // due 快照的 owner_guard 必须保证任务执行期间 owner 存活;guard 释放后才析构。 | 1729 | // due 快照的 owner_guard 必须保证任务执行期间 owner 存活;guard 释放后才析构。 |
| @@ -1732,7 +1762,7 @@ TEST_F(Csr1SmcDfxInfoTest, owner_guard_keeps_owner_alive_across_concurrent_relea | |||
| 1732 | // 回归(真实 due 快照路径):经 Scheduler::on_timer 的完整 Phase1/Phase2 执行, | 1762 | // 回归(真实 due 快照路径):经 Scheduler::on_timer 的完整 Phase1/Phase2 执行, |
| 1733 | // tick 内(mock BlockIO.Read 注入点,等价于同步调用递归 poll runtime 的重入窗口) | 1763 | // tick 内(mock BlockIO.Read 注入点,等价于同步调用递归 poll runtime 的重入窗口) |
| 1734 | // 对象被注销且本地强引用释放,due 快照的 owner_guard 保活整个批次; | 1764 | // 对象被注销且本地强引用释放,due 快照的 owner_guard 保活整个批次; |
| 1735 | -// on_timer 返回(快照销毁)后对象才析构。 | 1765 | +// on_timer 只投递任务;异步硬件访问完成后才允许对象析构。 |
| 1736 | TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) | 1766 | TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) |
| 1737 | { | 1767 | { |
| 1738 | auto obj = mc::make_shared<dev::csr1::SmcDfxInfo_object>(); | 1768 | auto obj = mc::make_shared<dev::csr1::SmcDfxInfo_object>(); |
| @@ -1746,8 +1776,9 @@ TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) | |||
| 1746 | 1776 | ||
| 1747 | mc::weak_ptr<dev::csr1::SmcDfxInfo_object> tracked = obj; | 1777 | mc::weak_ptr<dev::csr1::SmcDfxInfo_object> tracked = obj; |
| 1748 | 1778 | ||
| 1749 | - auto& sched = dev::csr1::Scheduler::get_instance(); | 1779 | + auto& sched = dev::csr1::Scheduler::get_instance(); |
| 1750 | - auto* disp = &obj->get_dispatcher(); | 1780 | + auto* disp = &obj->get_dispatcher(); |
| 1781 | + std::shared_ptr<dev::csr1::Scheduler::RunState> run_state; | ||
| 1751 | 1782 | ||
| 1752 | // tick 内注入"卸载重入":注销(teardown 清理)+ 释放本地强引用, | 1783 | // tick 内注入"卸载重入":注销(teardown 清理)+ 释放本地强引用, |
| 1753 | // 模拟递归 poll runtime 中卸载流程释放对象的全部非快照引用。 | 1784 | // 模拟递归 poll runtime 中卸载流程释放对象的全部非快照引用。 |
| @@ -1767,12 +1798,24 @@ TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick) | |||
| 1767 | for (auto& e : sched.m_entries) { | 1798 | for (auto& e : sched.m_entries) { |
| 1768 | if (e.task == disp) { | 1799 | if (e.task == disp) { |
| 1769 | e.next_run_ms = 0; | 1800 | e.next_run_ms = 0; |
| 1801 | + run_state = e.run_state; | ||
| 1770 | } | 1802 | } |
| 1771 | } | 1803 | } |
| 1772 | } | 1804 | } |
| 1805 | + ASSERT_NE(run_state, nullptr); | ||
| 1773 | EXPECT_NO_THROW(sched.on_timer()); | 1806 | EXPECT_NO_THROW(sched.on_timer()); |
| 1774 | 1807 | ||
| 1775 | - // 批次结束(快照销毁)后对象析构 | 1808 | + // 等待实际任务完成,不能把 on_timer 返回当作硬件访问已经结束。 |
| 1809 | + { | ||
| 1810 | + std::unique_lock<std::mutex> lock(run_state->mutex); | ||
| 1811 | + ASSERT_TRUE(run_state->cv.wait_for(lock, std::chrono::seconds(5), [&] { | ||
| 1812 | + return !run_state->running.load(); | ||
| 1813 | + })); | ||
| 1814 | + } | ||
| 1815 | + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); | ||
| 1816 | + while (!tracked.expired() && std::chrono::steady_clock::now() < deadline) { | ||
| 1817 | + std::this_thread::yield(); | ||
| 1818 | + } | ||
| 1776 | EXPECT_TRUE(tracked.expired()); | 1819 | EXPECT_TRUE(tracked.expired()); |
| 1777 | 1820 | ||
| 1778 | m_chip->blockio.on_read = nullptr; | 1821 | m_chip->blockio.on_read = nullptr; |


💡 建议:
NormalDispatcher::owner_guard()与SmcDfxDispatcher::owner_guard()仍是本 PR 想避免的写法host_guard()改成了m_owner_weak.lock(),i_aggregate.h的注释给出的理由是:final-release 窗口内不能对裸指针调用shared_from_this。但同一文件第 64-75 行的NormalDispatcher::owner_guard(),以及i_smc_dfx_dispatcher.cpp:336的SmcDfxDispatcher::owner_guard(),仍然是is_managed()+shared_from_this()+catch。这两个函数现在经
Scheduler::register_task()进入RunState,结果决定has_owner/can_retire,直接影响unregister_task是“等待”还是“退休不等待”。建议二选一:set_owner_object()时保存weak_ptr,owner_guard()返回lock(),三处统一;shared_from_this是安全的。否则读者会看到同一类操作有两种安全性假设。