Merged
【84cb464e7da8942fc069941149941e40】fix(csr1): retain task owners only during active runs #1329
【84cb464e7da8942fc069941149941e40】fix(csr1): retain task owners only during active runs #1329
Merged
liuxingsenAdd on 25 days ago
Total: 13 files changed+706-58
@@ -1,5 +1,8 @@
1# Changelog1# Changelog
2All notable changes to this project will be documented in this file.2All 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-106## [1.2.530] - 2026-10-10
4fix: 修复 os下电时误清理mac地址等静态信息7fix: 修复 os下电时误清理mac地址等静态信息
5 8 
@@ -64,16 +64,13 @@ void NormalDispatcher::add_scanner(const AggregateScannerInfo& info)
64mc::shared_ptr<mc::object_base> NormalDispatcher::owner_guard()64mc::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 
79void NormalDispatcher::run()76void NormalDispatcher::run()
@@ -427,6 +424,12 @@ Interface_Aggregate::~Interface_Aggregate()
427void Interface_Aggregate::set_owner_object(mc::object_base* owner)424void 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 指针期间借住宿主,防止宿主并发析构导致 UAF489// 宿主对象强引用保活:跨对象使用 master 指针期间借住宿主,防止宿主并发析构导致 UAF
487mc::shared_ptr<mc::object_base> Interface_Aggregate::host_guard() const490mc::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
Lliuxingsen25 days ago

💡 建议: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 是“等待”还是“退休不等待”。建议二选一:

  1. 同样在 set_owner_object() 时保存 weak_ptr,owner_guard() 返回 lock(),三处统一;
  2. 或者在这两个函数的注释里写明:它们只会在宿主确定存活时调用(不会出现在 final-release 窗口),所以 shared_from_this 是安全的。

否则读者会看到同一类操作有两种安全性假设。

