-
Notifications
You must be signed in to change notification settings - Fork 32
fix 模块 pod 替换导致 JVM 进程内模块实例消失 #247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
1bdc7ea
afdd6ff
a74e808
e6aee62
b2b3508
55f2fb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,16 +18,20 @@ | |
|
|
||
| import com.alipay.sofa.ark.api.ClientResponse; | ||
| import com.alipay.sofa.ark.api.ResponseCode; | ||
| import com.alipay.sofa.ark.common.util.BizIdentityUtils; | ||
| import com.alipay.sofa.ark.common.util.StringUtils; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.builtin.BuiltinCommand; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.builtin.handler.UninstallBizHandler.Input; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.coordinate.BizOpsPodCoordinator; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.meta.AbstractCommandHandler; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.meta.Command; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.meta.Output; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizMeta; | ||
| import com.alipay.sofa.koupleless.arklet.core.command.meta.bizops.ArkBizOps; | ||
| import com.alipay.sofa.koupleless.arklet.core.common.exception.ArkletRuntimeException; | ||
| import com.alipay.sofa.koupleless.arklet.core.common.exception.CommandValidationException; | ||
| import com.alipay.sofa.koupleless.common.log.ArkletLogger; | ||
| import com.alipay.sofa.koupleless.common.log.ArkletLoggerFactory; | ||
|
|
||
| /** | ||
| * <p>UninstallBizHandler class.</p> | ||
|
|
@@ -37,15 +41,25 @@ | |
| * @version 1.0.0 | ||
| */ | ||
| public class UninstallBizHandler extends AbstractCommandHandler<Input, ClientResponse> | ||
| implements ArkBizOps { | ||
| implements ArkBizOps { | ||
|
|
||
| private static final ArkletLogger LOGGER = ArkletLoggerFactory.getDefaultLogger(); | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override | ||
| public Output<ClientResponse> handle(Input input) { | ||
| try { | ||
| ClientResponse res = getOperationService().uninstall(input.getBizName(), | ||
| input.getBizVersion()); | ||
| String bizIdentity = BizIdentityUtils.generateBizIdentity(input.getBizName(), input.getBizVersion()); | ||
| String bizModelVersion = input.getBizModelVersion(); | ||
| if (!BizOpsPodCoordinator.canAccess(bizIdentity, bizModelVersion)) { | ||
| LOGGER.error( | ||
| "can not access biz because the command is expired. bizIdentity: {}, bizModelVersion: {}", | ||
| bizIdentity, bizModelVersion); | ||
| return Output.ofFailed("can not access biz because the command is expired"); | ||
| } | ||
| ClientResponse res = getOperationService().uninstall(input.getBizName(), input.getBizVersion()); | ||
| if (ResponseCode.SUCCESS.equals(res.getCode())) { | ||
| BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion); | ||
| return Output.ofSuccess(res); | ||
|
Comment on lines
63
to
65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible NPE on remove when bizModelVersion is null (ConcurrentHashMap disallows null value).
Apply coordinator fix shown in that file’s comment; alternatively, add a local guard: - BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion);
+ if (!StringUtils.isEmpty(bizModelVersion)) {
+ BizOpsPodCoordinator.remove(bizIdentity, bizModelVersion);
+ } else {
+ // legacy client: best-effort cleanup; safe to remove by key
+ BizOpsPodCoordinator.remove(bizIdentity, StringUtils.EMPTY_STRING);
+ }
|
||
| } else { | ||
| return Output.ofFailed(res, "uninstall biz not success!"); | ||
|
|
@@ -67,7 +81,7 @@ public void validate(Input input) throws CommandValidationException { | |
| notBlank(input.getBizName(), "bizName should not be blank"); | ||
| notBlank(input.getBizVersion(), "bizVersion should not be blank"); | ||
| isTrue(!input.isAsync() || !StringUtils.isEmpty(input.getRequestId()), | ||
| "requestId should not be blank when async is true"); | ||
| "requestId should not be blank when async is true"); | ||
| } | ||
|
|
||
| public static class Input extends ArkBizMeta { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * contributor license agreements. See the NOTICE file distributed with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * this work for additional information regarding copyright ownership. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * (the "License"); you may not use this file except in compliance with | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * the License. You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * limitations under the License. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.alipay.sofa.koupleless.arklet.core.command.coordinate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.alipay.sofa.ark.common.util.StringUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * BizOpsPodCoordinator class. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @author liuzhuoheng | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @since 2025/7/15 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @version 1.0.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class BizOpsPodCoordinator { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * bizIdentityLockMap | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * key: bizIdentity, value: bizModelVersion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final Map<String, String> bizIdentityLockMap = new ConcurrentHashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * save. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param bizIdentity a {@link java.lang.String} object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param bizModelVersion a {@link java.lang.String} object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static void save(String bizIdentity, String bizModelVersion) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (StringUtils.isEmpty(bizIdentity)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (StringUtils.isEmpty(bizModelVersion)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bizModelVersion = StringUtils.EMPTY_STRING; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bizIdentityLockMap.put(bizIdentity, bizModelVersion); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate bizIdentity to avoid CHM NPE; normalize inputs.
Apply: public static void save(String bizIdentity, String bizModelVersion) {
+ if (StringUtils.isEmpty(bizIdentity)) {
+ // ignore invalid key or consider throwing IllegalArgumentException
+ return;
+ }
if (StringUtils.isEmpty(bizModelVersion)) {
bizModelVersion = StringUtils.EMPTY_STRING;
}
bizIdentityLockMap.put(bizIdentity, bizModelVersion);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * remove. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param bizIdentity a {@link java.lang.String} object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param bizModelVersion a {@link java.lang.String} object | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static void remove(String bizIdentity, String bizModelVersion) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (StringUtils.isEmpty(bizIdentity)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (StringUtils.isEmpty(bizModelVersion)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bizIdentityLockMap.remove(bizIdentity); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bizIdentityLockMap.remove(bizIdentity, bizModelVersion); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * canAccess. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * </p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 判断是否可以访问指定的业务模块,基于业务模块版本的协调机制 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param bizIdentity 业务模块标识 (bizName:bizVersion) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param bizModelVersion 业务模块模型版本,用于命令协调和防止过期命令执行 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return 是否允许访问该业务模块 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static boolean canAccess(String bizIdentity, String bizModelVersion) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 判断逻辑说明: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Case 1: bizModelVersion 为空 - 兼容性处理,允许访问(兼容旧版本 module-controller,arktcl, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // pod-not-exist 和 pod 紧急删除场景) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Case 2: bizIdentityLockMap 中没有该 bizIdentity 的记录,允许访问(安装时不带 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // BizModelVersion,卸载时带上 BizModelVersion) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Case 3: bizIdentityLockMap 中的版本与当前请求的版本匹配 - 版本一致,确认卸载的是该 Biz,允许访问 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 只有当 bizModelVersion 不为空且存在 bizModelVersion 且不匹配时,才拒绝访问(防止旧的卸载命令执行) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return StringUtils.isEmpty(bizModelVersion) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || StringUtils.isEmpty(bizIdentityLockMap.get(bizIdentity)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| || bizIdentityLockMap.get(bizIdentity).equals(bizModelVersion); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+90
to
+101
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainFix NPE risks in canAccess: null key to CHM and TOCTOU on double get.
Proposed fix: guard bizIdentity and read the map once; compare safely. - public static boolean canAccess(String bizIdentity, String bizModelVersion) {
- // 判断逻辑说明:
- // Case 1: bizModelVersion 为空 - 兼容性处理,允许访问(兼容旧版本 module-controller,arktcl,
- // pod-not-exist 和 pod 紧急删除场景)
- // Case 2: bizIdentityLockMap 中没有该 bizIdentity 的记录,允许访问(安装时不带
- // BizModelVersion,卸载时带上 BizModelVersion)
- // Case 3: bizIdentityLockMap 中的版本与当前请求的版本匹配 - 版本一致,确认卸载的是该 Biz,允许访问
- // 只有当 bizModelVersion 不为空且存在 bizModelVersion 且不匹配时,才拒绝访问(防止旧的卸载命令执行)
- return StringUtils.isEmpty(bizModelVersion)
- || StringUtils.isEmpty(bizIdentityLockMap.get(bizIdentity))
- || bizIdentityLockMap.get(bizIdentity).equals(bizModelVersion);
- }
+ public static boolean canAccess(String bizIdentity, String bizModelVersion) {
+ // Case 1: legacy / compatibility
+ if (StringUtils.isEmpty(bizModelVersion)) {
+ return true;
+ }
+ // Guard against null/blank identity (avoid CHM NPE). Choose safer default: deny.
+ if (StringUtils.isEmpty(bizIdentity)) {
+ return false;
+ }
+ final String recorded = bizIdentityLockMap.get(bizIdentity);
+ // Case 2: no record or empty record -> allow
+ // Case 3: exact match -> allow
+ return StringUtils.isEmpty(recorded) || bizModelVersion.equals(recorded);
+ }Optional: if you prefer permissive behavior for blank bizIdentity, return true instead — please confirm desired policy. 🏁 Script executed: #!/bin/bash
set -euo pipefail
file="arklet-core/src/main/java/com/alipay/sofa/koupleless/arklet/core/command/coordinate/BizOpsPodCoordinator.java"
echo "PWD: $(pwd)"
if [ -f "$file" ]; then
echo "=== FOUND: $file ==="
nl -ba "$file" | sed -n '1,260p'
else
echo "=== FILE NOT FOUND: $file ==="
echo "Searching repo for class / filename..."
rg -n --hidden --glob '!**/target/**' 'BizOpsPodCoordinator' -S || true
rg -n --hidden --glob '!**/target/**' 'BizOpsPodCoordinator.java' -S || true
fi
echo
echo "=== Search for bizIdentityLockMap declaration/usages ==="
rg -n --hidden --glob '!**/target/**' 'bizIdentityLockMap' -S || true
echo
echo "=== Search for canAccess(...) call sites ==="
rg -n --hidden --glob '!**/target/**' 'canAccess\(' -S || trueLength of output: 189 🏁 Script executed: #!/bin/bash
set -euo pipefail
file="arklet-core/src/main/java/com/alipay/sofa/koupleless/arklet/core/command/coordinate/BizOpsPodCoordinator.java"
echo "PWD: $(pwd)"
if [ -f "$file" ]; then
echo "=== FOUND: $file ==="
sed -n '1,240p' "$file"
else
echo "=== FILE NOT FOUND: $file ==="
echo "Searching for BizOpsPodCoordinator..."
rg -n --hidden --glob '!**/target/**' 'class\s+BizOpsPodCoordinator' -S || true
fi
echo
echo "=== Search for bizIdentityLockMap declaration/usages ==="
rg -n --hidden --glob '!**/target/**' 'bizIdentityLockMap' -S || true
echo
echo "=== Search for canAccess(...) call sites ==="
rg -n --hidden --glob '!**/target/**' 'canAccess\(' -S || trueLength of output: 3944 Fix NPE & TOCTOU in canAccess — guard null identity and read map once ConcurrentHashMap.get(null) throws NPE; calling get() twice races with concurrent remove() and can cause a .equals(...) NPE. Read the map once and validate bizIdentity before lookup. - public static boolean canAccess(String bizIdentity, String bizModelVersion) {
- // 判断逻辑说明:
- // Case 1: bizModelVersion 为空 - 兼容性处理,允许访问(兼容旧版本 module-controller,arktcl,
- // pod-not-exist 和 pod 紧急删除场景)
- // Case 2: bizIdentityLockMap 中没有该 bizIdentity 的记录,允许访问(安装时不带
- // BizModelVersion,卸载时带上 BizModelVersion)
- // Case 3: bizIdentityLockMap 中的版本与当前请求的版本匹配 - 版本一致,确认卸载的是该 Biz,允许访问
- // 只有当 bizModelVersion 不为空且存在 bizModelVersion 且不匹配时,才拒绝访问(防止旧的卸载命令执行)
- return StringUtils.isEmpty(bizModelVersion)
- || StringUtils.isEmpty(bizIdentityLockMap.get(bizIdentity))
- || bizIdentityLockMap.get(bizIdentity).equals(bizModelVersion);
- }
+ public static boolean canAccess(String bizIdentity, String bizModelVersion) {
+ // Case 1: legacy / compatibility
+ if (StringUtils.isEmpty(bizModelVersion)) {
+ return true;
+ }
+ // Guard against null/blank identity (avoid CHM NPE). Choose safer default: deny.
+ if (StringUtils.isEmpty(bizIdentity)) {
+ return false;
+ }
+ final String recorded = bizIdentityLockMap.get(bizIdentity);
+ // Case 2: no record or empty record -> allow
+ // Case 3: exact match -> allow
+ return StringUtils.isEmpty(recorded) || bizModelVersion.equals(recorded);
+ }If permissive behavior for blank bizIdentity is preferred, change the blank-identity branch to return true — confirm policy. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Access check allows empty bizModelVersion — consider stricter rule behind a flag.
Current logic permits uninstall when request version is empty, even if a non‑empty version is recorded; this weakens protection against stale commands. Suggest denying when map has a non‑empty version and request is empty, gated by a compat flag.
Proposed guard:
and inside coordinator (see separate comment) refine empty‑version handling or make it configurable.
🤖 Prompt for AI Agents