<fix>[core]: synchronize consistent hash ring to prevent dual-MN race condition#3332
<fix>[core]: synchronize consistent hash ring to prevent dual-MN race condition#3332MatheMatrix wants to merge 11 commits into5.5.6from
Conversation
Walkthrough对管理节点哈希环、节点信息访问和生命周期处理增加同步保护与两轮缺失确认;在 VM 生命周期关键点新增设备地址同步调用;调整 VM 根盘分配大小计算和状态机新增从 Destroying→Stopped 的转移;为 ZBS HTTP 调用添加可选的“尝试下一个 MDS”重试开关。 Changes
Sequence Diagram(s)sequenceDiagram
participant Heartbeat as HeartbeatReconciler
participant Manager as ManagementNodeManagerImpl
participant DB as Database
participant HashRing as NodeHashRing
Heartbeat->>Manager: start reconciliation()
Manager->>Manager: synchronized(lifecycleLock)
Manager->>HashRing: list nodes in hash ring
loop for each node
Manager->>DB: query node by uuid
alt DB has node
DB-->>Manager: node exists
Manager->>HashRing: ensure node present
Manager->>Manager: suspectedMissingFromDb.remove(node)
else DB missing node
DB-->>Manager: not found
Manager->>Manager: mark node in suspectedMissingFromDb (first detection)
alt still missing on next reconciliation
Manager->>HashRing: nodeLeft(node)
Manager->>Manager: suspectedMissingFromDb.remove(node)
end
end
end
预估代码审查工作量🎯 4 (复杂) | ⏱️ ~45 分钟 诗
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.40.5)compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.javaComment |
…isk offering Resolves: ZSTAC-74683 Change-Id: Id0339ed0221e92e506f60745cde972cc3ee6d9ae
When anti-split-brain check selects a disconnected MDS node, the HTTP call now times out after 30s instead of 5+ minutes, and automatically retries the next available MDS via tryNext mechanism. Resolves: ZSTAC-80595 Change-Id: I1be80f1b70cad1606eb38d1f0078c8f2781e6941
When MN restarts during a destroy operation, the hypervisor may report the VM as Stopped. Without this transition, the state machine throws an exception and the VM stays stuck in Destroying state forever. Resolves: ZSTAC-80620 Change-Id: I037edba70d145a44a88ce0d3573089182fedb162
Resolves: ZSTAC-82153 Change-Id: Ib51c2e21553277416d1a9444be55aca2aa4b2fc4
add syncVmDeviceInfo when vm is running Resolves: ZSTAC-67275 Change-Id: I746e63786a677676676b6d6265657063666b7677
<fix>[compute]: add syncVmDeviceInfo when vm is running See merge request zstackio/zstack!9197
e8732a5 to
312bd83
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
core/src/main/java/org/zstack/core/cloudbus/ResourceDestinationMakerImpl.java (1)
80-93:⚠️ Potential issue | 🔴 Critical修复 getNodeInfo 中 put 返回值导致的空返回。
Map.put返回的是旧值,当前写法会让info变为null并直接返回,功能错误。应先创建NodeInfo,再 put,并返回新对象。🔧 建议修复
if (info == null) { ManagementNodeVO vo = dbf.findByUuid(nodeUuid, ManagementNodeVO.class); if (vo == null) { throw new ManagementNodeNotFoundException(nodeUuid); } - nodeHash.add(nodeUuid); - info = nodes.put(nodeUuid, new NodeInfo(vo)); + info = new NodeInfo(vo); + nodeHash.add(nodeUuid); + nodes.put(nodeUuid, info); } return info;
<fix>[vm]: add Destroying->Stopped state transition See merge request zstackio/zstack!9156
<fix>[i18n]: improve snapshot error message for unattached volume See merge request zstackio/zstack!9192
<fix>[zbs]: reduce mds connect timeout and enable tryNext for volume clients See merge request zstackio/zstack!9153
<fix>[vm]: use max of virtual and actual size for root disk allocation See merge request zstackio/zstack!9155
…talling In dual management node scenarios, concurrent modifications to the consistent hash ring from heartbeat reconciliation and canonical event callbacks can cause NodeHash/Nodes inconsistency, leading to message routing failures and task timeouts. Fix: (1) synchronized all ResourceDestinationMakerImpl methods to ensure atomic nodeHash+nodes updates, (2) added lifecycleLock in ManagementNodeManagerImpl to serialize heartbeat reconciliation with event callbacks, (3) added two-round delayed confirmation before removing nodes from hash ring to avoid race with NodeJoin events. Resolves: ZSTAC-77711 Change-Id: I3d33d53595dd302784dff17417a5b25f2d0f3426
312bd83 to
26b8b1a
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
header/src/main/java/org/zstack/header/vm/VmInstanceState.java (1)
166-172:⚠️ Potential issue | 🟠 Major建议为新增状态迁移提供兼容性开关(Line 166-172)
新增 Destroying → Stopped 的迁移会改变既有状态机行为,可能影响依赖旧行为的用户。建议增加版本/配置开关或仅对新安装/升级路径启用,避免存量行为被直接改变。As per coding guidelines, “之前的代码产生的行为不要直接去改动,即使是 bug 也可能用户有依赖,不应直接改动,可以考虑升级用户一套行为、新安装用户一套行为,开关控制等”.
core/src/main/java/org/zstack/core/cloudbus/ResourceDestinationMakerImpl.java (1)
80-93:⚠️ Potential issue | 🟠 Major修复
Map.put()返回值使用不当导致的空指针异常。第 89 行代码
info = nodes.put(nodeUuid, new NodeInfo(vo))使用了put()的返回值,但put()返回的是旧值(首次缓存时为null)而非新插入的对象。因此info在首次调用时被赋值为null,方法会返回null。后续在ManagementNodeManagerImpl和CloudBusImpl3中链式调用getNodeInfo(nodeUuid).getNodeIP()会抛出NullPointerException。应将新创建的
NodeInfo对象保存在独立变量中,再返回该对象:修复方案
nodeHash.add(nodeUuid); - info = nodes.put(nodeUuid, new NodeInfo(vo)); + NodeInfo newInfo = new NodeInfo(vo); + nodes.put(nodeUuid, newInfo); + info = newInfo;
🧹 Nitpick comments (2)
compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java (1)
8476-8496: 建议缓存 hostUuid 以避免异步日志漂移。
异步回调里再读self.getHostUuid()可能与发送时不一致,容易产生误导日志。建议在方法开头缓存并复用。♻️ 建议修改
private void syncVmDevicesAddressInfo(String vmUuid) { - if (self.getHostUuid() == null) { + String hostUuid = self.getHostUuid(); + if (hostUuid == null) { return; } SyncVmDeviceInfoMsg msg = new SyncVmDeviceInfoMsg(); msg.setVmInstanceUuid(vmUuid); - msg.setHostUuid(self.getHostUuid()); - bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, msg.getHostUuid()); + msg.setHostUuid(hostUuid); + bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, hostUuid); bus.send(msg, new CloudBusCallBack(msg) { `@Override` public void run(MessageReply reply) { if (!reply.isSuccess()) { logger.warn(String.format("Failed to sync vm device info for vm[uuid:%s], %s", vmUuid, reply.getError())); } else { logger.debug(String.format("Sent SyncVmDeviceInfoMsg for vm[uuid:%s] on host[uuid:%s]", - vmUuid, self.getHostUuid())); + vmUuid, hostUuid)); } } }); }header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java (1)
848-855: 存在空指针异常风险需要防御当
rootDiskOffering为null时,代码直接访问this.getImageSpec().getInventory().getSize()和getActualSize()。存在两个 NPE 风险:
- getInventory() 返回 null:ImageSpec 的 inventory 字段未经初始化,getInventory() 无空值检查
- 值拆箱 NPE:ImageInventory 中 size 和 actualSize 都是
Long类型(可为 null),拆箱到原始类型时会抛出 NPE建议增加防御性检查:
♻️ 建议的改进
public long getRootDiskAllocateSize() { if (rootDiskOffering == null) { + ImageInventory inv = this.getImageSpec().getInventory(); + if (inv == null) { + return 0L; + } - long virtualSize = this.getImageSpec().getInventory().getSize(); - long actualSize = this.getImageSpec().getInventory().getActualSize(); + long virtualSize = inv.getSize(); + long actualSize = inv.getActualSize(); return Math.max(virtualSize, actualSize); } return rootDiskOffering.getDiskSize(); }
Summary
Files Changed
ResourceDestinationMakerImpl.java— synchronized lock on all methodsResolves: ZSTAC-77711
sync from gitlab !9154