已开启
fix: 完善 MCTP 端点取消与协议动作收尾 #1331
fix: 完善 MCTP 端点取消与协议动作收尾 #1331
已开启
liuxingsen创建于 25 天前
共 10 个文件变更+1341-52
@@ -0,0 +1,8 @@
1+# [PR !1331](https://gitcode.com/openUBMC/component_drivers/pull/1331):完善 MCTP 端点取消与协议动作收尾
2+ 
3+组件版本:1.2.580
4+ 
5+## 变更内容
6+ 
7+- 完善 MCTP 端点重试取消与在途回调排空:停止后忽略旧代次,活动回调退出后执行延后清理;非框架托管对象仍须由调用方保证回调期间存活。
8+- 加入协议动作队列,串行执行停旧、清理和启动,避免嵌套调用直接释放当前调用栈仍在使用的资源。
@@ -0,0 +1,133 @@
1+/*
2+ * Copyright (c) 2026 Huawei Technologies Co., Ltd.
3+ * openUBMC is licensed under Mulan PSL v2.
4+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
5+ * You may obtain a copy of Mulan PSL v2 at:
6+ * http://license.coscl.org.cn/MulanPSL2
7+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
8+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
9+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
10+ * See the Mulan PSL v2 for more details.
11+ */
12+#ifndef DRIVERS_PROTOCOL_LIFECYCLE_H
13+#define DRIVERS_PROTOCOL_LIFECYCLE_H
14+ 
15+#include <deque>
16+#include <exception>
17+#include <functional>
18+#include <mc/log.h>
19+#include <memory>
20+#include <mutex>
21+#include <utility>
22+ 
23+namespace dev {
24+// 串行执行完整的停旧、清理、启动过程;短锁只保护队列,不能跨越会推进事件循环的同步请求。
25+class protocol_lifecycle {
26+public:
27+ using completion = std::function<void()>;
28+ using action = std::function<void(completion)>;
29+ 
30+ void enqueue(action operation)
31+ {
32+ auto state = m_state;
33+ auto entry = std::make_shared<item>(std::move(operation));
34+ {
35+ std::lock_guard<std::mutex> guard(state->mutex);
36+ state->pending.push_back(entry);
37+ }
38+ pump(state, entry);
39+ }
40+ 
41+private:
42+ struct item {
43+ explicit item(action operation) : operation(std::move(operation))
44+ {}
45+ action operation;
46+ bool invoked = false;
47+ bool completed = false;
48+ };
49+ struct state {
50+ std::mutex mutex;
51+ std::deque<std::shared_ptr<item>> pending;
52+ std::shared_ptr<item> current;
53+ bool pumping = false;
54+ };
55+ 
56+ static void pump(const std::shared_ptr<state>& queue, const std::shared_ptr<item>& caller = {})
57+ {
58+ {
59+ std::lock_guard<std::mutex> guard(queue->mutex);
60+ if (queue->pumping) {
61+ return;
62+ }
63+ queue->pumping = true;
64+ }
65+ std::exception_ptr error;
66+ for (;;) {
67+ std::shared_ptr<item> retired;
68+ std::shared_ptr<item> current;
69+ {
70+ std::lock_guard<std::mutex> guard(queue->mutex);
71+ if (queue->current && queue->current->completed) {
72+ retired = std::move(queue->current);
73+ }
74+ if (!queue->current && !queue->pending.empty()) {
75+ queue->current = std::move(queue->pending.front());
76+ queue->pending.pop_front();
77+ }
78+ if (queue->current && !queue->current->invoked) {
79+ current = queue->current;
80+ current->invoked = true;
81+ } else {
82+ queue->pumping = false;
83+ }
84+ }
85+ // 释放项内保活的设备可能触发析构及再次入队,必须在锁外进行。
86+ if (retired) {
87+ // 完成通知可能被调用方继续保存;清空操作捕获,不能因此延长设备保活。
88+ retired->operation = {};
89+ }
90+ retired.reset();
91+ if (!current) {
92+ if (error) {
93+ std::rethrow_exception(error);
O

⚠️ 警告:protocol_lifecycle::pump 的异常可能在异步线程上被重新抛出

第 92 行 std::rethrow_exception(error) 会把本次 pump 循环中捕获的首个异常,在该循环最终走到 !current 时抛出。而 pump 既会由调用方线程(同步 enqueue)触发,也会由异步完成回调 done()(例如 MCTP worker/timer 线程)触发。

后果有二:

  1. 若队列是经某异步回调的 done() 排空的,异常会在那个框架 worker/timer 线程上被抛出;该上下文若无捕获,会变成未捕获异常导致进程终止。
  2. 异常源是操作 A,却会被投递给“恰好触发下一次排空的 B 的调用上下文”,而不是 A 的原始 enqueue 调用方,错误归属错乱。

注意:error 是 pump 的局部变量,每次调用都会复位,因此这个行为逐次成立。

修复建议:在由 done() 驱动的异步排空路径中不要向外重抛异常(记录日志并继续),只在同步 enqueue 路径向调用方传播;或显式区分调用来源。

likedislike
liuxingsen
18 天前 评论:
94+ }
95+ return;
96+ }
97+ auto done = [queue, current] {
98+ {
99+ std::lock_guard<std::mutex> guard(queue->mutex);
100+ if (current->completed) {
101+ return;
102+ }
103+ current->completed = true;
104+ }
105+ // 同步完成时只标记结束;外层 operation 返回后才能执行下一项。
106+ pump(queue);
107+ };
108+ try {
109+ current->operation(done);
110+ } catch (...) {
O

💡 建议:同步异常的传播契约只对“首个被同步执行的 action”成立,多线程入队者拿不到自己的异常

current == caller 只比较当前 action 与本次 enqueue 的 item。当队列已被某一线程的 pump 驱动、且首个 action 异步完成时,后续线程 enqueue 的 action 会由“完成回调触发的另一个 pump”在 caller == {} 的上下文中执行:此时若该 action 同步抛异常,会落入 else 分支仅打印日志并标记完成,不会传播给真正入队的调用方,与注释“仅本次 enqueue 同步执行的动作可向原调用方传播异常”不一致。

修复建议:若多线程入队并期望同步拿到自身 action 的异常,应在入队 API 处记录每个 item 的入队线程/调用方,使其异常回投给本入队者;或在文档/契约中明确“同步传播仅保证对排空队列的首个 action 有效”,避免调用方误以为总能捕获自己 action 的异常。

likedislike
111+ if (current == caller) {
112+ // 仅本次 enqueue 同步执行的动作可向原调用方传播异常。
113+ error = std::current_exception();
114+ } else {
115+ // done() 可能来自 worker;其他入队者的错误不能抛到其完成通知中。
116+ try {
117+ throw;
118+ } catch (const std::exception& ex) {
119+ elog("Deferred protocol lifecycle action failed: ${error}", ("error", ex.what()));
120+ } catch (...) {
121+ elog("Deferred protocol lifecycle action failed: unknown exception");
122+ }
123+ }
124+ std::lock_guard<std::mutex> guard(queue->mutex);
125+ current->completed = true;
126+ }
127+ }
128+ }
129+ 
130+ std::shared_ptr<state> m_state = std::make_shared<state>();
131+};
132+} // namespace dev
133+#endif // DRIVERS_PROTOCOL_LIFECYCLE_H
@@ -12,22 +12,123 @@
12 12 
13#include <mc/engine/base.h>13#include <mc/engine/base.h>
14#include <mc/log.h>14#include <mc/log.h>
15+#include <mc/runtime/condition_variable.h>
15#include <mc/timer.h>16#include <mc/timer.h>
17+#include <mutex>
18+#include <thread>
19+#include <unordered_map>
16 20 
17#include "mctp.h"21#include "mctp.h"
18#include "pcie_transport.h"22#include "pcie_transport.h"
19 23 
20namespace dev {24namespace dev {
21 25 
26+// timer 的 stop 只能等待那一只 timer,无法覆盖初次同步查询或已被下一轮替换的 timer。
27+// 独立共享状态让晚到的 lambda 先检查代次,再决定能否访问捕获的 this。
28+struct mctp::endpoint_retry_state {
29+ std::mutex mutex;
30+ mc::runtime::condition_variable finished;
31+ uint64_t generation{0};
32+ bool cancelled{false};
33+ size_t active{0};
34+ std::unordered_map<std::thread::id, size_t> active_threads;
35+ bool lifetime_initialized{false};
36+ bool managed_self{false};
37+ bool managed_parent{false};
38+ mc::weak_ptr<mc::object> self;
39+ mc::weak_ptr<mc::object> parent;
40+ std::vector<std::function<void()>> deferred_cleanups;
41+ bool cleanup_running{false};
42+ std::thread::id cleanup_thread;
43+ 
44+ bool is_current(uint64_t expected)
45+ {
46+ std::lock_guard<std::mutex> lock(mutex);
47+ return !cancelled && generation == expected;
48+ }
49+};
50+ 
51+class mctp::endpoint_retry_activity {
52+public:
53+ endpoint_retry_activity(std::shared_ptr<endpoint_retry_state> state, uint64_t generation)
54+ : m_state(std::move(state))
55+ {
56+ std::lock_guard<std::mutex> lock(m_state->mutex);
57+ if (!m_state->cancelled && m_state->generation == generation) {
58+ // weak 在首次有效调用中建立;重试不再解引用裸 this 来取得所有权。
59+ // mc::shared_ptr 的保活只适用于本来受框架管理的对象,不能接管栈/unique_ptr 对象。
60+ m_self = m_state->self.lock();
61+ m_parent = m_state->parent.lock();
62+ if ((m_state->managed_self && !m_self) || (m_state->managed_parent && !m_parent)) {
63+ return;
64+ }
65+ ++m_state->active_threads[std::this_thread::get_id()];
66+ ++m_state->active;
67+ m_entered = true;
68+ }
69+ }
70+ 
71+ ~endpoint_retry_activity()
72+ {
73+ if (!m_entered) {
74+ return;
75+ }
76+ std::vector<std::function<void()>> cleanups;
77+ {
78+ std::lock_guard<std::mutex> lock(m_state->mutex);
79+ auto it = m_state->active_threads.find(std::this_thread::get_id());
80+ if (--it->second == 0) {
81+ m_state->active_threads.erase(it);
82+ }
83+ --m_state->active;
84+ if (m_state->active == 0 && !m_state->cleanup_running && !m_state->deferred_cleanups.empty()) {
85+ m_state->cleanup_running = true;
86+ m_state->cleanup_thread = std::this_thread::get_id();
87+ cleanups.swap(m_state->deferred_cleanups);
88+ }
89+ }
90+ while (!cleanups.empty()) {
91+ for (auto& cleanup : cleanups) {
92+ try {
93+ cleanup();
94+ } catch (const std::exception& e) {
95+ elog("Deferred MCTP cleanup failed: ${error}", ("error", e.what()));
96+ } catch (...) {
97+ elog("Deferred MCTP cleanup failed with unknown exception");
98+ }
99+ }
100+ // 函数捕获的引用也只能在锁外释放,避免析构再次取得状态锁。
101+ cleanups.clear();
102+ {
103+ std::lock_guard<std::mutex> lock(m_state->mutex);
104+ cleanups.swap(m_state->deferred_cleanups);
105+ if (cleanups.empty()) {
106+ m_state->cleanup_running = false;
107+ m_state->cleanup_thread = {};
108+ }
109+ }
110+ }
111+ // 外部取消必须等 deferred cleanup 一并完成,不能仅等待 active 降至零。
112+ m_state->finished.notify_all();
113+ }
114+ 
115+ explicit operator bool() const
116+ {
117+ return m_entered;
118+ }
119+ endpoint_retry_activity(const endpoint_retry_activity&) = delete;
120+ endpoint_retry_activity& operator=(const endpoint_retry_activity&) = delete;
121+ 
122+private:
123+ std::shared_ptr<endpoint_retry_state> m_state;
124+ mc::object_ptr m_parent;
125+ mc::object_ptr m_self;
126+ bool m_entered{false};
127+};
128+ 
22mctp::~mctp()129mctp::~mctp()
23{130{
24- m_alive->store(false);131+ cancel_endpoint_retry();
25- // 取消未决重试链:到期回调经 alive 检查已不会执行 cb,stop 同时把 timer
26- // 从自身 children 摘除,避免析构期滞留
27- if (m_retry_timer) {
28- m_retry_timer->stop();
29- m_retry_timer.reset();
30- }
31 if (m_transport_obj) {132 if (m_transport_obj) {
32 m_transport_obj.reset();133 m_transport_obj.reset();
33 m_transport_obj = nullptr;134 m_transport_obj = nullptr;
@@ -36,7 +137,7 @@ mctp::~mctp()
36 137 
37mctp::mctp(mc::engine::abstract_object* parent, uint16_t phy_addr, MCTP_MESSAGE_TYPE msg_type, std::string position)138mctp::mctp(mc::engine::abstract_object* parent, uint16_t phy_addr, MCTP_MESSAGE_TYPE msg_type, std::string position)
38 : mc::object(parent), m_phy_addr(phy_addr), m_msg_type(msg_type), m_position(position), m_parent(parent),139 : mc::object(parent), m_phy_addr(phy_addr), m_msg_type(msg_type), m_position(position), m_parent(parent),
39- m_alive(std::make_shared<std::atomic<bool>>(true))140+ m_endpoint_retry_state(std::make_shared<endpoint_retry_state>())
40{141{
41 m_transport_obj = nullptr;142 m_transport_obj = nullptr;
42 if (position != std::string("")) {143 if (position != std::string("")) {
@@ -112,20 +213,64 @@ void mctp::create_transport_obj(mc::dict& object)
112 }213 }
113}214}
114 215 
115-void mctp::destroy_transport_obj()216+bool mctp::cancel_endpoint_retry(std::function<void()> deferred_cleanup)
116{217{
117- // 先取消未决重试链,避免销毁过程中 timer 到期重入218+ auto state = m_endpoint_retry_state;
118- if (m_retry_timer) {219+ mc::timer_ptr timer;
119- m_retry_timer->stop();220+ bool reentrant;
120- m_retry_timer.reset();221+ bool in_cleanup;
222+ {
223+ std::lock_guard<std::mutex> lock(state->mutex);
224+ state->cancelled = true;
225+ ++state->generation;
226+ timer = std::move(m_retry_timer);
227+ reentrant = state->active_threads.count(std::this_thread::get_id()) != 0;
228+ in_cleanup = state->cleanup_running && state->cleanup_thread == std::this_thread::get_id();
229+ if ((reentrant || in_cleanup) && deferred_cleanup) {
230+ state->deferred_cleanups.push_back(std::move(deferred_cleanup));
231+ }
121 }232 }
122 233 
123- if (m_transport_obj) {234+ // 不能持有状态锁调用 stop:活动回调退出和重装检查也需要同一把锁。
124- // parent/service 为空时无法 unregister235+ if (timer) {
125- if (m_parent == nullptr) {236+ timer->timeout.disconnect_all();
126- wlog("destroy_transport_obj: parent is null, path=${path}", ("path", m_transport_path));237+ if (reentrant || in_cleanup) {
238+ // 同线程可能是同步请求推进事件时的嵌套调用,不能等待自己或互相等待。
239+ // 摘链后只剩在途引用;即使外部仍保留 timer,其槽和旧代都已失效。
240+ timer->set_parent(nullptr);
127 } else {241 } else {
128- auto service = m_parent->get_service();242+ timer->stop();
243+ }
244+ }
245+ if (reentrant || in_cleanup) {
246+ return false;
247+ }
248+ 
249+ std::unique_lock<std::mutex> lock(state->mutex);
250+ // runtime 条件变量在框架 worker 上会推进等待,避免阻断 callback 的同步请求。
251+ state->finished.wait(lock, [&state]() {
252+ return state->active == 0 && !state->cleanup_running;
253+ });
254+ return true;
255+}
256+ 
257+void mctp::destroy_transport_obj()
258+{
259+ // 保留旧接口同步注销的契约,避免旧调用方随后重建同路径时仍遇到旧注册。
260+ // 同线程不能排空时这里只使旧代失效;消费者安全须由调用方先 cancel(cleanup) 保证。
261+ cancel_endpoint_retry();
262+ destroy_transport_obj_impl();
263+}
264+ 
265+void mctp::destroy_transport_obj_impl()
266+{
267+ if (m_transport_obj) {
268+ {
269+ // 宿主可能已被框架注销;transport 自己的注册 service 才能完成延后注销。
270+ auto service = m_transport_obj->get_service();
271+ if (service == nullptr && m_parent != nullptr) {
272+ service = m_parent->get_service();
273+ }
129 if (service == nullptr) {274 if (service == nullptr) {
130 wlog("destroy_transport_obj: service is null, path=${path}", ("path", m_transport_path));275 wlog("destroy_transport_obj: service is null, path=${path}", ("path", m_transport_path));
131 } else if (!m_transport_path.empty()) {276 } else if (!m_transport_path.empty()) {
@@ -151,10 +296,33 @@ void mctp::destroy_transport_obj()
151 296 
152void mctp::try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb)297void mctp::try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb)
153{298{
154- auto alive = m_alive;299+ auto state = m_endpoint_retry_state;
155- // 对象已析构时直接返回,不得再调用 cb():cb 通常以裸指针/[this] 捕获调用方对象,300+ uint64_t generation;
156- // 调用方此时可能也已销毁,继续调用会造成 use-after-free(悬空虚函数调用)。301+ {
157- if (!alive->load()) {302+ std::lock_guard<std::mutex> lock(state->mutex);
303+ if (!state->cancelled && !state->lifetime_initialized) {
304+ state->managed_self = is_managed();
305+ state->managed_parent = m_parent != nullptr && m_parent->is_managed();
306+ if (state->managed_self) {
307+ state->self = weak_from_this();
308+ }
309+ if (state->managed_parent) {
310+ state->parent = m_parent->weak_from_this();
311+ }
312+ state->lifetime_initialized = true;
313+ }
314+ generation = state->generation;
315+ }
316+ endpoint_retry_activity activity(state, generation);
317+ if (activity) {
318+ try_get_endpoint_object_impl(retry_count, cb, state, generation);
319+ }
320+}
321+ 
322+void mctp::try_get_endpoint_object_impl(uint8_t retry_count, const std::function<void()>& cb,
323+ const std::shared_ptr<endpoint_retry_state>& state, uint64_t generation)
324+{
325+ if (!state->is_current(generation)) {
158 return;326 return;
159 }327 }
160 328 
@@ -173,7 +341,7 @@ void mctp::try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb
173 "org.freedesktop.DBus.Properties", "GetAll", "s",341 "org.freedesktop.DBus.Properties", "GetAll", "s",
174 mc::variants{std::string(PCIE_ENDPOINT_INTERFACE)});342 mc::variants{std::string(PCIE_ENDPOINT_INTERFACE)});
175 343 
176- if (!alive->load()) {344+ if (!state->is_current(generation)) {
177 return;345 return;
178 }346 }
179 347 
@@ -197,31 +365,42 @@ void mctp::try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb
197 m_create_endpoint_success = false;365 m_create_endpoint_success = false;
198 }366 }
199 }367 }
200- if (cb) {368+ if (cb && state->is_current(generation)) {
201 cb();369 cb();
202 }370 }
203 } catch (const std::exception& e) {371 } catch (const std::exception& e) {
204- if (!alive->load()) {372+ if (!state->is_current(generation)) {
205 return;373 return;
206 }374 }
207 if (retry_count < 60) {375 if (retry_count < 60) {
208 elog("Get endpoint object(${name}) failed, retry...(${retry_count}): ${error}",376 elog("Get endpoint object(${name}) failed, retry...(${retry_count}): ${error}",
209 ("name", m_mctp_name)("retry_count", retry_count)("error", e.what()));377 ("name", m_mctp_name)("retry_count", retry_count)("error", e.what()));
210- // 先摘除上一轮到期的重试 timer(stop 触发框架钩子从 children 强引用摘链),378+ mc::timer_ptr previous;
211- // 否则失败窗口内每轮重试净滞留一个 timer,并把本 mctp 子树续命379+ {
212- if (m_retry_timer) {380+ std::lock_guard<std::mutex> lock(state->mutex);
213- m_retry_timer->stop();381+ if (state->cancelled || state->generation != generation) {
214- }382+ return;
215- m_retry_timer = mc::timer::single_shot(mc::milliseconds(1000), this, [this, alive, retry_count, cb]() {
216- // alive 为 false 说明 mctp(及其调用方)可能已析构,cb 通常捕获调用方裸指针/[this],
217- // 此时绝不能再调用,否则将对已释放/复用内存做虚函数调用(UAF)。
218- if (alive->load()) {
219- try_get_endpoint_object(retry_count + 1, cb);
220 }383 }
221- });384+ // 与取消原子地发布下一轮;不能先在锁外启动,再把句柄写回已取消的链。
385+ auto next = mc::make_shared<mc::timer>(this);
386+ next->timeout.connect([this, state, generation, retry_count, cb]() {
387+ endpoint_retry_activity activity(state, generation);
388+ if (activity) {
389+ try_get_endpoint_object_impl(retry_count + 1, cb, state, generation);
390+ }
391+ });
392+ next->set_single_shot(true);
393+ previous = std::move(m_retry_timer);
394+ m_retry_timer = std::move(next);
395+ m_retry_timer->start(mc::milliseconds(1000));
O

💡 建议:m_retry_timer->start() 在持有 state->mutex 期间被调用,与其余代码主动规避锁内调用框架方法的原则不一致

本函数其它分支刻意把 timer->stop() / disconnect_all() 放到锁外(见注释“不能持有状态锁调用 stop”),但异常路径的重装块却在 std::lock_guard 作用域内直接执行 m_retry_timer->start(mc::milliseconds(1000))。若框架计时器 start() 在单线程 worker 上同步触发派发或回调,可能重入 try_get_endpoint_object_impl / cancel_endpoint_retry,进而再次请求同一把锁造成死锁。

修复建议:仿照下方 previous->stop() 的处理,把 next->start(...) 移到锁外(例如先持锁完成 previous/m_retry_timer 句柄交接,解锁后再 start),保持“状态锁内不做可能推进事件循环的同步调用”的统一约定。

likedislike
396+ }
397+ if (previous) {
398+ previous->timeout.disconnect_all();
399+ previous->stop();
400+ }
222 } else {401 } else {
223 elog("Get endpoint object(${name}) failed: ${error}", ("name", m_mctp_name)("error", e.what()));402 elog("Get endpoint object(${name}) failed: ${error}", ("name", m_mctp_name)("error", e.what()));
224- if (cb) {403+ if (cb && state->is_current(generation)) {
225 cb();404 cb();
226 }405 }
227 }406 }
@@ -230,6 +409,14 @@ void mctp::try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb
230 409 
231void mctp::create_transport_and_endpoint(std::string module_name, std::function<void()> cb)410void mctp::create_transport_and_endpoint(std::string module_name, std::function<void()> cb)
232{411{
412+ // 新一次显式启动才重新开放查询;旧回调持有的 generation 永远不能恢复有效。
413+ cancel_endpoint_retry();
414+ {
415+ std::lock_guard<std::mutex> lock(m_endpoint_retry_state->mutex);
416+ m_endpoint_retry_state->cancelled = false;
417+ ++m_endpoint_retry_state->generation;
418+ }
419+ m_create_endpoint_success = false;
233 uint8_t retry_count = 0;420 uint8_t retry_count = 0;
234 mc::dict transport_interface = {421 mc::dict transport_interface = {
235 {"ServiceName", module_name},422 {"ServiceName", module_name},
@@ -12,7 +12,6 @@
12#ifndef DEV_MCTP_H12#ifndef DEV_MCTP_H
13#define DEV_MCTP_H13#define DEV_MCTP_H
14 14 
15-#include <atomic>
16#include <mc/object.h>15#include <mc/object.h>
17#include <mc/timer.h>16#include <mc/timer.h>
18#include <mctp/pcie_transport.h>17#include <mctp/pcie_transport.h>
@@ -59,7 +58,15 @@ public:
59 mctp(mc::engine::abstract_object* parent, uint16_t phy_addr, MCTP_MESSAGE_TYPE msg_type, std::string position);58 mctp(mc::engine::abstract_object* parent, uint16_t phy_addr, MCTP_MESSAGE_TYPE msg_type, std::string position);
60 ~mctp();59 ~mctp();
61 60 
62- void create_transport_obj(mc::dict& object);61+ void create_transport_obj(mc::dict& object);
62+ // 先取消并排空端点查询/回调,再停止依赖它的业务 timer,最后注销 transport。
63+ // 返回 true 后调用方可清理消费者;同线程嵌套不能等待当前调用栈,返回 false 并延后清理。
64+ // deferred_cleanup 是消费者清理函数体;true 时由调用方执行,false 时由最后活动锁外执行。
65+ // 不传清理函数时仅取消旧代;非 mc 管理对象仍须由调用方保持有效,不能在回调内直接 delete。
66+ // 显式 create/start 与 stop/destroy 仍须由调用方串行发起。
67+ bool cancel_endpoint_retry(std::function<void()> deferred_cleanup = {});
68+ // 保持同步注销,不保证同一调用栈内的业务回调已经结束;消费者必须先 cancel(cleanup)。
69+ // 具体驱动迁移由后续 PR 完成;提供此入口不代表所有消费者已按该顺序排空。
63 void destroy_transport_obj();70 void destroy_transport_obj();
64 void try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb);71 void try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb);
65 void create_transport_and_endpoint(std::string module_name, std::function<void()> cb);72 void create_transport_and_endpoint(std::string module_name, std::function<void()> cb);
@@ -82,18 +89,23 @@ public:
82 }89 }
83 90 
84private:91private:
85- uint16_t m_phy_addr;92+ struct endpoint_retry_state;
86- MCTP_MESSAGE_TYPE m_msg_type;93+ class endpoint_retry_activity;
87- std::string m_position;94+ void destroy_transport_obj_impl();
88- std::string m_mctp_name;95+ void try_get_endpoint_object_impl(uint8_t retry_count, const std::function<void()>& cb,
89- std::string m_transport_path;96+ const std::shared_ptr<endpoint_retry_state>& state, uint64_t generation);
90- std::string m_endpoint_path;97+ 
91- mc::shared_ptr<transport_object> m_transport_obj;98+ uint16_t m_phy_addr;
92- bool m_create_endpoint_success = false;99+ MCTP_MESSAGE_TYPE m_msg_type;
93- mc::engine::abstract_object* m_parent;100+ std::string m_position;
94- std::shared_ptr<std::atomic<bool>> m_alive; // 共享存活标志,异步回调通过共享副本安全检测对象是否已销毁101+ std::string m_mctp_name;
95- mc::timer_ptr m_retry_timer; // 重试链句柄:发起下一轮前先 stop 摘链,102+ std::string m_transport_path;
96- // 否则到期 timer 永久滞留 children(强引用)103+ std::string m_endpoint_path;
104+ mc::shared_ptr<transport_object> m_transport_obj;
105+ bool m_create_endpoint_success = false;
106+ mc::engine::abstract_object* m_parent;
107+ std::shared_ptr<endpoint_retry_state> m_endpoint_retry_state;
108+ mc::timer_ptr m_retry_timer; // 句柄发布、替换与取消统一由 retry_state 的 mutex 保护。
97};109};
98 110 
99} // namespace dev111} // namespace dev
@@ -3,7 +3,7 @@
3 "type": "application",3 "type": "application",
4 "language": "cpp",4 "language": "cpp",
5 "deployConfig": "",5 "deployConfig": "",
6- "version": "1.2.579",6+ "version": "1.2.580",
7 "license": "",7 "license": "",
8 "description": "",8 "description": "",
9 "options": {9 "options": {
@@ -0,0 +1,295 @@
1+# MCTP 端点重试的取消与生命周期
2+ 
3+公共实现位于 `libraries/mctp/mctp.h` 和 `libraries/mctp/mctp.cpp`。本次修改解决三个相连的问题:端点查询的旧回调可能在停止期间重新启动业务 timer;取消仅等待当前 timer,不能覆盖初次同步查询及已经替换 timer 句柄的活动调用;框架同步等待可能在同一线程推进独立 stop,不能把同线程都当作允许立即清成员的回调自身重入。本文的“宿主”指持有 MCTP 和协议接口的设备对象。消费者清理保证适用于采用 `cancel_endpoint_retry(cleanup)` 的调用方;不能把公共接口的修改解释为所有厂商驱动都已完成生命周期整改。
4+ 
5+## 1. 原来的停止顺序为何不够
6+ 
7+修改前的 `mctp::destroy_transport_obj()` 开头是实际代码:
8+ 
9+```cpp
10+if (m_retry_timer) {
11+ m_retry_timer->stop();
12+ m_retry_timer.reset();
13+}
14+```
15+ 
16+`m_retry_timer` 是项目封装 `mc::shared_ptr<mc::timer>` 的成员句柄。`->stop()` 调用项目定时器的停止函数;`reset()` 只释放该成员持有的一份引用。排队任务或其他对象可能仍持有 timer,不能把 `reset()` 等同于 C 的“立即 free 定时器及其所有回调”。当前框架 `stop()` 会等待其他线程已经进入的该 timer 回调,但不会替调用方查找其他 timer,也不知道哪次同步查询还在运行。
17+ 
18+修改前的异常路径会替换该成员,节选如下:
19+ 
20+```cpp
21+if (m_retry_timer) {
22+ m_retry_timer->stop();
23+}
24+m_retry_timer = mc::timer::single_shot(mc::milliseconds(1000), this,
25+ [this, alive, retry_count, cb]() {
26+ if (alive->load()) {
27+ try_get_endpoint_object(retry_count + 1, cb);
28+ }
29+ });
30+```
31+ 
32+`single_shot` 是项目函数,创建并启动一次性 timer。`[this, alive, retry_count, cb]` 是 C++ lambda 的值捕获清单:框架保存的回调对象会复制这四项。`this` 只复制对象地址,类似 C 回调保存的 `void *ctx`;`cb` 是保存业务函数及其捕获内容的 `std::function`。`alive` 的标准库共享指针只延长标志内存寿命,不能延长 `this` 指向对象的寿命,也不能排空已经越过标志检查的回调。
33+ 
34+初次 `try_get_endpoint_object()` 会直接查询并调用 `cb()`,此时可以完全没有重试 timer。另一方面,某个 timer 的回调可能已创建下一只 timer,当前成员句柄因此不再代表所有活动调用。
35+ 
36+设备清理的错误顺序可写成下面的 C 教学伪代码:
37+ 
38+```c
39+stop_all_consumer_timers(card);
40+stop_endpoint_retry(card->mctp); /* 里面的 callback 仍可能再次 start 消费者 */
41+unregister_transport(card->mctp);
42+```
43+ 
44+```text
45+清理线程:停止业务 timer ───────── 等待端点回调 ── 注销 transport ── 返回
46+回调线程: 创建新业务 timer ── 返回
47+ ↑ 停止完成后仍可能留下新 timer
48+```
49+ 
50+这个时序支持“业务 timer 被重新启动”的判断;由于清理线程随后等待回调返回,不能再据此断言等待之后的 vector 清空与同一个已结束回调并发。未复现的业务崩溃、端口状态变化也不能由这张图直接归因。
51+ 
52+## 2. 先登记访问,再允许清理等待
53+ 
54+当前共享状态的核心成员如下,省略了方法体:
55+ 
56+```cpp
57+struct mctp::endpoint_retry_state {
58+ std::mutex mutex;
59+ mc::runtime::condition_variable finished;
60+ uint64_t generation{0};
61+ bool cancelled{false};
62+ size_t active{0};
63+ std::unordered_map<std::thread::id, size_t> active_threads;
64+ std::vector<std::function<void()>> deferred_cleanups;
65+ bool cleanup_running{false};
66+ std::thread::id cleanup_thread;
67+ // 另有首次调用建立的 weak self/parent 与 managed 状态,见下文。
68+};
69+```
70+ 
71+可按 C 的 `struct retry_state` 理解:`mutex` 保护状态、活动计数及 `m_retry_timer` 的发布/移出;`generation` 是本轮查询编号;`cancelled` 表示这轮已停止;`active` 是已经获准访问 MCTP、尚未返回的调用数量。线程计数表用于识别当前线程是否正在查询或回调中,防止重入取消等待自己。它不是 MCTP 所有业务字段的通用锁。
72+ 
73+`std::mutex` 是标准库互斥量。`mc::runtime::condition_variable` 是项目等待工具,框架 worker 等待时可以继续推进运行时事件;不能不加分析就换成阻塞该 worker 的普通等待。调用 `wait(lock, predicate)` 时会释放传入的锁,让其他线程完成工作;醒来后重新持锁检查条件。
74+ 
75+当前 timer lambda 的关键代码是:
76+ 
77+```cpp
78+[this, state, generation, retry_count, cb]() {
79+ endpoint_retry_activity activity(state, generation);
80+ if (activity) {
81+ try_get_endpoint_object_impl(retry_count + 1, cb, state, generation);
82+ }
83+}
84+```
85+ 
86+`state` 是 `std::shared_ptr<endpoint_retry_state>`:复制它保留的是独立同步状态,而不是宿主对象。`endpoint_retry_activity` 的构造函数在状态锁内确认代次未失效,再为原本受框架管理的 MCTP 和宿主取得强引用,成功后增加活动计数;`if (activity)` 调用该类型的布尔转换,判断登记是否成功。只有成功之后才通过 `this` 调用成员函数,因此晚到回调可以只读取仍有效的共享状态并返回。
87+ 
88+`activity` 是栈上的 C++ 对象。离开作用域时自动执行其析构函数,在锁内减少计数并通知等待者;正常返回和 C++ 异常展开均经过这里。这种模式常称 RAII,在这里相当于 C 中每个返回分支都必须走同一个 `out:` 清理出口。当前 guard 还持有本次活动的 MCTP 和宿主强引用,直到延后清理结束才释放;它不单独保证线程安全,业务成员的清理仍须服从取消和排空协议。
89+ 
90+当前保活的关键实际代码是:
91+ 
92+```cpp
93+m_self = m_state->self.lock();
94+m_parent = m_state->parent.lock();
95+if ((m_state->managed_self && !m_self) || (m_state->managed_parent && !m_parent)) {
96+ return;
97+}
98+```
99+ 
100+这里是项目的 `mc::weak_ptr` 和 `mc::object_ptr`,不是 `std::weak_ptr`/`std::shared_ptr`。弱引用只允许稍后尝试取得所有权,不长期阻止设备析构;`lock()` 成功才获得一份强引用。首次有效 `try_get_endpoint_object()` 在调用者保证对象有效的入口记录 `is_managed()`,只为原本受 mc 管理的对象建立弱引用。以后 timer 到期从独立 state 取得这些引用,不先解引用可能已失效的裸 `this`。如果原本 managed 的对象已无法 lock,必须拒绝活动,不能当成未托管对象继续访问。
101+ 
102+用 C 表示,可把对象强引用理解为已有引用计数系统的 `object_get_if_alive(ctx)` / `object_put(ctx)`;不是向任意 `void *ctx` 新加一套所有权。栈对象和 `unique_ptr` 对象不因此变成受 mc 管理,调用者仍负责使其活到全部查询、回调及清理结束。
103+ 
104+对应的 C 教学伪代码如下,省略线程登记和异常处理:
105+ 
106+```c
107+lock(state->mutex);
108+if (state->cancelled || state->generation != captured_generation)
109+ goto reject;
110+self_ref = lock_weak(state->self);
111+owner_ref = lock_weak(state->owner);
112+if ((state->managed_self && !self_ref) ||
113+ (state->managed_owner && !owner_ref))
114+ goto reject;
115+++state->active;
116+unlock(state->mutex);
117+ 
118+query_and_maybe_call_user(ctx); /* 不持状态锁;强引用保内存,排空协议保业务成员 */
119+ 
120+lock(state->mutex);
121+--state->active;
122+if (!state->active && state->cleanup_queue_not_empty) {
123+ state->cleanup_running = true;
124+ take_cleanup_queue(state, &local_queue);
125+}
126+unlock(state->mutex);
127+run_cleanup_queue_without_lock(&local_queue);
128+/* 实际实现还处理清理期间新排队的工作,再清 cleanup_running 并通知等待者。 */
129+object_put(self_ref);
130+object_put(owner_ref);
131+return;
132+reject:
133+unlock(state->mutex);
134+/* 释放可能已取得的引用,不调用业务函数。 */
135+```
136+ 
137+初次同步查询也使用同样的 guard,不能只保护 timer lambda。保活只是保证对象内存存在,不保证设备仍注册在 service 中,也不允许继续使用已经被其他停止路径清空的成员。
138+ 
139+## 3. 取消与重装在同一处交接
140+ 
141+`cancel_endpoint_retry()` 先在锁内执行以下实际操作:
142+ 
143+```cpp
144+state->cancelled = true;
145+++state->generation;
146+timer = std::move(m_retry_timer);
147+reentrant = state->active_threads.count(std::this_thread::get_id()) != 0;
148+```
149+ 
150+前两行让捕获旧编号的调用失效。`std::move` 在此把共享句柄从成员转移到局部变量,类似 C 中取走一个指针并将原槽清空;不是移动 timer 对象的内存,也不是停止动作。它发生在与重装相同的锁内,因此取消者能取得已发布的 timer,重装者也无法无条件把新句柄写回已取消的链。
151+ 
152+非重入取消随后在锁外断开 timer 槽、调用 `stop()`,最后等待:
153+ 
154+```cpp
155+std::unique_lock<std::mutex> lock(state->mutex);
156+state->finished.wait(lock, [&state]() {
157+ return state->active == 0 && !state->cleanup_running;
158+});
159+```
160+ 
161+最后的等待覆盖全部登记活动,包含初次同步查询、已替换句柄的旧 timer,以及已经接管的延后清理。不能在 `active` 刚减到零时就允许外部 stop 返回,因为延后清理此时可能仍在停止消费者或释放协议。停止期间用户回调可能已经进入;取消不会强行中止它,而是等待它返回。调用方必须允许回调依赖的 I/O 继续推进。
162+ 
163+异常路径准备重装时,先持同一把锁检查:
164+ 
165+```cpp
166+if (state->cancelled || state->generation != generation) {
167+ return;
168+}
169+// 持锁创建、连接、发布并启动下一轮 timer。
170+// 旧 timer 移到局部变量,解锁后才 stop/disconnect。
171+```
172+ 
173+查询、用户 `cb()` 和旧 timer 的 `stop()` 都不跨越状态锁,否则它们返回时所需的活动登记清理可能被这把锁阻塞。新的显式 `create_transport_and_endpoint()` 先取消旧链,再开启新编号;普通 `try_get_endpoint_object()` 不自动解除取消。只用一个布尔值不够:新 create 已把布尔值重新开放时,旧调用栈仍可能异常返回,必须靠编号拒绝其重装。
174+ 
175+## 4. 调用方必须先停启动者
176+ 
177+调用方把原有停止函数体提取为不再调用外层取消入口的 cleanup 函数,再由局部 lambda 调用;下面内联该函数体,是去除设备字段差异后的教学代码:
178+ 
179+```cpp
180+auto cleanup = [this]() {
181+ stop_consumer_timers();
182+ release_protocol_dependencies();
183+ if (m_mctp_object) {
184+ m_mctp_object->destroy_transport_obj();
185+ m_mctp_object.reset();
186+ }
187+};
188+if (m_mctp_object && !m_mctp_object->cancel_endpoint_retry(cleanup)) {
189+ return; // 本次清理已入队,不能在这里再次执行。
190+}
191+cleanup(); // true 表示当前线程已经排空,可由调用方执行原函数体。
192+```
193+ 
194+`[this]` 只复制设备地址,类似 C 的 `void *ctx`;这里的有效寿命来自活动 guard 持有的宿主引用,不能单靠 lambda 捕获保证。`cancel_endpoint_retry(cleanup)` 接收 `std::function<void()>`,相当于把 C 的函数指针和 ctx 打包保存。返回 `true` 时接口不会调用传入函数,由调用方执行一次;返回 `false` 时函数已排队,最后一个活动退出后执行。无 MCTP 时照常直接清理。
195+ 
196+普通跨线程路径:
197+ 
198+```text
199+清理线程:取消旧代 ── 等待活动及延后清理 ── true ── 清理消费者/注销
200+回调线程:已进入的回调完成 ── 返回
201+晚到回调:检查旧代失败 → 返回,不使用 this
202+失败重装:检查旧代失败 → 返回,不发布新 timer
203+```
204+ 
205+restart 必须把“清理旧协议 → 建立新协议”整个序列交给同一取消判断,不能仍写成 `stop_protocol(); start_protocol();`:stop 可能因为嵌套等待返回 `false` 而尚未清理,此时立刻 start 会与旧清理交错。教学代码如下:
206+ 
207+```cpp
208+auto restart = [this]() {
209+ cleanup_protocol(); // 已排空后的函数体,不重新调用外层 stop/cancel。
210+ start_protocol();
211+};
212+if (m_mctp_object && !m_mctp_object->cancel_endpoint_retry(restart)) {
213+ return;
214+}
215+restart();
216+```
217+ 
218+`create_transport_and_endpoint()` 本身只开启新代,不会根据取消的 `false` 自动把启动操作排队。已有“回调内显式 create 使旧代失效”的测试只验证代次,不等于任意设备 restart 都能直接这样调用。设备必须在上面的完整 transition 中安排清理与启动,不能跳过尚未执行的消费者清理。
219+ 
220+不能机械地在所有 `stop_update_task()` 中增加取消:某些业务回调本来就调用这些局部停止函数来替换旧消费者,错误放置会取消正在正常建立的协议。`destroy_transport_obj()` 保持旧有同步注销契约,并不替旧调用方延后整个业务清理;安全入口是消费者停止之前的 `cancel_endpoint_retry(cleanup)`。析构中的取消也不是允许任意线程直接 `delete` 的协议。
221+ 
222+## 5. 同线程嵌套、延后清理和注册身份
223+ 
224+框架 `mc::future::get()` 在 worker 上等待同步请求时可以推进池里的其他任务。因此下面不是“业务回调自己调用 stop”,而是独立的 stop 被嵌套调度到同一 OS 线程。真实框架 probe 已复现原先 `void cancel_endpoint_retry()` 过早返回;没有通过释放非法对象来制造崩溃。
225+ 
226+```text
227+同一线程调用栈:
228+ 端点业务回调 → 同步请求等待 → 推进独立 stop
229+ │
230+旧行为:按 thread::id 认作重入 → cancel 返回 → 清空消费者/协议
231+ │
232+ stop 返回 → 请求完成 → 原业务回调继续使用成员/启动 timer
233+ 
234+新行为:标记旧代失效 → 将 cleanup(ctx) 入队 → false,stop 不清成员
235+ │
236+ stop 返回 → 请求完成 → 原业务回调自然结束
237+ │
238+ 最后 activity → 锁外 cleanup → 释放保活引用
239+```
240+ 
241+该独立 stop 不能在内层等待外层调用栈退出,否则会等待自己。代次检查能拦截查询返回后的旧工作,但业务 `cb()` 已进入并在其中再次同步等待时,不能靠检查代次撤销它;此时必须保留依赖,等整个 cb 返回再清理。
242+ 
243+`active_threads` 只用于识别“当前线程不能同步排空”的情形,不声称识别了某个唯一业务任务。实现还记录 `cleanup_running` 和 `cleanup_thread`:延后清理期间的同步等待若再次推进独立 stop,带清理函数的取消仍排队并返回 `false`。执行队列不持有状态锁;最后一个活动保留对象引用,待队列全部处理完才清除 cleanup 状态、通知外部等待者。传入的函数应是实际清理函数体,不能重新调用外层 stop,否则会不断重新入队。
244+ 
245+不传清理函数的取消仍可用于只使旧代失效的场景;同线程返回 `false` 不代表可以立即销毁对象。显式 start/create 与 stop/destroy 仍须由调用方串行发起,这不是任意并发重建协议的接口。非 mc 管理对象仍由调用者负责有效寿命;直接销毁栈对象、重置 `unique_ptr` 或手工 `delete` 不会被这个 guard 自动延后。清理函数异常会被记录,不能把异常日志当成清理成功。
246+ 
247+`destroy_transport_obj()` 保持原有同步注销时机,实际代码为:
248+ 
249+```cpp
250+void mctp::destroy_transport_obj()
251+{
252+ cancel_endpoint_retry();
253+ destroy_transport_obj_impl();
254+}
255+```
256+ 
257+这里无回调参数的取消在同线程仍可能返回 `false`,但旧的 void destroy 接口继续同步注销。它不等于“消费者已经排空”,也不承诺让原本有并发清理问题的旧驱动自动安全。本轮整改的 Broadcom、Emulex、Huawei NVMe 和 Samsung NVMe 必须先调用带完整 cleanup 的取消入口,只有排空后才在清理体中停止消费者、释放协议依赖、调用 destroy。
258+ 
259+保持同步注销也有兼容原因:其他旧调用方仍可能在 destroy 返回后立即重建相同路径。若仅把 destroy 改为异步排队,新对象会发现旧路径仍在而跳过注册;随后旧队列又删除该路径,导致新协议没有自己的注册对象。兼容回归分别检查 destroy 后表项数为 0、重建后为 1、旧 callback 返回后仍为 1;另一个用例验证新调用方的延后 cleanup 内执行同样的注销与重建顺序。
260+ 
261+还有一个独立的生命周期细节:框架注销宿主时会执行 `set_service(nullptr)`,强引用只保内存,不保注册身份。原先 `destroy_transport_obj()` 通过 `m_parent->get_service()` 注销独立 transport,延后清理时可能已取不到服务。因此现在优先使用 transport 自己的注册服务:
262+ 
263+```cpp
264+auto service = m_transport_obj->get_service();
265+if (service == nullptr && m_parent != nullptr) {
266+ service = m_parent->get_service();
267+}
268+```
269+ 
270+`m_transport_obj` 是另外注册的对象,未挂在宿主的 engine 子对象树中。它自身的 `get_service()` 才能在宿主注销后仍定位应移除的对象表;正常停止时的宿主服务保留为后备。
271+ 
272+## 6. 回归及证据范围
273+ 
274+`tests/libraries/mctp/test_mctp.cpp` 中的 `endpoint_cancel_*` 使用生产 MCTP 查询逻辑和真实框架 timer,测试服务提供端点响应,不访问实际硬件。用例覆盖:
275+ 
276+| 用例后缀 | 行为 |
277+| --- | --- |
278+| `drains_initial_callback_before_stopping_consumer` | 初次同步回调阻塞时清理必须等待,随后清理真实消费者 timer |
279+| `from_callback_prevents_exception_rearm` | 首次回调自身取消后抛异常,不得重新创建 retry |
280+| `is_idempotent_and_explicit_create_reopens_query` | 重复取消、保留旧 timer、普通查询不复活、显式 create 可复用 |
281+| `old_generation_cannot_rearm_after_reentrant_create` | 新代已启动时,旧调用栈的异常仍不能重装 |
282+| `drains_retry_callback_and_prevents_exception_rearm` | 真正 retry timer 到期进入阻塞回调,外部取消等待,随后异常不得重装 |
283+| `from_retry_callback_detaches_pending_timer` | 真实 retry 回调自身取消时断槽、摘链且不自等待 |
284+| `independent_stop_nested_on_same_worker` | 真实单 worker 的等待推进独立 stop,清理必须在 cb 结束后执行 |
285+| `waits_for_deferred_cleanup_and_queues_nested_cleanup` | 清理期间的第二个嵌套 stop 排队,外部取消等待整个清理队列 |
286+| `endpoint_activity_keeps_managed_owner_and_transport_until_cleanup`(完整用例名) | 注销宿主并释放最后外部引用后,活动及清理仍可访问对象,最终引用可释放 |
287+| `destroy_transport_after_owner_unregister_removes_independent_registration`(完整用例名) | 宿主 service 已清空仍能注销独立 transport |
288+| `deferred_cleanup_unregisters_transport_before_same_path_restart`(完整用例名) | 延后清理内同步注销旧 transport,再建同路径对象不会被旧队列删除 |
289+| `destroy_inside_callback_preserves_synchronous_registration_cleanup`(完整用例名) | 旧接口在活动 callback 内仍同步注销,随后同路径重建的注册保留;不宣称旧消费者已安全 |
290+ 
291+前两条在未修改公共实现时已经执行并得到预期 RED:旧清理过早返回,旧异常路径又创建 retry。同线程独立 stop 的 probe 也已在改为延后清理之前得到预期 RED;同路径重建和旧接口兼容用例在 destroy 仍追加到清理队尾时同样得到预期 RED(旧注册未立即移除、旧队列结束后新路径消失)。其他用例是新增覆盖,是否 GREEN 必须依据本轮实际运行记录,不把“已编写”写成“已通过”。测试中保留旧 timer 后直接触发其信号,只验证旧槽已拆除;真实调度验证由另外两条 retry 到期用例承担。
292+ 
293+设备入口测试可以将受控业务回调放入真实 timer,以单独检验“端点取消在消费者停止之前”的顺序;它们不能替代公共查询代码的代次测试。协议编码测试中的 MCTP 替身没有真正的查询链,取消接口直接返回 `true` 且不调用传入清理函数,不能作为生产取消逻辑通过的证据。
294+ 
295+这些用例不等于完整热拔插、固件升级、长时间压力测试或全部驱动动态覆盖。提交对应的测试日志、所用框架库及实际目标应随验证记录保存;本说明不固定会随修订变化的通过总数。
@@ -10,12 +10,18 @@
10 * See the Mulan PSL v2 for more details.10 * See the Mulan PSL v2 for more details.
11 */11 */
12 12 
13+#include <atomic>
13#include <chrono>14#include <chrono>
15+#include <future>
14#include <mc/engine/base.h>16#include <mc/engine/base.h>
17+#include <mc/future.h>
18+#include <mc/runtime/thread_pool.h>
15#include <mctp/mctp.h>19#include <mctp/mctp.h>
20+#include <stdexcept>
16#include <test_utilities/test_base.h>21#include <test_utilities/test_base.h>
17#include <thread>22#include <thread>
18#include <gtest/gtest.h>23#include <gtest/gtest.h>
24+#include "../../../drivers/protocol_lifecycle.h"
19#include "Endpoint.h"25#include "Endpoint.h"
20#include "test_mctp_class.h"26#include "test_mctp_class.h"
21 27 
@@ -52,6 +52,12 @@ mctp::~mctp() = default;
52void mctp::create_transport_obj(mc::dict& /*object*/)52void mctp::create_transport_obj(mc::dict& /*object*/)
53{}53{}
54 54 
55+// 协议替身同步执行回调,不创建端点重试;取消语义由链接真实 MCTP 的用例验证。
56+bool mctp::cancel_endpoint_retry(std::function<void()> /*deferred_cleanup*/)
57+{
58+ return true;
59+}
60+ 
55void mctp::try_get_endpoint_object(uint8_t /*retry_count*/, std::function<void()> /*cb*/)61void mctp::try_get_endpoint_object(uint8_t /*retry_count*/, std::function<void()> /*cb*/)
56{}62{}
57 63 
@@ -43,6 +43,16 @@ mctp::~mctp() = default;
43void mctp::create_transport_obj(mc::dict& /*object*/)43void mctp::create_transport_obj(mc::dict& /*object*/)
44{}44{}
45 45 
46+// 与模拟构造和创建路径对应:没有注册真实传输对象。
47+void mctp::destroy_transport_obj()
48+{}
49+ 
50+// 协议替身同步执行回调,不创建端点重试;取消语义由链接真实 MCTP 的用例验证。
51+bool mctp::cancel_endpoint_retry(std::function<void()> /*deferred_cleanup*/)
52+{
53+ return true;
54+}
55+ 
46void mctp::try_get_endpoint_object(uint8_t /*retry_count*/, std::function<void()> /*cb*/)56void mctp::try_get_endpoint_object(uint8_t /*retry_count*/, std::function<void()> /*cb*/)
47{}57{}
48 58 
@@ -41,6 +41,12 @@ mctp::~mctp() = default;
41void mctp::create_transport_obj(mc::dict& /*object*/)41void mctp::create_transport_obj(mc::dict& /*object*/)
42{}42{}
43 43 
44+// 协议替身同步执行回调,不创建端点重试;取消语义由链接真实 MCTP 的用例验证。
45+bool mctp::cancel_endpoint_retry(std::function<void()> /*deferred_cleanup*/)
46+{
47+ return true;
48+}
49+ 
44void mctp::try_get_endpoint_object(uint8_t /*retry_count*/, std::function<void()> /*cb*/)50void mctp::try_get_endpoint_object(uint8_t /*retry_count*/, std::function<void()> /*cb*/)
45{}51{}
46 52