likedislike
liuxingsen
25 days ago Comments:
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 为空 → 视同无 master510+ // 不可能插入注销;宿主进入 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_H14#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_guard244 // 宿主芯片对象,传递给 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):窗口不同不共享
@@ -59,9 +59,10 @@ void Scheduler::register_task(ITask* task)
59 // Reuse the state when a task is registered more than once. This keeps59 // Reuse the state when a task is registered more than once. This keeps
60 // the single-flight guarantee attached to the task rather than to an60 // the single-flight guarantee attached to the task rather than to an
61 // accidental duplicate Entry.61 // accidental duplicate Entry.
62- for (const auto& existing : m_entries) {62+ for (const auto& state : m_retired_states) {
O
Oopenubmc-ci-bot25 days ago

💡 建议:复用回收的 RunState 时未刷新 owner_guard,依赖隐式不变量

register_task 从 m_retired_states 复用某个 state 时(行 62-64),直接 entry.run_state = state 并强制 next_run_ms = UINT64_MAX,隐含假设"被回收的 state 一定仍在运行"。当前确实成立(unregister_task 只把 running==true 的 state 迁入 m_retired_states),所以行为正确。

但需要注意的是:复用路径不会重新调用 task->owner_guard() / 重新派生出 owner/has_owner/can_retire,而是沿用上一次注册时的 owner 引用。对于 can_retire 的托管任务(通常宿主即任务自身,如 owned_async_task)这无影响;但若某类任务在"注销期间宿主上下文发生变化"后重新注册,将复用旧的 owner guard。

此外,复用期间同一 RunState 同时存在于 m_entries 和 m_retired_states(靠 complete_task 在完成后清理),属于瞬时双引用。建议:复用前先从 m_retired_states 中移除该 state(保持单一所有权),并将"回收 state == 正在运行"这一不变量显式注释或断言出来,降低后续维护误用风险。

likedislike
liuxingsen
25 days ago Comments:
63- if (existing.task == task && existing.run_state) {63+ if (state->task == task) {
64- entry.run_state = existing.run_state;64+ entry.run_state = state;
65+ entry.next_run_ms = UINT64_MAX;
65 break;66 break;
66 }67 }
67 }68 }
@@ -85,10 +86,17 @@ void Scheduler::unregister_task(ITask* task)
85 {86 {
86 std::lock_guard<std::recursive_mutex> lock(m_mutex);87 std::lock_guard<std::recursive_mutex> lock(m_mutex);
87 88 
88- auto it = std::remove_if(m_entries.begin(), m_entries.end(), [&states, task](const Entry& e) {89+ auto it = std::remove_if(m_entries.begin(), m_entries.end(), [this, &states, task](const Entry& e) {
89 if (e.task == task) {90 if (e.task == task) {
90 if (e.run_state) {91 if (e.run_state) {
91- states.push_back(e.run_state);92+ if (e.run_state->can_retire && e.run_state->running.load(std::memory_order_acquire)) {
93+ if (std::find(m_retired_states.begin(), m_retired_states.end(), e.run_state) ==
94+ m_retired_states.end()) {
95+ m_retired_states.push_back(e.run_state);
96+ }
97+ } else {
98+ states.push_back(e.run_state);
99+ }
92 }100 }
93 return true;101 return true;
94 }102 }
@@ -101,8 +109,9 @@ void Scheduler::unregister_task(ITask* task)
101 arm_next();109 arm_next();
102 }110 }
103 111 
104- // 生命周期保证:等待被注销 Entry 的 run() 结束,避免 owner 在异步112+ // Tasks bound to their retained host can unregister inside an asynchronous
105- // callback 仍使用 task 时销毁 task。等待放在锁外,避免 run() 反向访问 Scheduler。113+ // callback without waiting on themselves. Unmanaged and replaceable child
114+ // tasks must finish before their caller can destroy or replace them.
106 wait_for_tasks(states);115 wait_for_tasks(states);
107}116}
108 117 
@@ -172,6 +181,8 @@ void Scheduler::clear()
172 }181 }
173 }182 }
174 m_entries.clear();183 m_entries.clear();
184+ states.insert(states.end(), m_retired_states.begin(), m_retired_states.end());
185+ m_retired_states.clear();
175 }186 }
176 187 
177 // 与 unregister_task 相同,只等待 clear() 移除的任务。188 // 与 unregister_task 相同,只等待 clear() 移除的任务。
@@ -211,13 +222,19 @@ void Scheduler::on_timer(uint64_t expected_generation)
211 continue;222 continue;
212 }223 }
213 224 
214- if (entry.task != nullptr) {
215- entry.period_ms = std::max(entry.task->period_ms(), MIN_TICK_MS);
216- }
217- 
218 if (!entry.run_state) {225 if (!entry.run_state) {
219 entry.run_state = std::make_shared<RunState>(entry.task);226 entry.run_state = std::make_shared<RunState>(entry.task);
220 }227 }
228+ auto owner = entry.run_state->owner.lock();
229+ if (entry.run_state->has_owner && !owner) {
230+ // The host is being destroyed and will unregister its task.
231+ // Do not dereference the task or keep rearming its expired deadline.
232+ entry.next_run_ms = UINT64_MAX;
233+ continue;
L
Lliuxingsen25 days ago

💡 建议:宿主析构窗口内的跳过分支会让调度定时器每 10ms 空转一次

has_owner && !owner 时直接 continue,entry.next_run_ms 仍是已到期的旧值。随后 arm_next() 计算出的 min_next <= now_ms,delay_ms 被钳到 MIN_TICK_MS(10ms),于是 on_timer 每 10ms 触发一次,每次都走到这个分支,一直持续到宿主析构走到 unregister_task。

如果宿主析构在 unregister_task 之前还要等别的慢操作(例如 wait_for_tasks 等另一个在途的 I/O 任务),这段空转可能持续较长时间,并且是在 I/O executor 上。弱引用一旦失效就不可能恢复,所以这里可以直接把该项挂起:

if (entry.run_state->has_owner && !owner) {
    entry.next_run_ms = UINT64_MAX; // host is in final release; unregister_task will remove this entry
    continue;
}

UINT64_MAX 在本文件已经表示“不参与排期”,arm_next() 会自动忽略这一项。

likedislike
liuxingsen
25 days ago Comments:
234+ }
235+ if (entry.task != nullptr) {
236+ entry.period_ms = std::max(entry.task->period_ms(), MIN_TICK_MS);
237+ }
221 // 任务仍在执行时丢弃本轮,不把慢 I/O 变成排队堆积。238 // 任务仍在执行时丢弃本轮,不把慢 I/O 变成排队堆积。
222 bool claimed = false;239 bool claimed = false;
223 {240 {
@@ -227,6 +244,7 @@ void Scheduler::on_timer(uint64_t expected_generation)
227 std::memory_order_acquire);244 std::memory_order_acquire);
228 }245 }
229 if (claimed) {246 if (claimed) {
247+ entry.run_state->owner_guard = std::move(owner);
230 // 下一次触发严格从本轮完成后重新计算;慢任务期间不保留248 // 下一次触发严格从本轮完成后重新计算;慢任务期间不保留
231 // 周期 deadline,也不做 missed-period 补偿。249 // 周期 deadline,也不做 missed-period 补偿。
232 entry.next_run_ms = UINT64_MAX;250 entry.next_run_ms = UINT64_MAX;
@@ -283,6 +301,7 @@ void Scheduler::execute_task(const std::shared_ptr<RunState>& state)
283 301 
284void Scheduler::complete_task(const std::shared_ptr<RunState>& state)302void Scheduler::complete_task(const std::shared_ptr<RunState>& state)
285{303{
304+ mc::shared_ptr<mc::object_base> completed_owner;
286 {305 {
287 std::lock_guard<std::recursive_mutex> scheduler_lock(m_mutex);306 std::lock_guard<std::recursive_mutex> scheduler_lock(m_mutex);
288 for (auto& entry : m_entries) {307 for (auto& entry : m_entries) {
@@ -296,8 +315,11 @@ void Scheduler::complete_task(const std::shared_ptr<RunState>& state)
296 entry.next_run_ms = now_ms + entry.period_ms;315 entry.next_run_ms = now_ms + entry.period_ms;
297 break;316 break;
298 }317 }
318+ m_retired_states.erase(std::remove(m_retired_states.begin(), m_retired_states.end(), state),
319+ m_retired_states.end());
299 {320 {
300 std::lock_guard<std::mutex> state_lock(state->mutex);321 std::lock_guard<std::mutex> state_lock(state->mutex);
322+ completed_owner = std::move(state->owner_guard);
O
Oopenubmc-ci-bot25 days ago

⚠️ 警告:任务未决时 m_retired_states 持续保握宿主,且无超时回收路径

当 lifetime_bound_to_owner() 的托管任务在 in-flight 期间被 unregister_task 迁入 m_retired_states 时,其 owner_guard(由 on_timer claimed 分支在行 246 写入的强引用)会一直保留在该 RunState 中,直到异步 future 结算并由 complete_task 中 std::move(state->owner_guard) 释放。

complete_task 仅在 future 完成后被调用。若某次硬件读/异步操作挂起、永不 resolve,则该宿主对象将被永久钉住而无法析构,对应的 RunState 也一直滞留 m_retired_states(唯一兜底是 clear())。

旧实现虽然也有"完成任务前保留 owner_guard"的行为,但不会把这个 state 放进一个独立的、只由完成路径清理的容器。建议至少补充注释说明这一约束;如果可能,为长时间未决的托管任务考虑超时/放弃策略,避免挂死造成宿主泄漏。

likedislike
liuxingsen
25 days ago Comments:
301 state->running.store(false, std::memory_order_release);323 state->running.store(false, std::memory_order_release);
302 }324 }
303 arm_next();325 arm_next();
@@ -340,8 +362,8 @@ void Scheduler::arm_next()
340 }362 }
341 }363 }
342 364 
343- // 所有任务都在 in-flight:不保留无意义的 timer。下一次 timer 由任务365+ // 所有任务都在 in-flight 或等待宿主析构注销:不保留无意义的 timer。
344- // 完成路径 complete_task() 重新启动。366+ // in-flight 任务由 complete_task() 重新安排;失去宿主的任务不再运行。
345 if (min_next == UINT64_MAX) {367 if (min_next == UINT64_MAX) {
346 auto old_timer = std::move(m_timer);368 auto old_timer = std::move(m_timer);
347 m_armed_min_next = 0;369 m_armed_min_next = 0;
@@ -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 
57class SchedulerTestHelper; // 测试辅助类,friend 访问私有成员64class 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()
336mc::shared_ptr<mc::object_base> SmcDfxDispatcher::owner_guard()336mc::shared_ptr<mc::object_base> SmcDfxDispatcher::owner_guard()
337{337{
338 // Scheduler 持锁调用的纯查询契约:只借引用、无重入。338 // Scheduler 持锁调用的纯查询契约:只借引用、无重入。
339- // 经 object_base 视图调用(返回类型精确匹配,不依赖 engine 层 shared_from_this339+ // 调用方仍须保证 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 
359bool SmcDfxDispatcher::process_tick(mc::engine::service* svc, const std::vector<uint8_t>& data)355bool 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```cpp210```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 
167private:181private:
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+ 
352template <typename Predicate>464template <typename Predicate>
353bool wait_until(Predicate&& predicate, std::chrono::milliseconds timeout)465bool 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 
386TEST_F(SchedulerTest, throwing_task_run_throws_expected_exception)761TEST_F(SchedulerTest, throwing_task_run_throws_expected_exception)
@@ -866,8 +1241,8 @@ public:
866 }1241 }
867 1242 
868private:1243private:
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} // namespace1248} // 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// 同时暴露锁深度计数,供用例断言"读期间持锁、读后释放"
102class counted_locking_bus : public dev::bus_base {132class 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))
285MC_REFLECT(dev::csr1::test::WindowScannerObject, ((scanner, "bmc.kepler.WindowScanner")))315MC_REFLECT(dev::csr1::test::WindowScannerObject, ((scanner, "bmc.kepler.WindowScanner")))
286MC_REFLECT(dev::csr1::test::AggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate")))316MC_REFLECT(dev::csr1::test::AggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate")))
317+MC_REFLECT(dev::csr1::test::BlockingAggregateHostObject, ((aggregate, "bmc.kepler.Chip.Aggregate")))
287 318 
288namespace {319namespace {
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+ 
300class window_regression_service : public mc::engine::service {343class window_regression_service : public mc::engine::service {
301public:344public:
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+ 
400TEST_F(AggregateWindowRegressionTest, different_windows_are_not_deduped)473TEST_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 + 新注册落自身 dispatcher912 // 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 
848TEST_F(AggregateWindowRegressionTest, aggregate_tick_holds_bus_lock_during_read_and_releases)970TEST_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 
33class mock_chip_bus : public dev::bus_base {48class mock_chip_bus : public dev::bus_base {
34public:49public:
@@ -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 只投递任务;异步硬件访问完成后才允许对象析构。
1736TEST_F(Csr1SmcDfxInfoTest, scheduler_due_snapshot_keeps_owner_alive_during_tick)1766TEST_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;