Open
fix: 路由重绑定向撤销旧事务并恢复端点 #15
liuxingsenAdd on 13 days ago
fix: 路由重绑定向撤销旧事务并恢复端点 #15
Open
liuxingsenAdd on 13 days ago
Total: 16 files changed+841-40
@@ -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.130.20] - 2026-10-27
4+路由变化定向撤销旧端点和事务,修复两卡交换EID的登记串改、驱动失败后的恢复、MDB订阅及物理迁移;与共享传输/回复上下文/有界发送配套。
5+ 
3## [1.130.15] - 2026-08-256## [1.130.15] - 2026-08-25
4更新版本号,触发流水线推包7更新版本号,触发流水线推包
5 8 
@@ -0,0 +1,15 @@
1+# MCTP路由与订阅重绑
2+ 
3+当同一物理地址的EID、范围、介质或其他路由字段变化时,原路由表只比较物理地址,未发布更新。现在比较完整平坦字段,先删除旧路由再添加新路由;同值轮询保持幂等,路由删除同步移除MDB对象缓存。
4+ 
5+`upd_mdb_endpoint`原来给四参数订阅函数传三个错位参数,并无条件调用尚未建立的signal。现在传递system_id、engine、MDB对象和app,允许初始订阅尚未完成;更新ep_info,使trace定位新的EID。旧订阅断开后绑定新端点;延迟执行的初始订阅不能再次绑定,避免一条通知重复投递。删除时标记失效、断开已存在订阅并清理诊断映射。
6+ 
7+路由撤销先退休对应端点,终结查询等待、重组、响应预约和PLDM回复上下文,再恢复新路由的各协议登记;共享发送队列用端点对象身份拒绝旧消息。物理地址迁移同步重建MDB路径。驱动配置失败时标记未就绪,随已有路由轮询重试,不保留指向旧地址的订阅。PCIe路由删除/添加采用相同绑定过滤,非PCIe同地址删除不撤销PCIe端点;退休对象的新请求/发送显式抛出MctpEndpointReset。优先匹配物理地址,避免两卡交换EID时旧EID抢先命中另一张卡;协议类型属于登记键,类型变化须独立登记。
8+ 
9+验证覆盖完整字段变化与同值独立对象重放、两卡交换EID的两种路由顺序、初次失败后改EID恢复、多个协议消费者逐个重绑、物理迁移、新旧通知归属、延迟订阅和重复删除。保留两处discovery测试漏传ep_name的修正。
10+ 
11+43000首次真实换址暴露接收路径只按EID查端点,旧物理来源通知会误投给新持有者;已增加PCIe物理地址校验及先失败后通过的回归。正常来源仍保留既有未匹配响应通知语义;配套 qemu_mockup !190 修正NC-SI模板的完整BDF后,43000集成1ba53ad已实测旧来源零误投递、重绑/恢复8条通知正确归属;变化网卡查询3.68秒失效,未变化网卡保持15.01秒正常超时。未注入查询响应。
12+ 
13+边界:早期独立!15只含路由/MDB修复;本轮候选增加上层peer失效接线,组合依赖!11的协议隔离、!13的回复上下文和!14的有界发送。上述实测对应1ba53ad集成候选;后续非PCIe删除守卫、退休错误分类及测试隔离变更已单独回归,不冒充同次实机测试。已经写入硬件的报文不能撤回,过期后的任意迟到无法仅靠有限Tag区分;完整SMBus接口身份、本端整体复位的旧Tag隔离、Control/V2和阶段2仍待完成。版本保持1.130.20,按用户要求暂不合入。
14+ 
15+注册表的add_reg_info固定使用PCIe介质;SMBus发现直接管理自己的端点,不进入该表。PCIe登记更新/删除和路由刷新只恢复对应PCIe身份,不宣称完成跨SMBus接口生命周期支持。新增同地址/EID的SMBus端点回归,确认PCIe登记变更及清表不会撤销它。
@@ -2,7 +2,7 @@
2 "name": "mctpd",2 "name": "mctpd",
3 "type": "application",3 "type": "application",
4 "deployConfig": "framework.service",4 "deployConfig": "framework.service",
5- "version": "1.130.15",5+ "version": "1.130.20",
6 "author": "Huawei",6 "author": "Huawei",
7 "license": "Mulan PSL v2",7 "license": "Mulan PSL v2",
8 "options": {8 "options": {
@@ -160,8 +160,8 @@ function mctpd_app:subscribe_changed_msg(system_id)
160 self.mctp_engines[system_id]:get_eid_table().on_add_reg:on(function(reg)160 self.mctp_engines[system_id]:get_eid_table().on_add_reg:on(function(reg)
161 self.mctp_mdb_mgmts[system_id]:add_mdb_endpoint(reg)161 self.mctp_mdb_mgmts[system_id]:add_mdb_endpoint(reg)
162 end)162 end)
163- self.mctp_engines[system_id]:get_eid_table().on_update_reg:on(function(reg)163+ self.mctp_engines[system_id]:get_eid_table().on_update_reg:on(function(reg, _, previous)
164- self.mctp_mdb_mgmts[system_id]:upd_mdb_endpoint(reg)164+ self.mctp_mdb_mgmts[system_id]:upd_mdb_endpoint(reg, previous)
165 end)165 end)
166 self.mctp_engines[system_id]:get_eid_table().on_del_reg:on(function(reg)166 self.mctp_engines[system_id]:get_eid_table().on_del_reg:on(function(reg)
167 self.mctp_mdb_mgmts[system_id]:del_mdb_endpoint(reg)167 self.mctp_mdb_mgmts[system_id]:del_mdb_endpoint(reg)
@@ -29,29 +29,41 @@ function c_mctp_eid_table.new(system_id)
29 }, c_mctp_eid_table)29 }, c_mctp_eid_table)
30end30end
31 31 
32----查找注册表,找到第一个 phy_addr 或 eid 匹配的条目32+---先匹配物理地址;只有没有该物理登记时才按EID查找迁移项。协议类型是登记键。
33function c_mctp_eid_table:find_reg_info(pid, msg_src_eid, phy_addr, msg_type)33function c_mctp_eid_table:find_reg_info(pid, msg_src_eid, phy_addr, msg_type)
34+ local eid_match
34 for _, reg in ipairs(self.reg_table) do35 for _, reg in ipairs(self.reg_table) do
35- if36+ if reg.pid == pid and msg_type == reg.msg_type then
36- reg.pid == pid37+ if reg.phy_addr == phy_addr then
37- and msg_type == reg.msg_type38+ return reg
38- and (reg.phy_addr == phy_addr or reg.msg_src_eid == msg_src_eid)39+ end
39- then40+ if not eid_match and reg.msg_src_eid == msg_src_eid then
40- return reg41+ eid_match = reg
42+ end
41 end43 end
42 end44 end
43- 45+ return eid_match
44- return nil
45end46end
46 47 
47function c_mctp_eid_table:add_reg_info(process_tag, pid, msg_src_eid, phy_addr, msg_type, module_name)48function c_mctp_eid_table:add_reg_info(process_tag, pid, msg_src_eid, phy_addr, msg_type, module_name)
48 local reg = self:find_reg_info(pid, msg_src_eid, phy_addr, msg_type)49 local reg = self:find_reg_info(pid, msg_src_eid, phy_addr, msg_type)
49 if reg then50 if reg then
50- local need_re_config = (reg.phy_addr ~= phy_addr or reg.msg_src_eid ~= msg_src_eid)51+ local previous = {}
52+ for key, value in pairs(reg) do
53+ previous[key] = value
54+ end
55+ local need_re_config = (
56+ reg.phy_addr ~= phy_addr
57+ or reg.msg_src_eid ~= msg_src_eid
58+ or reg.route_withdrawn == true
59+ or reg.config_failed == true
60+ )
51 local need_update = (need_re_config or (reg.module_name ~= module_name))61 local need_update = (need_re_config or (reg.module_name ~= module_name))
52 if need_re_config then62 if need_re_config then
53 reg.phy_addr = phy_addr63 reg.phy_addr = phy_addr
54 reg.msg_src_eid = msg_src_eid64 reg.msg_src_eid = msg_src_eid
65+ reg.route_withdrawn = nil
66+ reg.ep_status = mctp_def.ENDPOINT_IS_DISCOVERY
55 end67 end
56 if need_update then68 if need_update then
57 reg.module_name = module_name69 reg.module_name = module_name
@@ -63,7 +75,7 @@ function c_mctp_eid_table:add_reg_info(process_tag, pid, msg_src_eid, phy_addr,
63 phy_addr,75 phy_addr,
64 msg_type76 msg_type
65 )77 )
66- self.on_update_reg:emit(reg, need_re_config)78+ self.on_update_reg:emit(reg, need_re_config, previous)
67 end79 end
68 return true80 return true
69 end81 end
@@ -13,6 +13,7 @@ local mctp = require('mctp')
13local network = require('network.core')13local network = require('network.core')
14local signal = require('mc.signal')14local signal = require('mc.signal')
15local mctp_def = require('mctp_def')15local mctp_def = require('mctp_def')
16+local errors = require('mctpd.errors')
16 17 
17local MSG_TAG_MASK <const> = 0x0718local MSG_TAG_MASK <const> = 0x07
18local ROUTE_BY_ID <const> = 0x0219local ROUTE_BY_ID <const> = 0x02
@@ -94,7 +95,7 @@ function c_ep_mctp:request(id, cb, timeout_ms, more_process_cb)
94 local key = self:prepare_mctp_request(id)95 local key = self:prepare_mctp_request(id)
95 -- 暂存本次请求的rsp key,smbus发送时随请求带给service,失败回传后用于完整匹配唤醒96 -- 暂存本次请求的rsp key,smbus发送时随请求带给service,失败回传后用于完整匹配唤醒
96 self.cur_rsp_key = key97 self.cur_rsp_key = key
97- local ret, rsp = self.mctp_engine:request(key, cb, timeout_ms, more_process_cb)98+ local ret, rsp = self.mctp_engine:request(key, cb, timeout_ms, more_process_cb, nil, self)
98 if ret == E_TIMEOUT then99 if ret == E_TIMEOUT then
99 self.req_timeout_count = self.req_timeout_count + 1100 self.req_timeout_count = self.req_timeout_count + 1
100 elseif ret == E_ERROR then101 elseif ret == E_ERROR then
@@ -121,6 +122,9 @@ function c_ep_mctp:send(cb)
121end122end
122 123 
123function c_ep_mctp:prepare_mctp_request(id)124function c_ep_mctp:prepare_mctp_request(id)
125+ if self.retired then
126+ error(errors.mctp_endpoint_reset(), 0)
127+ end
124 local mctp_engine = self.mctp_engine128 local mctp_engine = self.mctp_engine
125 local cnt = 0129 local cnt = 0
126 local mctp_ctx = self.mctp_ctx130 local mctp_ctx = self.mctp_ctx
@@ -20,6 +20,7 @@ local mctp_routing_table = require('mctp_routing_table')
20local _, errors = pcall(require, 'mctpd.errors')20local _, errors = pcall(require, 'mctpd.errors')
21local mctp_lib = require('mctp_lib')21local mctp_lib = require('mctp_lib')
22local signal = require('mc.signal')22local signal = require('mc.signal')
23+local network = require('network.core')
23local c_timer = skynet.timer24local c_timer = skynet.timer
24local c_new_timer = c_timer.new25local c_new_timer = c_timer.new
25local stop_timer = c_timer.stop26local stop_timer = c_timer.stop
@@ -151,11 +152,127 @@ function c_mctp_engine:init()
151 self:config_eid_table(reg)152 self:config_eid_table(reg)
152 end)153 end)
153 154 
154- self.eid_table.on_update_reg:on(function(reg, need_re_config)155+ self.eid_table.on_update_reg:on(function(reg, need_re_config, previous)
155 if need_re_config then156 if need_re_config then
157+ if previous and previous.ep_status ~= mctp_def.ENDPOINT_UN_DISCOVERY then
158+ self:retire_peer(previous.medium_type, network.htons(previous.phy_addr), previous.msg_src_eid)
159+ end
156 self:config_eid_table(reg)160 self:config_eid_table(reg)
157 end161 end
158 end)162 end)
163+ self.eid_table.on_del_reg:on(function(reg)
164+ self:retire_peer(reg.medium_type, network.htons(reg.phy_addr), reg.msg_src_eid)
165+ end)
166+ self.routing_table.on_del_entry:on(function(entry)
O
Oopenubmc-ci-bot13 days ago

⚠️ 警告:路由撤销路径缺少 PCIe 传输类型守卫,存在非 PCIe 路由被错误退休的风险

on_del_entry → retire_peer 的撤销路径没有像 on_add_entry(mctp_engine.lua:174)和 on_entry_updated(mctp_engine.lua:200)那样检查 phys_transport_binding_id == MCTP_OVER_PCIE_ID。它只按 reg.phy_addr == entry.phys_address 匹配即退休 peer 并置 route_withdrawn。

而 on_add_entry/on_entry_updated 的重注册路径都要求 PCIe 绑定,遇到非 PCIe(如 SMBus)路由会提前 return,不进行重注册。

影响:若某个非 PCIe 路由条目恰好与已登记 peer 的 phys_address 相同,删除该路由会退休对应 peer 并置 route_withdrawn=true,但其后同物理地址的路由再添加时因非 PCIe 被跳过,peer 永远停留在撤销/未就绪状态,无法恢复。

修复建议:在 on_del_entry 的循环和 retire_peer 内与 peer 匹配前,同样加入 phys_transport_binding_id == MCTP_OVER_PCIE_ID(或明确的 medium_type 匹配)守卫,保证撤销/重注册的传输过滤条件一致,避免非 PCIe 路由错误触发 PCIe peer 退休。

likedislike
liuxingsen
13 days ago Comments:
167+ if entry.phys_transport_binding_id ~= MCTP_OVER_PCIE_ID then
168+ return
169+ end
170+ for _, reg in ipairs(self.eid_table.reg_table) do
171+ if reg.phy_addr == entry.phys_address and reg.msg_type ~= MCTP_MESSAGE_TYPE_MCTP_CTRL then
172+ self:retire_peer(reg.medium_type, network.htons(reg.phy_addr), reg.msg_src_eid)
173+ end
174+ end
175+ end)
176+ self.routing_table.on_add_entry:on(function(entry)
177+ if entry.phys_transport_binding_id ~= MCTP_OVER_PCIE_ID then
178+ return
179+ end
180+ for _, reg in ipairs(self.eid_table.reg_table) do
181+ if reg.route_withdrawn and reg.phy_addr == entry.phys_address then
182+ local eid = entry.eid_range_size == 1 and entry.starting_eid or reg.msg_src_eid
183+ if eid >= entry.starting_eid and eid < entry.starting_eid + entry.eid_range_size then
184+ self.eid_table:add_reg_info(
185+ reg.process_tag,
186+ reg.pid,
187+ eid,
188+ reg.phy_addr,
189+ reg.msg_type,
190+ reg.module_name
191+ )
192+ end
193+ end
194+ end
195+ end)
196+ self.routing_table.on_entry_updated:on(function()
OO
Oopenubmc-ci-bot13 days ago

⚠️ 警告:撤销(retire)与恢复(restore)对传输介质的守卫不对称,非 PCIe 端点被撤销后可能无法恢复

on_update_reg(155-162) 与 on_del_reg(163-165) 触发 retire_peer 时不区分介质,任何 medium(含 SMBus/非 PCIe)的登记变更都会退休对应端点并置 route_withdrawn=true。

但恢复路径都被 PCIe 绑定限定:

  • on_add_entry(176-179):phys_transport_binding_id ~= MCTP_OVER_PCIE_ID 时直接 return;
  • on_entry_updated(196-217):重试仅当 entry.phys_transport_binding_id == MCTP_OVER_PCIE_ID。

因此若某个 SMBus/非 PCIe 端点的 EID/物理字段变化触发撤销后,其路由再次广播时因介质非 PCIe 会提前返回,route_withdrawn 永不复位、端点一直停在未就绪状态,无法随路由刷新恢复。这与已修复的"on_del_entry 撤销侧 PCIe 守卫"对应的是恢复侧的同类缺口。

修复建议:在 retire_peer 内记录被撤销项的 medium_type(route 重放时一并携带),并在恢复判断中加入介质一致性校验——撤销与恢复对同一介质保持一致(要么都按介质过滤,要么在撤销侧就跳过非 PCIe)。建议补充一条非 PCIe 路由变化后能恢复正常端点状态的单测。

likedislike
liuxingsen
13 days ago Comments:
Oopenubmc-ci-bot13 days ago

⚠️ 警告:撤销(retire)与恢复(restore)对传输介质的守卫不对称,非 PCIe 端点被撤销后可能无法恢复

on_update_reg(155-162) 与 on_del_reg(163-165) 触发 retire_peer 时不区分介质,任何 medium(含 SMBus/非 PCIe)的登记变更都会退休对应端点并置 route_withdrawn=true。

但恢复路径都被 PCIe 绑定限定:

  • on_add_entry(176-179):phys_transport_binding_id ~= MCTP_OVER_PCIE_ID 时直接 return;
  • on_entry_updated(196-217):重试仅当 entry.phys_transport_binding_id == MCTP_OVER_PCIE_ID。

因此若某个 SMBus/非 PCIe 端点的 EID/物理字段变化触发撤销后,其路由再次广播时因介质非 PCIe 会提前返回,route_withdrawn 永不复位、端点一直停在未就绪状态,无法随路由刷新恢复。这与已修复的"on_del_entry 撤销侧 PCIe 守卫"对应的是恢复侧的同类缺口。

修复建议:在 retire_peer 内记录被撤销项的 medium_type(route 重放时一并携带),并在恢复判断中加入介质一致性校验——撤销与恢复对同一介质保持一致(要么都按介质过滤,要么在撤销侧就跳过非 PCIe)。建议补充一条非 PCIe 路由变化后能恢复正常端点状态的单测。

likedislike
liuxingsen
13 days ago Comments:
197+ -- 复用现有路由刷新节奏重试驱动注册;不保留旧地址的通知订阅。
198+ for _, reg in ipairs(self.eid_table.reg_table) do
199+ local entry = self.routing_table:get_entry(reg.phy_addr)
200+ if
201+ reg.config_failed
202+ and entry
203+ and entry.phys_transport_binding_id == MCTP_OVER_PCIE_ID
204+ and reg.msg_src_eid >= entry.starting_eid
205+ and reg.msg_src_eid < entry.starting_eid + entry.eid_range_size
206+ then
207+ self.eid_table:add_reg_info(
208+ reg.process_tag,
209+ reg.pid,
210+ reg.msg_src_eid,
211+ reg.phy_addr,
212+ reg.msg_type,
213+ reg.module_name
214+ )
215+ end
216+ end
217+ end)
218+end
219+ 
220+function c_mctp_engine:retire_peer(medium, address, eid)
221+ local function matches(ep)
222+ return ep and ep.medium_type == medium and ep.phy_addr == address and ep.eid == eid
223+ end
224+ -- 先断开全部旧端点及等待项,再唤醒可能重入的使用者。
225+ for _, endpoints in ipairs({ self.endpoints.endpoints, self.endpoints.smbus_endpoints }) do
226+ for key, ep in pairs(endpoints) do
227+ if matches(ep) then
228+ ep.retired, endpoints[key] = true, nil
229+ end
230+ end
231+ end
232+ local waits = {}
233+ for key, wait in pairs(self.rsp_wait_list) do
234+ if matches(wait.endpoint) then
235+ self.rsp_wait_list[key] = nil
236+ wait.err = errors.mctp_endpoint_reset()
237+ if wait.timer then
238+ local timer = wait.timer
239+ wait.timer = nil
240+ stop_timer(timer)
241+ end
242+ waits[#waits + 1] = wait
243+ end
244+ end
245+ if self.mctp_proto.invalidate_peer then
246+ self.mctp_proto:invalidate_peer(medium, address, eid)
247+ end
248+ local responder = self.pldm_responder
249+ if responder then
250+ for _, entry in pairs(responder.pending) do
251+ if matches(entry.peer) then
252+ responder:remove(entry)
253+ end
254+ end
255+ end
256+ -- 同一网卡的其他协议消费者也必须退就绪;其后逐个重绑不能撤销已更新的端点。
257+ for _, reg in ipairs(self.eid_table.reg_table) do
258+ if
259+ reg.medium_type == medium
260+ and network.htons(reg.phy_addr) == address
261+ and reg.msg_src_eid == eid
262+ and reg.msg_type ~= MCTP_MESSAGE_TYPE_MCTP_CTRL
263+ then
264+ local was_ready = reg.ep_status ~= mctp_def.ENDPOINT_UN_DISCOVERY
265+ reg.route_withdrawn, reg.ep_status = true, mctp_def.ENDPOINT_UN_DISCOVERY
266+ if was_ready then
267+ self.eid_table.on_update_reg:emit(reg, false)
268+ end
269+ end
270+ end
271+ for _, wait in ipairs(waits) do
272+ if wait.co ~= coroutine.running() then
273+ skynet.wakeup(wait.co)
274+ end
275+ end
159end276end
160 277 
161function c_mctp_engine.get_ep_details(msg_key)278function c_mctp_engine.get_ep_details(msg_key)
@@ -416,18 +533,21 @@ function c_mctp_engine:log_mctp_time_out()
416 self.repeat_log_times = (self.repeat_log_times + 1) % 60000533 self.repeat_log_times = (self.repeat_log_times + 1) % 60000
417end534end
418 535 
419-function c_mctp_engine:request(key, cb, timeout_ms, more_process_cb)536+function c_mctp_engine:request(key, cb, timeout_ms, more_process_cb, reservation, endpoint)
420 local wait_info = self.rsp_wait_list[key]537 local wait_info = self.rsp_wait_list[key]
421 if wait_info then538 if wait_info then
422 local err = string.format('[System%s]mctp_engine: request failed, prev request not completed', self.system_id)539 local err = string.format('[System%s]mctp_engine: request failed, prev request not completed', self.system_id)
423 return E_SEND_FAILED, err540 return E_SEND_FAILED, err
424 end541 end
425 542 
426- wait_info = { co = coroutine.running() }543+ wait_info = { co = coroutine.running(), reservation = reservation, endpoint = endpoint }
O
Oopenubmc-ci-bot13 days ago

💡 建议:reservation 参数写入 wait_info 后从未被读取

request() 新增的第 5 个参数 reservation 被存入 wait_info.reservation,但在整个 mctp_engine 及调用方(ep_mctp:request 传入 nil,ep_nvme 未传)中都没有任何读取/使用,属于未使用的死参数字段。

修复建议: 若本 PR 尚未接线该能力,建议移除该参数(或至少不要在 wait_info 里持久化它),避免后续维护者误以为它有实际语义;若计划用于未来的资源预约,请补充注释说明并落地使用。

likedislike
427 self.rsp_wait_list[key] = wait_info544 self.rsp_wait_list[key] = wait_info
428 local ok, err = pcall(cb, key)545 local ok, err = pcall(cb, key)
429- if not ok then546+ if not ok or wait_info.err then
430- self.rsp_wait_list[key] = nil547+ err = wait_info.err or err
548+ if self.rsp_wait_list[key] == wait_info then
549+ self.rsp_wait_list[key] = nil
550+ end
431 log:error('[System%s]mctp_engine: request cb failed, %s', self.system_id, err)551 log:error('[System%s]mctp_engine: request cb failed, %s', self.system_id, err)
432 return E_SEND_FAILED, err552 return E_SEND_FAILED, err
433 end553 end
@@ -441,15 +561,23 @@ function c_mctp_engine:request(key, cb, timeout_ms, more_process_cb)
441 end561 end
442 562 
443 wait_info.timeout_ms = timeout_ms563 wait_info.timeout_ms = timeout_ms
444- wait_info.timer = self:new_timer(wait_info, key)564+ ok, err = pcall(function()
445- 565+ wait_info.timer = self:new_timer(wait_info, key)
446- skynet.wait()566+ skynet.wait()
447- if more_process_cb and type(more_process_cb) == 'function' then567+ if not wait_info.err and not wait_info.is_timeout and type(more_process_cb) == 'function' then
O
Oopenubmc-ci-bot13 days ago

⚠️ 警告:超时/复位时跳过 more_process_cb 会改变 NVMe 响应对账/清理行为

原逻辑在 skynet.wait() 返回后无条件调用 more_process_cb(wait_info, key);现改为仅当 not wait_info.err and not wait_info.is_timeout 时才调用。ep_nvme 的 more_process_cb(ep_nvme.lua:94)内部维护 self.wait_info[key] 的重放/聚合状态,其 while 循环会依据 wait_info.is_need_wait/is_replay 继续 skynet.wait() 并累积 wait_info.rsp。

风险评估:当首段响应落在超时边界(is_timeout 被置位)或端点被 retire_peer 撤销(err 被置)时,该回调被整体跳过,self.wait_info[key] 及其重放标记可能残留,影响下一次同 slot 请求的对账。

建议:确认在超时/撤销路径上 ep_nvme 的重放状态是否需要清理。若需要,建议在 request 退出前尽量保证等价清理逻辑执行,或由 ep_nvme 在捕获到 E_TIMEOUT/E_ERROR 时自行清理 self.wait_info[key]。

likedislike
448- more_process_cb(wait_info, key)568+ more_process_cb(wait_info, key)
569+ end
570+ end)
571+ -- 复位后同键可能已属于新查询,旧协程只能清理自己的等待项。
572+ if self.rsp_wait_list[key] == wait_info then
573+ self.rsp_wait_list[key] = nil
574+ end
575+ if wait_info.timer then
576+ stop_timer(wait_info.timer)
577+ end
578+ if not ok then
579+ return E_ERROR, err
449 end580 end
450- 
451- self.rsp_wait_list[key] = nil
452- stop_timer(wait_info.timer)
453 581 
454 if wait_info.is_timeout then582 if wait_info.is_timeout then
455 self:log_mctp_time_out()583 self:log_mctp_time_out()
@@ -484,6 +612,11 @@ function c_mctp_engine:rx(ctx, data, msg_type, medium_type)
484 local src_eid = mctp_ctx.src612 local src_eid = mctp_ctx.src
485 local ep = self.endpoints:get_endpoint(src_eid, msg_type, medium_type)613 local ep = self.endpoints:get_endpoint(src_eid, msg_type, medium_type)
486 if ep then614 if ep then
615+ -- EID可被另一张卡复用;通知也必须来自该端点当前的物理身份。
616+ if medium_type == mctp_def.PHY_MEDIUM_PCI and ctx:get('remote_phy_addr', 'pcie_vdm_ctx') ~= ep.phy_addr then
O
Oopenubmc-ci-bot13 days ago

💡 建议:PCIe 陈旧来源校验会把「缺少 remote_phy_addr」的合法报文误判为来源不匹配

ctx:get('remote_phy_addr', 'pcie_vdm_ctx') ~= ep.phy_addr 在 remote_phy_addr 未填充(返回 nil)时同样成立,会直接 return true 丢弃该 PCIe 数据报文。现有测试 test_reused_eid... 始终显式写入 physical.remote_phy_addr,未覆盖字段缺失/为空的分支。

修复建议:仅当确实存在来源物理地址且与端点不一致时才丢弃;字段缺失时保留原行为,例如:

local remote = ctx:get('remote_phy_addr', 'pcie_vdm_ctx')
if remote and remote ~= ep.phy_addr then
    ...return true
end

或补充单测覆盖 remote_phy_addr 未设置时正常投递,确认该来源过滤不会误伤合法流量。

likedislike
liuxingsen
13 days ago Comments:
617+ log:debug('[System%s]drop stale PCIe source for eid=%d type=%d', self.system_id, src_eid, msg_type)
618+ return true
619+ end
487 return ep:rx(ctx, data)620 return ep:rx(ctx, data)
488 end621 end
489 622 
@@ -498,7 +631,10 @@ function c_mctp_engine:config_eid_table(reg)
498 reg_info.msg_src_eid = reg.msg_src_eid631 reg_info.msg_src_eid = reg.msg_src_eid
499 reg_info.msg_type = reg.msg_type632 reg_info.msg_type = reg.msg_type
500 if self.hw_pcie_proto:config_eid_table(reg_info) then633 if self.hw_pcie_proto:config_eid_table(reg_info) then
634+ reg.config_failed, reg.ep_status = nil, mctp_def.ENDPOINT_IS_DISCOVERY
L
Lliuxingsen12 days ago

[P2] 按整数返回码判断登记结果,使失败恢复分支可达

驱动登记失败时,本 PR 新增的未就绪标记和路由刷新重试实际上不会执行:hw_pcie_vdm_channel::config_eid_table() 返回 int(成功为 0,失败返回 E_ERROR=-1 或驱动非零错误码),l_mctp.cpp:415 又通过 c_func_wrap 直接把整数交给 Lua;Lua 中 0 和 -1 都是真,因此这里总进入成功分支,清掉 config_failed、把端点标成已发现并创建端点。on_entry_updated 只重试 config_failed 的登记,后续同值路由刷新也不会恢复。当前 SHA 的真实 Lua 函数以 -1/0 返回值做隔离探针时,首次失败后得到 status=1, config_failed=nil, endpoint_exists=true,刷新后驱动调用仍为 1 次;测试中用 false/true 模拟驱动掩盖了该契约。请显式比较成功返回码(或在绑定层明确定义布尔返回),并将失败/恢复回归改为真实数字返回值。这里指出的是本 PR 新增恢复分支不可达;原有裸真假判断不另记一个旧缺陷。

likedislike
501 self.endpoints:create_endpoint(reg.phy_addr, reg.msg_src_eid, reg.msg_type, reg.medium_type, reg.ep_status)635 self.endpoints:create_endpoint(reg.phy_addr, reg.msg_src_eid, reg.msg_type, reg.medium_type, reg.ep_status)
636+ else
637+ reg.config_failed, reg.ep_status = true, mctp_def.ENDPOINT_UN_DISCOVERY
502 end638 end
503end639end
504 640 
@@ -189,6 +189,7 @@ function c_mctp_mdb_mgmt:on_del_routing_entry(entry)
189 return189 return
190 end190 end
191 class_mgnt(obj.class_name):remove(obj)191 class_mgnt(obj.class_name):remove(obj)
192+ self.mdb_routing_entries[phys_address] = nil
192end193end
193 194 
194function c_mctp_mdb_mgmt:monitor_pmu_status()195function c_mctp_mdb_mgmt:monitor_pmu_status()
@@ -378,6 +379,10 @@ function c_mctp_mdb_mgmt:process_transport_register_sig(msg)
378end379end
379 380 
380local function register_endpoint_on_receive_signal(system_id, mctp_engine, obj, app)381local function register_endpoint_on_receive_signal(system_id, mctp_engine, obj, app)
382+ -- 撤销后的资源对象仍用于诊断;恢复端点前不查找或重订阅信号。
383+ if obj.Status == mctp_def.ENDPOINT_UN_DISCOVERY then
384+ return
385+ end
381 local ep = mctp_engine.endpoints:get_endpoint(obj.TargetEid, obj.MessageType, obj.MediumType)386 local ep = mctp_engine.endpoints:get_endpoint(obj.TargetEid, obj.MessageType, obj.MediumType)
382 if not ep then387 if not ep then
383 log:error(388 log:error(
@@ -411,13 +416,15 @@ function c_mctp_mdb_mgmt:add_mdb_endpoint(reg)
411 obj.Status = reg.ep_status416 obj.Status = reg.ep_status
412 obj.ObjectName = s_format('Endpoint_%s_%s', reg.phy_addr, reg.msg_type)417 obj.ObjectName = s_format('Endpoint_%s_%s', reg.phy_addr, reg.msg_type)
413 end)418 end)
414- local o = { obj = obj, signal = nil }419+ local o = { obj = obj, signal = nil, active = true }
415 self.mdb_endpoints[#self.mdb_endpoints + 1] = o420 self.mdb_endpoints[#self.mdb_endpoints + 1] = o
416 self.ep_info[obj.path] = { MediumType = reg.medium_type, TargetEid = reg.msg_src_eid, MessageType = reg.msg_type }421 self.ep_info[obj.path] = { MediumType = reg.medium_type, TargetEid = reg.msg_src_eid, MessageType = reg.msg_type }
417 422 
418 skynet.fork(function()423 skynet.fork(function()
419 -- 信号需要保存下来,如果endpoint发生变更则需要订阅新的endpoint424 -- 信号需要保存下来,如果endpoint发生变更则需要订阅新的endpoint
420- o.signal = register_endpoint_on_receive_signal(self.system_id, self.mctp_engine, obj, self.app)425+ if o.active and not o.signal then
426+ o.signal = register_endpoint_on_receive_signal(self.system_id, self.mctp_engine, obj, self.app)
427+ end
421 end)428 end)
422end429end
423 430 
@@ -431,7 +438,12 @@ function c_mctp_mdb_mgmt:find_endpoint(msg_type, phy_addr)
431 return nil438 return nil
432end439end
433 440 
434-function c_mctp_mdb_mgmt:upd_mdb_endpoint(reg)441+function c_mctp_mdb_mgmt:upd_mdb_endpoint(reg, previous)
442+ if previous and previous.phy_addr ~= reg.phy_addr then
443+ self:del_mdb_endpoint(previous)
444+ self:add_mdb_endpoint(reg)
445+ return
446+ end
435 local o = self:find_endpoint(reg.msg_type, reg.phy_addr)447 local o = self:find_endpoint(reg.msg_type, reg.phy_addr)
436 if not o then448 if not o then
437 return449 return
@@ -440,10 +452,19 @@ function c_mctp_mdb_mgmt:upd_mdb_endpoint(reg)
440 o.obj.TargetPhyAddr = reg.phy_addr452 o.obj.TargetPhyAddr = reg.phy_addr
441 o.obj.TargetEid = reg.msg_src_eid453 o.obj.TargetEid = reg.msg_src_eid
442 o.obj.ServiceName = reg.module_name454 o.obj.ServiceName = reg.module_name
455+ o.obj.MediumType = reg.medium_type or o.obj.MediumType
456+ o.obj.Status = reg.ep_status or o.obj.Status
457+ self.ep_info[o.obj.path] = {
O
Oopenubmc-ci-bot13 days ago

⚠️ 警告:upd_mdb_endpoint 未刷新 o.obj.MessageType,可能与 ep_info 不一致

ep_info[o.obj.path] 中的 MessageType 取 reg.msg_type,而 o.obj.MessageType 仍保持创建时(add_mdb_endpoint)的值,本函数未对其更新。register_endpoint_on_receive_signal 及 trace_message/endpoint_request/endpoint_send 均使用 obj.MessageType。

如果同物理地址重绑时恰好 msg_type 也变化,find_endpoint(reg.msg_type, reg.phy_addr) 会因为 o.obj.MessageType 仍为旧值而匹配不到,导致本次更新被静默丢弃,且 ep_info 与对象实际字段分叉。

修复建议:确认重绑场景下 msg_type 不会变化;若可能变化,需在 find_endpoint 匹配前同步更新 o.obj.MessageType = reg.msg_type,并保证 ep_info 与对象字段同源(可统一从 o.obj 派生 MessageType),避免后续 trace 定位到错误端点。

likedislike
liuxingsen
13 days ago Comments:
458+ MediumType = o.obj.MediumType,
459+ TargetEid = reg.msg_src_eid,
460+ MessageType = reg.msg_type,
461+ }
443 462 
444 -- endpoint改变后,重新将endpoint的on_receive和OnMessage关联起来463 -- endpoint改变后,重新将endpoint的on_receive和OnMessage关联起来
445- o.signal:disconnect()464+ if o.signal then
446- o.signal = register_endpoint_on_receive_signal(self.mctp_engine, o.obj, self.bus)465+ o.signal:disconnect()
466+ end
467+ o.signal = register_endpoint_on_receive_signal(self.system_id, self.mctp_engine, o.obj, self.app)
atomgit-botO
atomgit-botatomgit-bot13 days ago

🟡 Medium Priority

变更行(mctp_mdb_mgmt.lua:454-457):先 o.signal:disconnect() 断开旧订阅,再无条件 o.signal = register_endpoint_on_receive_signal(...)。

被调用方 register_endpoint_on_receive_signal(同文件 381-392 行)在 mctp_engine.endpoints:get_endpoint(obj.TargetEid, obj.MessageType, obj.MediumType) 返回 nil 时只 log:error 并 return(nil),因此 o.signal 会被赋成 nil。

触发条件:同一 phys_addr 重分配 EID 时,引擎侧为新 EID 创建的 endpoint 尚不存在。这正是可达路径:引擎的 config_eid_table(mctp_engine.lua:493-503)只有在 hw_pcie_proto:config_eid_table(reg_info) 成功时才 create_endpoint;驱动侧失败时新 endpoint 不会被创建,而 mctp_app 的 on_update_reg 处理器仍会调用 upd_mdb_endpoint。

失败模式:旧订阅已被 disconnect、新订阅为 nil,该 MDB endpoint 从此没有任何 on_receive 订阅,EndpointPCIeEndpointOnMessage 不再投递(且 ep_info 已被改成新 EID,trace_message 也会指向一个无人订阅的 endpoint),在下一次 on_update_reg 之前不会自愈——对上层表现为该服务报文静默丢失。

建议:改为"先注册、成功再断开旧订阅",注册返回 nil 时保留原 o.signal,避免订阅真空。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
Oopenubmc-ci-bot13 days ago

💡 建议:退休路径触发 upd_mdb_endpoint 会重复断开/重订阅并打出无意义 error

retire_peer 在撤销时发出 on_update_reg(reg, false)(mctp_engine.lua:265),mctp_app 因此调用到 upd_mdb_endpoint(reg, nil)。此时端点对象已从 self.endpoints 移除,第 463 行的 register_endpoint_on_receive_signal 内 get_endpoint 返回 nil,会打印一条 [System%s]unknown endpoint 的 log:error(mctp_mdb_mgmt.lua:384-391)并返回 nil,o.signal 被置空。

每次撤销都会触发这次断开+重订阅+error 日志抖动,虽不改变最终状态,但会污染日志并造成无谓的订阅重建。

修复建议:在 upd_mdb_endpoint/register_endpoint_on_receive_signal 端判断信号来源端点是否仍有效,对已退休(对端已从 engine 移除)的情况直接跳过重订阅,避免该 error 与重复断开重连。

likedislike
liuxingsen
13 days ago Comments:
447end468end
448 469 
449function c_mctp_mdb_mgmt:del_mdb_endpoint(reg)470function c_mctp_mdb_mgmt:del_mdb_endpoint(reg)
@@ -452,7 +473,11 @@ function c_mctp_mdb_mgmt:del_mdb_endpoint(reg)
452 return473 return
453 end474 end
454 475 
455- o.signal:disconnect()476+ o.active = false
477+ if o.signal then
478+ o.signal:disconnect()
479+ end
480+ self.ep_info[o.obj.path] = nil
456 log:notice(481 log:notice(
457 '[System%s]mctp_mdb_mgmt: unregister mdb endpoint, msg_type=%d, phy_addr=%d',482 '[System%s]mctp_mdb_mgmt: unregister mdb endpoint, msg_type=%d, phy_addr=%d',
458 self.system_id,483 self.system_id,
@@ -103,14 +103,34 @@ function c_mctp_routing_table:del_entry(phys_address)
103 self.on_del_entry:emit(entry)103 self.on_del_entry:emit(entry)
104end104end
105 105 
106+local function same_entry(entry, previous)
107+ if not previous then
108+ return false
109+ end
110+ for field, value in pairs(entry) do
111+ if previous[field] ~= value then
112+ return false
113+ end
114+ end
115+ for field, value in pairs(previous) do
116+ if entry[field] ~= value then
117+ return false
118+ end
119+ end
120+ return true
121+end
122+ 
106local function compare_entries(entries, old_entries)123local function compare_entries(entries, old_entries)
107 local new_entryies = {}124 local new_entryies = {}
108 local del_entryies = {}125 local del_entryies = {}
109 local new_entryies_map = {}126 local new_entryies_map = {}
110 for _, entry in ipairs(entries) do127 for _, entry in ipairs(entries) do
111 local phys_address = entry.phys_address128 local phys_address = entry.phys_address
112- if not old_entries[phys_address] then129+ if not same_entry(entry, old_entries[phys_address]) then
113 new_entryies[#new_entryies + 1] = entry130 new_entryies[#new_entryies + 1] = entry
131+ if old_entries[phys_address] then
132+ del_entryies[#del_entryies + 1] = phys_address
133+ end
114 end134 end
115 new_entryies_map[phys_address] = true135 new_entryies_map[phys_address] = true
116 end136 end
@@ -126,13 +146,13 @@ end
126 146 
127function c_mctp_routing_table:update(entries)147function c_mctp_routing_table:update(entries)
128 local new_entryies, del_entryies = compare_entries(entries, self.entries)148 local new_entryies, del_entryies = compare_entries(entries, self.entries)
129- for _, entry in ipairs(new_entryies) do149+ -- 先撤销所有旧身份和事务再发布新路由;删除/新增信号用于失效通知,不是单纯更新显示字段。
130- self:add_entry(entry)
131- end
132- 
133 for _, entry in ipairs(del_entryies) do150 for _, entry in ipairs(del_entryies) do
134 self:del_entry(entry)151 self:del_entry(entry)
135 end152 end
153+ for _, entry in ipairs(new_entryies) do
O
Oopenubmc-ci-bot13 days ago

💡 建议:路由字段变化采用「删除+重建」可能引起 D-Bus 对象抖动

当同一物理地址的路由字段发生变化时, 会把该地址同时放入 和 , 先 (触发 移除 mdb 路由对象并清理缓存),再 (重新 创建对象)。

同一同步周期内对同一对象路径做「移除 + 重建」,会额外产生一次 InterfacesRemoved/InterfacesAdded 抖动。若该 D-Bus 路由对象存在订阅该路径信号的上游组件,可能短暂感知到对象消失。

修复建议:可考虑对已存在对象直接更新其属性()并仅对新增/删除发布事件,避免对象删除重建;若当前做法是刻意为之(为保证先收回旧 EID 再发布新 EID),建议在代码注释中说明该时序依赖,便于后续维护。当前实现逻辑与单测均正确,此项仅为可选优化。

likedislike
liuxingsen
13 days ago Comments:
154+ self:add_entry(entry)
155+ end
136 156 
137 self.on_entry_updated:emit()157 self.on_entry_updated:emit()
138end158end
@@ -66,7 +66,11 @@ skynet.timer = {
66 cb()66 cb()
67 end67 end
68 end,68 end,
69- stop = function(...) end,69+ stop = function(timer)
70+ if timer and timer.stop then
71+ timer:stop()
72+ end
73+ end,
70}74}
71 75 
72return skynet76return skynet
@@ -114,6 +114,7 @@ function TestMctpDiscovery:test_mctp_discovery_success()
114 end)114 end)
115 115 
116 self.mctp_discovery:smbus_set_eid_to_endpoint(116 self.mctp_discovery:smbus_set_eid_to_endpoint(
117+ 'discovery-test',
117 target_eid,118 target_eid,
118 ref_chip,119 ref_chip,
119 medium_type,120 medium_type,
@@ -158,6 +159,7 @@ function TestMctpDiscovery:test_mctp_discovery_failed()
158 local ref_chip = { path = path }159 local ref_chip = { path = path }
159 160 
160 self.mctp_discovery:smbus_set_eid_to_endpoint(161 self.mctp_discovery:smbus_set_eid_to_endpoint(
162+ 'discovery-test',
161 target_eid,163 target_eid,
162 ref_chip,164 ref_chip,
163 medium_type,165 medium_type,
@@ -150,3 +150,12 @@ function TestMctpEidTable:test_find_reg_info()
150 res = self.eid_table:find_reg_info(reg_info.pid, reg_info.msg_src_eid, reg_info.phy_addr, reg_info.msg_type)150 res = self.eid_table:find_reg_info(reg_info.pid, reg_info.msg_src_eid, reg_info.phy_addr, reg_info.msg_type)
151 lu.assertEquals(res, nil)151 lu.assertEquals(res, nil)
152end152end
153+ 
154+function TestMctpEidTable:test_message_type_is_an_immutable_registration_key()
155+ local registry = c_mctp_eid_table.new(1)
156+ registry:add_reg_info(1, 1, 9, 32, 2, 'ncsi')
157+ registry:add_reg_info(1, 1, 9, 32, 126, 'vendor')
158+ lu.assertEquals(#registry.reg_table, 2)
159+ lu.assertEquals(registry:find_reg_info(1, 9, 32, 2).module_name, 'ncsi')
160+ lu.assertEquals(registry:find_reg_info(1, 9, 32, 126).module_name, 'vendor')
161+end
@@ -43,3 +43,100 @@ function TestEndpointGeneral:test_request()
43 ret, _ = pcall(self.send, self, req_bin)43 ret, _ = pcall(self.send, self, req_bin)
44 lu.assertEquals(ret, true)44 lu.assertEquals(ret, true)
45end45end
46+ 
47+TestPeerRequestLifetime = {}
48+function TestPeerRequestLifetime:setup()
49+ self.engine = c_mctp_engine.new(nil, 1)
50+ self.coroutines = {}
51+ self.skynet = require('skynet')
52+end
53+ 
54+function TestPeerRequestLifetime:teardown()
55+ for _, co in ipairs(self.coroutines) do
56+ if coroutine.status(co) == 'suspended' then
57+ coroutine.resume(co)
58+ end
59+ end
60+end
61+ 
62+function TestPeerRequestLifetime:start_request(ep, consumer)
63+ local co = coroutine.create(function()
64+ return require('mctp_endpoints.ep_mctp').request(ep, 1, function() end, 1000, consumer)
65+ end)
66+ self.coroutines[#self.coroutines + 1] = co
67+ local old_wait = self.skynet.wait
68+ self.skynet.wait = coroutine.yield
69+ local ok, err = coroutine.resume(co)
70+ self.skynet.wait = old_wait -- resume包含协程内异常;所有断言之前恢复共享模块。
71+ lu.assertTrue(ok, tostring(err))
72+ lu.assertEquals(coroutine.status(co), 'suspended')
73+ return co
74+end
75+ 
76+function TestPeerRequestLifetime:test_failed_request_always_restores_wait_before_assertions()
77+ local original = self.skynet.wait
78+ lu.assertFalse(pcall(self.start_request, self, {}))
79+ lu.assertIs(self.skynet.wait, original)
80+end
81+ 
82+function TestPeerRequestLifetime:test_retired_endpoint_rejects_new_request_and_send_as_reset()
83+ local ep = self.engine.endpoints:create_endpoint(32, 9, 15, 15)
84+ self.engine:retire_peer(15, ep.phy_addr, 9)
85+ local base, calls = require('mctp_endpoints.ep_mctp'), 0
86+ local cb = function()
87+ calls = calls + 1
88+ end
89+ local ok, err = pcall(base.request, ep, 1, cb, 1000)
90+ lu.assertFalse(ok)
91+ lu.assertStrContains(tostring(err), 'MctpEndpointReset')
92+ ok, err = pcall(base.send, ep, cb)
93+ lu.assertFalse(ok)
94+ lu.assertStrContains(tostring(err), 'MctpEndpointReset')
95+ lu.assertEquals(calls, 0)
96+end
97+ 
98+function TestPeerRequestLifetime:test_retirement_cancels_only_matching_query_and_skips_consumer()
99+ local engine, calls = self.engine, 0
100+ local old = engine.endpoints:create_endpoint(32, 9, 15, 15)
101+ local other = engine.endpoints:create_endpoint(33, 10, 15, 15)
102+ local cancelled = self:start_request(old, function()
103+ calls = calls + 1
104+ end)
105+ local live = self:start_request(other)
106+ engine:retire_peer(15, old.phy_addr, 9)
107+ lu.assertTrue(old.retired)
108+ lu.assertIsNil(engine.endpoints:get_endpoint(9, 15, 15))
109+ lu.assertIs(engine.endpoints:get_endpoint(10, 15, 15), other)
110+ local ok, err = coroutine.resume(cancelled)
111+ lu.assertFalse(ok)
112+ lu.assertStrContains(tostring(err), 'MctpEndpointReset')
113+ lu.assertEquals(calls, 0)
114+ lu.assertEquals(coroutine.status(live), 'suspended')
115+ local waits = 0
116+ for _, wait in pairs(engine.rsp_wait_list) do
117+ lu.assertIs(wait.endpoint, other)
118+ waits = waits + 1
119+ end
120+ lu.assertEquals(waits, 1)
121+ lu.assertFalse(pcall(old.prepare_mctp_request, old))
122+end
123+ 
124+function TestPeerRequestLifetime:test_retirement_stops_wait_timer_once_before_resume()
125+ local engine, stops = self.engine, 0
126+ engine.new_timer = function()
127+ return {
128+ stop = function()
129+ stops = stops + 1
130+ end,
131+ }
132+ end
133+ local ep = engine.endpoints:create_endpoint(32, 9, 15, 15)
134+ local co = self:start_request(ep)
135+ engine:retire_peer(15, ep.phy_addr, 9)
136+ lu.assertEquals(stops, 1)
137+ engine:retire_peer(15, ep.phy_addr, 9)
138+ local ok, err = coroutine.resume(co)
139+ lu.assertFalse(ok)
140+ lu.assertStrContains(tostring(err), 'MctpEndpointReset')
141+ lu.assertEquals(stops, 1)
142+end
@@ -276,3 +276,217 @@ function TestMctpEngine:test_start()
276 276 
277 mctp_engine.hw_pcie_proto = old_hw_pcie_proto277 mctp_engine.hw_pcie_proto = old_hw_pcie_proto
278end278end
279+ 
280+TestPeerLifetime = {}
281+function TestPeerLifetime:setup()
282+ self.engine = c_mctp_engine.new(nil, 1) -- mc.class.new already invokes init.
283+ self.engine.config_eid_table = function(engine, reg)
284+ engine.endpoints:create_endpoint(reg.phy_addr, reg.msg_src_eid, reg.msg_type, reg.medium_type, reg.ep_status)
285+ end
286+end
287+ 
288+function TestPeerLifetime:test_non_pcie_route_removal_keeps_same_address_pcie_peer()
289+ local engine = self.engine
290+ engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'card')
291+ local ep = engine.endpoints:get_endpoint(9, 126, 15)
292+ engine.routing_table.on_del_entry:emit({ phys_address = 32, phys_transport_binding_id = 1 })
293+ lu.assertIs(engine.endpoints:get_endpoint(9, 126, 15), ep)
294+ lu.assertFalse(ep.retired == true)
295+ lu.assertFalse(engine.eid_table.reg_table[1].route_withdrawn == true)
296+end
297+ 
298+function TestPeerLifetime:test_pcie_registry_changes_do_not_retire_smbus_endpoint()
299+ local engine = self.engine
300+ local medium = mctp_def.PHY_MEDIUM_SMBUS_OVER_I2COVERLOCALBUS
301+ local smbus = engine.endpoints:create_endpoint(32, 9, 126, medium)
302+ engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'pcie')
303+ lu.assertEquals(engine.eid_table.reg_table[1].medium_type, mctp_def.PHY_MEDIUM_PCI)
304+ engine.eid_table:add_reg_info(1, 1, 10, 33, 126, 'pcie')
305+ lu.assertIs(engine.endpoints:get_endpoint(9, 126, medium), smbus)
306+ lu.assertFalse(smbus.retired == true)
307+ engine.eid_table:reset()
308+ lu.assertIs(engine.endpoints:get_endpoint(9, 126, medium), smbus)
309+ lu.assertFalse(smbus.retired == true)
310+end
311+ 
312+function TestPeerLifetime:test_registration_change_retires_old_identity_before_replacement()
313+ local table = self.engine.eid_table
314+ table:add_reg_info(1, 1, 9, 32, 126, 'card')
315+ table:add_reg_info(1, 1, 12, 33, 126, 'other')
316+ local old = self.engine.endpoints:get_endpoint(9, 126, 15)
317+ table:add_reg_info(1, 1, 10, 32, 126, 'card')
318+ lu.assertTrue(old.retired)
319+ lu.assertIsNil(self.engine.endpoints:get_endpoint(9, 126, 15))
320+ lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 126, 15))
321+ lu.assertNotNil(self.engine.endpoints:get_endpoint(12, 126, 15))
322+ local changed = self.engine.endpoints:get_endpoint(10, 126, 15)
323+ table:add_reg_info(1, 1, 10, 34, 126, 'card')
324+ lu.assertTrue(changed.retired)
325+ lu.assertEquals(self.engine.endpoints:get_endpoint(10, 126, 15).phy_addr, 0x2200)
326+end
327+ 
328+function TestPeerLifetime:test_route_rebind_changes_eid_and_restores_same_eid_without_stale_endpoints()
329+ local routing, registry = self.engine.routing_table, self.engine.eid_table
330+ local function update(eid, port)
331+ routing:update({
332+ {
333+ phys_address = 32,
334+ starting_eid = eid,
335+ eid_range_size = 1,
336+ phys_transport_binding_id = 2,
337+ phys_media_type_id = 11,
338+ port_num = port or 0,
339+ },
340+ })
341+ end
342+ update(9)
343+ registry:add_reg_info(1, 1, 9, 32, 2, 'card')
344+ registry:add_reg_info(1, 1, 9, 32, 126, 'card')
345+ registry:add_reg_info(1, 1, 12, 33, 126, 'other')
346+ local first = self.engine.endpoints:get_endpoint(9, 126, 15)
347+ update(10)
348+ lu.assertTrue(first.retired)
349+ lu.assertIsNil(self.engine.endpoints:get_endpoint(9, 126, 15))
350+ local next_peer = self.engine.endpoints:get_endpoint(10, 126, 15)
351+ lu.assertNotNil(next_peer)
352+ lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 2, 15))
353+ lu.assertNotNil(self.engine.endpoints:get_endpoint(12, 126, 15))
354+ update(10)
355+ lu.assertIs(self.engine.endpoints:get_endpoint(10, 126, 15), next_peer)
356+ update(10, 1)
357+ lu.assertTrue(next_peer.retired)
358+ lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 2, 15))
359+ routing:update({})
360+ lu.assertIsNil(self.engine.endpoints:get_endpoint(10, 126, 15))
361+ lu.assertEquals(registry.reg_table[1].ep_status, mctp_def.ENDPOINT_UN_DISCOVERY)
362+ update(10, 1)
363+ lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 2, 15))
364+ lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 126, 15))
365+end
366+ 
367+function TestPeerLifetime:test_driver_registration_failure_is_visible_and_retried_on_route_refresh()
368+ local engine, tries = self.engine, 0
369+ engine.config_eid_table = c_mctp_engine.config_eid_table
370+ engine.hw_pcie_proto = {
371+ config_eid_table = function()
372+ tries = tries + 1
373+ return tries > 1
374+ end,
375+ }
376+ local route = { phys_address = 32, starting_eid = 9, eid_range_size = 1, phys_transport_binding_id = 2 }
377+ engine.routing_table:update({ route })
378+ engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'card')
379+ local reg = engine.eid_table.reg_table[1]
380+ lu.assertEquals(tries, 1, 'initial driver attempts')
381+ lu.assertEquals(reg.ep_status, mctp_def.ENDPOINT_UN_DISCOVERY)
382+ lu.assertIsNil(engine.endpoints:get_endpoint(9, 126, 15))
383+ engine.routing_table:update({ route })
384+ lu.assertEquals(tries, 2)
385+ lu.assertEquals(reg.ep_status, mctp_def.ENDPOINT_IS_DISCOVERY)
386+ lu.assertNotNil(engine.endpoints:get_endpoint(9, 126, 15))
387+end
388+ 
389+function TestPeerLifetime:test_multiple_consumers_rebind_without_reviving_old_or_retiring_new_identity()
390+ local registry = self.engine.eid_table
391+ registry:add_reg_info(1, 1, 9, 32, 2, 'ncsi')
392+ registry:add_reg_info(1, 1, 9, 32, 126, 'vendor')
393+ registry:add_reg_info(1, 1, 10, 32, 2, 'ncsi')
394+ lu.assertEquals(registry:find_reg_info(1, 9, 32, 126).ep_status, mctp_def.ENDPOINT_UN_DISCOVERY)
395+ local new = self.engine.endpoints:get_endpoint(10, 2, 15)
396+ registry:add_reg_info(1, 1, 10, 32, 126, 'vendor')
397+ lu.assertIs(self.engine.endpoints:get_endpoint(10, 2, 15), new)
398+ lu.assertFalse(new.retired == true)
399+ lu.assertNotNil(self.engine.endpoints:get_endpoint(10, 126, 15))
400+end
401+ 
402+function TestPeerLifetime:test_two_cards_can_swap_eids_in_either_route_order()
403+ for _, reversed in ipairs({ false, true }) do
404+ self:setup()
405+ local engine, registry = self.engine, self.engine.eid_table
406+ local function route(phy, eid)
407+ return { phys_address = phy, starting_eid = eid, eid_range_size = 1, phys_transport_binding_id = 2 }
408+ end
409+ engine.routing_table:update({ route(32, 9), route(33, 10) })
410+ registry:add_reg_info(1, 1, 9, 32, 126, 'A')
411+ registry:add_reg_info(1, 1, 10, 33, 126, 'B')
412+ local first, second = registry.reg_table[1], registry.reg_table[2]
413+ local a, b = engine.endpoints:get_endpoint(9, 126, 15), engine.endpoints:get_endpoint(10, 126, 15)
414+ local next_routes = reversed and { route(33, 9), route(32, 10) } or { route(32, 10), route(33, 9) }
415+ engine.routing_table:update(next_routes)
416+ lu.assertEquals(#registry.reg_table, 2)
417+ lu.assertEquals({ first.phy_addr, first.msg_src_eid, first.module_name }, { 32, 10, 'A' })
418+ lu.assertEquals({ second.phy_addr, second.msg_src_eid, second.module_name }, { 33, 9, 'B' })
419+ lu.assertTrue(a.retired)
420+ lu.assertTrue(b.retired)
421+ lu.assertEquals(engine.endpoints:get_endpoint(10, 126, 15).phy_addr, 0x2000)
422+ lu.assertEquals(engine.endpoints:get_endpoint(9, 126, 15).phy_addr, 0x2100)
423+ end
424+end
425+ 
426+function TestPeerLifetime:test_failed_registration_recovers_after_eid_changes()
427+ local engine, attempts = self.engine, 0
428+ engine.config_eid_table = c_mctp_engine.config_eid_table
429+ engine.hw_pcie_proto = {
430+ config_eid_table = function()
431+ attempts = attempts + 1
432+ return attempts > 1
433+ end,
434+ }
435+ engine.routing_table:update({
436+ { phys_address = 32, starting_eid = 9, eid_range_size = 1, phys_transport_binding_id = 2 },
437+ })
438+ engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'card')
439+ lu.assertEquals(engine.eid_table.reg_table[1].ep_status, mctp_def.ENDPOINT_UN_DISCOVERY)
440+ engine.routing_table:update({
441+ { phys_address = 32, starting_eid = 10, eid_range_size = 1, phys_transport_binding_id = 2 },
442+ })
443+ local reg = engine.eid_table.reg_table[1]
444+ lu.assertEquals(attempts, 2)
445+ lu.assertEquals({ reg.phy_addr, reg.msg_src_eid, reg.ep_status }, { 32, 10, mctp_def.ENDPOINT_IS_DISCOVERY })
446+ lu.assertIsNil(engine.endpoints:get_endpoint(9, 126, 15))
447+ lu.assertNotNil(engine.endpoints:get_endpoint(10, 126, 15))
448+end
449+ 
450+function TestPeerLifetime:test_reused_eid_does_not_deliver_old_physical_source()
451+ local engine, received = self.engine, {}
452+ local ep = engine.endpoints:create_endpoint(33, 9, 126, 15)
453+ ep.on_receive:on(function(data)
454+ received[#received + 1] = data
455+ end)
456+ local physical, ctx = mctp.pcie_vdm_ctx.new(), mctp.mctp_ctx.new()
457+ ctx:set_prev(physical)
458+ ctx.src, ctx.dest, ctx.tag_owner = 9, 11, true
459+ physical.remote_phy_addr = 0x2000
460+ engine:rx(ctx, 'old notification', 126, 15)
461+ ctx.tag_owner = false
462+ engine:rx(ctx, 'old unmatched response', 126, 15)
463+ lu.assertEquals(received, {})
464+ physical.remote_phy_addr = 0x2100
465+ engine:rx(ctx, 'current legacy notification', 126, 15)
466+ ctx.tag_owner = true
467+ engine:rx(ctx, 'current notification', 126, 15)
468+ lu.assertEquals(received, { 'current legacy notification', 'current notification' })
469+end
470+ 
471+function TestPeerLifetime:test_pcie_requires_physical_context_and_wire_parser_supplies_it()
472+ local engine, received = self.engine, {}
473+ local ep = engine.endpoints:create_endpoint(33, 9, 126, 15)
474+ ep.on_receive:on(function(data)
475+ received[#received + 1] = data
476+ end)
477+ local ctx = mctp.mctp_ctx.new()
478+ ctx.src, ctx.dest, ctx.tag_owner = 9, 11, true
479+ lu.assertIsNil(ctx:get('remote_phy_addr', 'pcie_vdm_ctx'))
480+ engine:rx(ctx, 'missing source', 126, 15)
481+ lu.assertEquals(received, {})
482+ 
483+ local pcie, transport = mctp.pcie_vdm_protocol.new(), mctp.mctp_protocol.new()
484+ transport.pcie_eid = 11
485+ pcie:add_children(transport)
486+ transport:set_rx_handler(function(wire_ctx, data)
487+ lu.assertEquals(wire_ctx:get('remote_phy_addr', 'pcie_vdm_ctx'), 0x2100)
488+ return engine:rx(wire_ctx, data, 126, 15)
489+ end)
490+ pcie:pop('\x72\x00\x10\x01\x21\x00\x10\x7f\x03\x00\x1a\xb4\x01\x0b\x09\xd9\x7e\x8a\x0b', nil, 15)
491+ lu.assertEquals(received, { '\x7e\x8a\x0b' })
492+end
@@ -22,6 +22,79 @@ end
22 22 
23function TestMctpMdbMgmt:teardownClass() end23function TestMctpMdbMgmt:teardownClass() end
24 24 
25+function TestMctpMdbMgmt:test_rebind_updates_dispatch_and_disconnects_old_notifications()
26+ local engine = c_mctp_engine.new(nil, 1)
27+ engine:init()
28+ engine.endpoints:init()
29+ local first = engine.endpoints:create_endpoint(32, 10, 126, 15)
30+ local second = engine.endpoints:create_endpoint(32, 11, 126, 15)
31+ local obj = { path = '/test/endpoint', TargetPhyAddr = 32, TargetEid = 9, MessageType = 126, MediumType = 15 }
32+ local messages = {}
33+ local mgr = setmetatable({
34+ system_id = 1,
35+ mctp_engine = engine,
36+ mdb_endpoints = { { obj = obj } },
37+ ep_info = { [obj.path] = { TargetEid = 9 } },
38+ app = {
39+ EndpointPCIeEndpointOnMessage = function(_, target, data)
40+ lu.assertIs(target, obj)
41+ messages[#messages + 1] = data
42+ end,
43+ EndpointPCIeEndpointMessageReceived = function() end,
44+ },
45+ }, { __index = c_mctp_mdb_mgmt })
46+ mgr:upd_mdb_endpoint({ phy_addr = 32, msg_src_eid = 10, msg_type = 126, medium_type = 15, module_name = 'consumer' })
47+ first.on_receive:emit('first')
48+ mgr:upd_mdb_endpoint({ phy_addr = 32, msg_src_eid = 11, msg_type = 126, medium_type = 15, module_name = 'consumer' })
49+ first.on_receive:emit('obsolete')
50+ second.on_receive:emit('second')
51+ lu.assertEquals(messages, { 'first', 'second' })
52+ lu.assertEquals(mgr.ep_info[obj.path], { TargetEid = 11, MessageType = 126, MediumType = 15 })
53+ mgr:trace_message(obj, 'start')
54+ lu.assertEquals(second.is_trace, 'start')
55+ lu.assertNotEquals(first.is_trace, 'start')
56+end
57+ 
58+function TestMctpMdbMgmt:test_delayed_initial_subscription_does_not_duplicate_rebind()
59+ local signal, skynet = require('mc.signal'), require('skynet')
60+ local ep, messages = { on_receive = signal.new() }, 0
61+ local engine = {
62+ module_name = 'MCTP',
63+ endpoints = {
64+ get_endpoint = function()
65+ return ep
66+ end,
67+ },
68+ }
69+ local app = {
70+ CreateEndpoint = function(_, _, _, _, cb)
71+ local obj = { path = '/test/delayed' }
72+ cb(obj)
73+ return obj
74+ end,
75+ EndpointPCIeEndpointOnMessage = function()
76+ messages = messages + 1
77+ end,
78+ EndpointPCIeEndpointMessageReceived = function() end,
79+ }
80+ local mgr = setmetatable(
81+ { system_id = 1, app = app, mctp_engine = engine, mdb_endpoints = {}, ep_info = {} },
82+ { __index = c_mctp_mdb_mgmt }
83+ )
84+ local reg = { phy_addr = 32, msg_src_eid = 10, msg_type = 126, medium_type = 15, module_name = 'consumer' }
85+ local original, delayed = skynet.fork
86+ skynet.fork = function(fn)
87+ delayed = fn
88+ end
89+ local ok, err = pcall(mgr.add_mdb_endpoint, mgr, reg)
90+ skynet.fork = original
91+ lu.assertTrue(ok, tostring(err))
92+ mgr:upd_mdb_endpoint(reg)
93+ delayed()
94+ ep.on_receive:emit('one')
95+ lu.assertEquals(messages, 1)
atomgit-bot
atomgit-botatomgit-bot13 days ago

🔵 Low Priority

新增用例(test_mctp_mdb_mgmt.lua:58-96)只覆盖了"初始订阅尚未完成 → 重绑 → 延迟 fork 不重复订阅"这条分支;本次新增的 o.active 只在该用例中恒为 true,删除分支完全没有被触发。

未被覆盖的高风险逻辑:del_mdb_endpoint 中的 o.active = false(src/lualib/mctp_mdb_mgmt.lua:466)+ add_mdb_endpoint 延迟 fork 中的 if o.active and not o.signal(同文件 421 行)共同构成"删除后不复活"的守卫,是本 PR 声称修复的核心之一(PR 说明:"以 active 和 signal 保护延迟初始订阅,避免…删除后复活")。

漏测的具体回归:若 o.active = false 被删除,或 fork 守卫写成只判断 not o.signal,那么在"add → 在 fork 执行前 del"这一时序下,fork 仍会对已被 class_mgnt(...):remove 的 MDB 对象重新订阅,并继续把消息投递给已卸载对象(删除后复活 / 悬空通知)。现有用例(先 upd 再 delayed())无法捕获该类回归,作者自验证报告也承认"删除前初始 fork 未执行…的直接测试暂未补充"。

建议:补充"add → del(fork 未执行)→ 执行被捕获的 fork → 断言不再投递"的用例,覆盖 o.active=false 守卫。

likedislike
Incorrect?
liuxingsen
13 days ago Comments:
96+end
97+ 
25function TestMctpMdbMgmt:test_add_objects()98function TestMctpMdbMgmt:test_add_objects()
26 local ep_obj = {99 local ep_obj = {
27 name = 'Endpoint_1_0101',100 name = 'Endpoint_1_0101',
@@ -159,3 +232,152 @@ function TestMctpMdbMgmt:test_del_objects_callback()
159 pcall(c_mctp_mdb_mgmt.del_objects_callback, c_mctp_mdb_mgmt, 'Endpoint', { name = 'Enponit_test' }, '01')232 pcall(c_mctp_mdb_mgmt.del_objects_callback, c_mctp_mdb_mgmt, 'Endpoint', { name = 'Enponit_test' }, '01')
160 lu.assertNotIsNil(ret)233 lu.assertNotIsNil(ret)
161end234end
235+ 
236+local function lifecycle_fixture()
237+ local skynet = require('skynet')
238+ local original = package.loaded['mc.class_mgnt']
239+ local removed, messages, delayed = {}, {}, {}
240+ package.loaded['mc.class_mgnt'] = function()
241+ return {
242+ remove = function(_, obj)
243+ removed[#removed + 1] = obj.path
244+ end,
245+ }
246+ end
247+ local ok, Module = pcall(dofile, package.searchpath('mctp_mdb_mgmt', package.path))
248+ package.loaded['mc.class_mgnt'] = original
249+ lu.assertTrue(ok, tostring(Module))
250+ local engine = c_mctp_engine.new(nil, 1)
251+ local app = {
252+ CreateEndpoint = function(_, _, phy, typ, cb)
253+ local obj = { path = '/test/' .. phy .. '/' .. typ, class_name = 'Endpoint' }
254+ cb(obj)
255+ return obj
256+ end,
257+ EndpointPCIeEndpointOnMessage = function(_, obj, data)
258+ messages[#messages + 1] = { obj.path, data }
259+ end,
260+ EndpointPCIeEndpointMessageReceived = function() end,
261+ }
262+ local mgr = setmetatable(
263+ { system_id = 1, app = app, mctp_engine = engine, mdb_endpoints = {}, ep_info = {} },
264+ { __index = Module }
265+ )
266+ local function call(method, ...)
267+ local old = skynet.fork
268+ skynet.fork = function(fn)
269+ delayed[#delayed + 1] = fn
270+ end
271+ local success, err = pcall(method, mgr, ...)
272+ skynet.fork = old
273+ lu.assertTrue(success, tostring(err))
274+ end
275+ return mgr, engine, removed, messages, delayed, call
276+end
277+ 
278+function TestMctpMdbMgmt:test_delete_before_initial_fork_cannot_resurrect_subscription()
279+ local mgr, engine, removed, messages, delayed, call = lifecycle_fixture()
280+ local ep = engine.endpoints:create_endpoint(32, 9, 126, 15)
281+ local reg = { phy_addr = 32, msg_src_eid = 9, msg_type = 126, medium_type = 15, module_name = 'card' }
282+ call(mgr.add_mdb_endpoint, reg)
283+ mgr:del_mdb_endpoint(reg)
284+ delayed[1]()
285+ ep.on_receive:emit('late')
286+ mgr:del_mdb_endpoint(reg)
287+ lu.assertEquals(messages, {})
288+ lu.assertEquals(removed, { '/test/32/126' })
289+ lu.assertEquals(mgr.ep_info, {})
290+ lu.assertEquals(mgr.mdb_endpoints, {})
291+end
292+ 
293+function TestMctpMdbMgmt:test_physical_move_recreates_path_and_disconnects_old_notifications()
294+ local mgr, engine, removed, messages, delayed, call = lifecycle_fixture()
295+ local old = { phy_addr = 32, msg_src_eid = 9, msg_type = 126, medium_type = 15, module_name = 'card' }
296+ local new = { phy_addr = 33, msg_src_eid = 9, msg_type = 126, medium_type = 15, module_name = 'card' }
297+ local first = engine.endpoints:create_endpoint(32, 9, 126, 15)
298+ call(mgr.add_mdb_endpoint, old)
299+ delayed[1]()
300+ engine:retire_peer(15, first.phy_addr, 9)
301+ local second = engine.endpoints:create_endpoint(33, 9, 126, 15)
302+ call(mgr.upd_mdb_endpoint, new, old)
303+ lu.assertEquals(removed, { '/test/32/126' })
304+ delayed[2]()
305+ first.on_receive:emit('old')
306+ second.on_receive:emit('new')
307+ lu.assertEquals(messages, { { '/test/33/126', 'new' } })
308+ lu.assertIsNil(mgr.ep_info['/test/32/126'])
309+ lu.assertEquals(mgr.ep_info['/test/33/126'], { MediumType = 15, TargetEid = 9, MessageType = 126 })
310+end
311+ 
312+function TestMctpMdbMgmt:test_swapped_eids_keep_physical_paths_and_new_notifications()
313+ for _, reversed in ipairs({ false, true }) do
314+ local mgr, engine, removed, messages, delayed, call = lifecycle_fixture()
315+ engine.config_eid_table = function(e, reg)
316+ e.endpoints:create_endpoint(reg.phy_addr, reg.msg_src_eid, reg.msg_type, reg.medium_type, reg.ep_status)
317+ end
318+ engine.eid_table.on_add_reg:on(function(reg)
319+ call(mgr.add_mdb_endpoint, reg)
320+ end)
321+ engine.eid_table.on_update_reg:on(function(reg, _, previous)
322+ call(mgr.upd_mdb_endpoint, reg, previous)
323+ end)
324+ local function route(phy, eid)
325+ return { phys_address = phy, starting_eid = eid, eid_range_size = 1, phys_transport_binding_id = 2 }
326+ end
327+ engine.routing_table:update({ route(32, 9), route(33, 10) })
328+ engine.eid_table:add_reg_info(1, 1, 9, 32, 126, 'A')
329+ engine.eid_table:add_reg_info(1, 1, 10, 33, 126, 'B')
330+ local old_a, old_b = engine.endpoints:get_endpoint(9, 126, 15), engine.endpoints:get_endpoint(10, 126, 15)
331+ engine.routing_table:update(reversed and { route(33, 9), route(32, 10) } or { route(32, 10), route(33, 9) })
332+ for _, fn in ipairs(delayed) do
333+ fn()
334+ end
335+ lu.assertEquals(removed, {})
336+ lu.assertEquals(mgr.ep_info['/test/32/126'].TargetEid, 10)
337+ lu.assertEquals(mgr.ep_info['/test/33/126'].TargetEid, 9)
338+ old_a.on_receive:emit('old A')
339+ old_b.on_receive:emit('old B')
340+ engine.endpoints:get_endpoint(10, 126, 15).on_receive:emit('new A')
341+ engine.endpoints:get_endpoint(9, 126, 15).on_receive:emit('new B')
342+ lu.assertEquals(messages, { { '/test/32/126', 'new A' }, { '/test/33/126', 'new B' } })
343+ end
344+end
345+ 
346+function TestMctpMdbMgmt:test_withdrawn_endpoint_skips_subscription_and_recovers_once()
347+ for _, subscribed in ipairs({ false, true }) do
348+ local mgr, engine, _, messages, delayed, call = lifecycle_fixture()
349+ local reg = {
350+ phy_addr = 32,
351+ msg_src_eid = 9,
352+ msg_type = 126,
353+ medium_type = 15,
354+ module_name = 'card',
355+ ep_status = 1,
356+ }
357+ local old = engine.endpoints:create_endpoint(32, 9, 126, 15)
358+ call(mgr.add_mdb_endpoint, reg)
359+ if subscribed then
360+ delayed[1]()
361+ end
362+ engine:retire_peer(15, old.phy_addr, 9)
363+ local lookup, lookups = engine.endpoints.get_endpoint, 0
364+ engine.endpoints.get_endpoint = function(...)
365+ lookups = lookups + 1
366+ return lookup(...)
367+ end
368+ reg.ep_status = 0
369+ mgr:upd_mdb_endpoint(reg)
370+ mgr:upd_mdb_endpoint(reg)
371+ delayed[1]()
372+ lu.assertEquals(lookups, 0)
373+ lu.assertIsNil(mgr.mdb_endpoints[1].signal)
374+ old.on_receive:emit('obsolete')
375+ lu.assertEquals(messages, {})
376+ local current = engine.endpoints:create_endpoint(32, 9, 126, 15)
377+ reg.ep_status = 1
378+ mgr:upd_mdb_endpoint(reg)
379+ delayed[1]()
380+ current.on_receive:emit('restored')
381+ lu.assertEquals(messages, { { '/test/32/126', 'restored' } })
382+ end
383+end
@@ -83,3 +83,41 @@ function TestMctpRoutingTable:test_get_route_table()
83 }83 }
84 lu.assertEquals(res, expert)84 lu.assertEquals(res, expert)
85end85end
86+ 
87+function TestMctpRoutingTable:test_changed_route_retires_old_before_publishing_new()
88+ for _, field in ipairs({
89+ 'starting_eid',
90+ 'eid_range_size',
91+ 'entry_type',
92+ 'phys_transport_binding_id',
93+ 'phys_media_type_id',
94+ 'port_num',
95+ 'dymatic_static_entry',
96+ }) do
97+ local table_ = c_mctp_routing_table.new(1)
98+ local old, changed, events = {}, {}, {}
99+ for k, v in pairs(entries[1]) do
100+ old[k], changed[k] = v, v
101+ end
102+ table_:update({ old })
103+ changed[field] = changed[field] + 1
104+ table_.on_del_entry:on(function(entry)
105+ lu.assertEquals(entry, old)
106+ lu.assertIsNil(table_:get_entry(entry.phys_address))
107+ events[#events + 1] = 'del'
108+ end)
109+ table_.on_add_entry:on(function(entry)
110+ lu.assertEquals(entry, changed)
111+ events[#events + 1] = 'add'
112+ end)
113+ table_:update({ changed })
114+ lu.assertEquals(events, { 'del', 'add' }, field)
115+ lu.assertEquals(table_:get_entry(old.phys_address), changed)
116+ local repeated = {}
117+ for key, value in pairs(changed) do
118+ repeated[key] = value
119+ end
120+ table_:update({ repeated })
121+ lu.assertEquals(events, { 'del', 'add' }, '同值但不同表对象不重新发布')
122+ end
123+end