-
-
Notifications
You must be signed in to change notification settings - Fork 9.1k
Fix WeChat CP MsgAudit SDK being destroyed and re-initialized on every API call #3934
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
Open
Copilot
wants to merge
2
commits into
develop
Choose a base branch
from
copilot/fix-jvm-crash-issue
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+222
−15
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
204 changes: 204 additions & 0 deletions
204
.../src/test/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImplMsgAuditSdkTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| package me.chanjar.weixin.cp.config.impl; | ||
|
|
||
| import org.testng.Assert; | ||
| import org.testng.annotations.BeforeMethod; | ||
| import org.testng.annotations.Test; | ||
|
|
||
| import java.lang.reflect.Field; | ||
|
|
||
| /** | ||
| * 测试 WxCpDefaultConfigImpl 中会话存档 SDK 引用计数的正确性 | ||
| * 验证修复:SDK 在引用计数降为 0 但尚未过期时,不应被销毁 | ||
| * | ||
| * @author GitHub Copilot | ||
| */ | ||
| public class WxCpDefaultConfigImplMsgAuditSdkTest { | ||
|
|
||
| /** | ||
| * 用于测试的未过期时间偏移量(毫秒),模拟 SDK 有效状态 | ||
| */ | ||
| private static final long VALID_EXPIRATION_TIME_OFFSET = 7_000_000L; | ||
|
|
||
| private WxCpDefaultConfigImpl config; | ||
|
|
||
| @BeforeMethod | ||
| public void setUp() { | ||
| config = new WxCpDefaultConfigImpl(); | ||
| } | ||
|
|
||
| /** | ||
| * 通过反射设置内部字段 | ||
| */ | ||
| private void setField(String fieldName, Object value) throws Exception { | ||
| Field field = WxCpDefaultConfigImpl.class.getDeclaredField(fieldName); | ||
| field.setAccessible(true); | ||
| field.set(config, value); | ||
| } | ||
|
|
||
| /** | ||
| * 通过反射获取内部字段值 | ||
| */ | ||
| private Object getField(String fieldName) throws Exception { | ||
| Field field = WxCpDefaultConfigImpl.class.getDeclaredField(fieldName); | ||
| field.setAccessible(true); | ||
| return field.get(config); | ||
| } | ||
|
|
||
| /** | ||
| * 验证 acquireMsgAuditSdk 在 SDK 有效时能正确返回 SDK 并增加引用计数 | ||
| */ | ||
| @Test | ||
| public void testAcquireMsgAuditSdkWhenSdkValid() throws Exception { | ||
| long fakeSdk = 12345L; | ||
| // 设置一个有效的(未过期的)SDK | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET); | ||
| setField("msgAuditSdkRefCount", 0); | ||
|
|
||
| long acquired = config.acquireMsgAuditSdk(); | ||
|
|
||
| Assert.assertEquals(acquired, fakeSdk, "应返回已缓存的有效 SDK"); | ||
| int refCount = (int) getField("msgAuditSdkRefCount"); | ||
| Assert.assertEquals(refCount, 1, "引用计数应增加到 1"); | ||
| } | ||
|
|
||
| /** | ||
| * 验证 acquireMsgAuditSdk 在 SDK 已过期时返回 0 | ||
| */ | ||
| @Test | ||
| public void testAcquireMsgAuditSdkWhenSdkExpired() throws Exception { | ||
| long fakeSdk = 12345L; | ||
| // 设置已过期的 SDK | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkExpiresTime", System.currentTimeMillis() - 1000L); | ||
| setField("msgAuditSdkRefCount", 0); | ||
|
|
||
| long acquired = config.acquireMsgAuditSdk(); | ||
|
|
||
| Assert.assertEquals(acquired, 0L, "SDK 已过期,应返回 0"); | ||
| int refCount = (int) getField("msgAuditSdkRefCount"); | ||
| Assert.assertEquals(refCount, 0, "引用计数不应改变"); | ||
| } | ||
|
|
||
| /** | ||
| * 核心测试:验证当引用计数降为 0 但 SDK 尚未过期时,SDK 不会被销毁 | ||
| * 这是修复 issue 的关键验证:避免每次 API 调用后频繁销毁和重新初始化 SDK | ||
| */ | ||
| @Test | ||
| public void testReleaseMsgAuditSdkShouldNotDestroyWhenNotExpired() throws Exception { | ||
| long fakeSdk = 12345L; | ||
| // 设置一个有效的(未过期的)SDK,引用计数为 1 | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET); | ||
| setField("msgAuditSdkRefCount", 1); | ||
|
|
||
| // 释放引用,引用计数应降为 0,但 SDK 尚未过期,不应被销毁 | ||
| config.releaseMsgAuditSdk(fakeSdk); | ||
|
|
||
| long sdkAfterRelease = (long) getField("msgAuditSdk"); | ||
| int refCountAfterRelease = (int) getField("msgAuditSdkRefCount"); | ||
|
|
||
| Assert.assertEquals(sdkAfterRelease, fakeSdk, "SDK 尚未过期,引用计数归零后不应被销毁,应继续缓存"); | ||
| Assert.assertEquals(refCountAfterRelease, 0, "引用计数应为 0"); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| * 验证:SDK 在未过期、引用计数为 0 时,下次调用 acquireMsgAuditSdk 应直接复用,无需重新初始化 | ||
| * 这是修复后的核心行为:避免频繁初始化 | ||
| */ | ||
| @Test | ||
| public void testSdkReuseAfterReleaseWhenNotExpired() throws Exception { | ||
| long fakeSdk = 99999L; | ||
| // 模拟:SDK 有效,引用计数为 1(正在被使用) | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET); | ||
| setField("msgAuditSdkRefCount", 1); | ||
|
|
||
| // 模拟方法调用结束,释放引用 | ||
| config.releaseMsgAuditSdk(fakeSdk); | ||
|
|
||
| // 模拟下一次方法调用,应该直接复用缓存的 SDK | ||
| long reacquired = config.acquireMsgAuditSdk(); | ||
|
|
||
| Assert.assertEquals(reacquired, fakeSdk, "SDK 应被复用,而不是返回 0(需要重新初始化)"); | ||
| int refCount = (int) getField("msgAuditSdkRefCount"); | ||
| Assert.assertEquals(refCount, 1, "复用后引用计数应为 1"); | ||
| } | ||
|
|
||
| /** | ||
| * 验证:多线程场景下,多个并发调用的引用计数正确性 | ||
| */ | ||
| @Test | ||
| public void testConcurrentRefCounting() throws Exception { | ||
|
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. |
||
| long fakeSdk = 77777L; | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkExpiresTime", System.currentTimeMillis() + VALID_EXPIRATION_TIME_OFFSET); | ||
| setField("msgAuditSdkRefCount", 0); | ||
|
|
||
| // 模拟 3 个并发调用同时持有 SDK | ||
| long sdk1 = config.acquireMsgAuditSdk(); | ||
| long sdk2 = config.acquireMsgAuditSdk(); | ||
| long sdk3 = config.acquireMsgAuditSdk(); | ||
|
|
||
| Assert.assertEquals(sdk1, fakeSdk); | ||
| Assert.assertEquals(sdk2, fakeSdk); | ||
| Assert.assertEquals(sdk3, fakeSdk); | ||
| Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 3, "应有 3 个引用"); | ||
|
|
||
| // 逐一释放 | ||
| config.releaseMsgAuditSdk(fakeSdk); | ||
| Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 2, "释放一个后应有 2 个引用"); | ||
| Assert.assertEquals((long) getField("msgAuditSdk"), fakeSdk, "SDK 仍有引用,不应被销毁"); | ||
|
|
||
| config.releaseMsgAuditSdk(fakeSdk); | ||
| Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 1, "释放两个后应有 1 个引用"); | ||
|
|
||
| config.releaseMsgAuditSdk(fakeSdk); | ||
| Assert.assertEquals((int) getField("msgAuditSdkRefCount"), 0, "全部释放后引用计数应为 0"); | ||
| // SDK 未过期,不应被销毁 | ||
| Assert.assertEquals((long) getField("msgAuditSdk"), fakeSdk, "SDK 未过期,全部引用释放后不应被销毁"); | ||
| } | ||
|
Comment on lines
+128
to
+160
|
||
|
|
||
| /** | ||
| * 验证 incrementMsgAuditSdkRefCount 在 SDK 匹配时正确增加引用计数 | ||
| */ | ||
| @Test | ||
| public void testIncrementRefCount() throws Exception { | ||
| long fakeSdk = 11111L; | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkRefCount", 2); | ||
|
|
||
| int result = config.incrementMsgAuditSdkRefCount(fakeSdk); | ||
|
|
||
| Assert.assertEquals(result, 3, "引用计数应增加到 3"); | ||
| } | ||
|
|
||
| /** | ||
| * 验证 incrementMsgAuditSdkRefCount 在 SDK 不匹配时返回 -1 | ||
| */ | ||
| @Test | ||
| public void testIncrementRefCountWithWrongSdk() throws Exception { | ||
| setField("msgAuditSdk", 11111L); | ||
| setField("msgAuditSdkRefCount", 2); | ||
|
|
||
| int result = config.incrementMsgAuditSdkRefCount(99999L); | ||
|
|
||
| Assert.assertEquals(result, -1, "SDK 不匹配时应返回 -1"); | ||
| } | ||
|
|
||
| /** | ||
| * 验证 getMsgAuditSdkRefCount 的正确性 | ||
| */ | ||
| @Test | ||
| public void testGetMsgAuditSdkRefCount() throws Exception { | ||
| long fakeSdk = 55555L; | ||
| setField("msgAuditSdk", fakeSdk); | ||
| setField("msgAuditSdkRefCount", 5); | ||
|
|
||
| int count = config.getMsgAuditSdkRefCount(fakeSdk); | ||
| Assert.assertEquals(count, 5, "应返回正确的引用计数"); | ||
|
|
||
| int wrongCount = config.getMsgAuditSdkRefCount(99L); | ||
| Assert.assertEquals(wrongCount, -1, "SDK 不匹配时应返回 -1"); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
本次语义变更后,仓库里其它外部文档/示例中“引用计数归零即释放 SDK”的表述(例如
docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md的实现原理段落)可能会与这里不一致。建议检查并同步相关文档,避免用户误解 SDK 生命周期行为。Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.