Fix WeChat CP MsgAudit SDK being destroyed and re-initialized on every API call#3934
Open
Fix WeChat CP MsgAudit SDK being destroyed and re-initialized on every API call#3934
Conversation
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix JVM crash issue related to session archiving API
Fix WeChat CP MsgAudit SDK being destroyed and re-initialized on every API call
Mar 17, 2026
🤖 Augment PR Summary总结:本 PR 修复企业微信会话存档(MsgAudit)SDK 在每次 API 调用后被反复销毁并重新初始化的问题,通过引用计数 + 过期判断实现 SDK 复用。 主要变更:
技术要点:销毁动作由“最后一次释放”改为“最后一次释放且已过期”,而过期后的清理依旧会在后续重新初始化( 🤖 Was this summary useful? React with 👍 or 👎 |
There was a problem hiding this comment.
Pull request overview
该 PR 修复企业微信会话存档(MsgAudit)SDK 在每次 API 调用结束时因引用计数归零而被无条件销毁、导致频繁重新初始化的问题,从而避免不必要的 SDK teardown/init 循环,提升调用效率并降低抖动。
Changes:
- 在
WxCpDefaultConfigImpl/WxCpRedisConfigImpl中,将DestroySdk的触发条件调整为“引用计数归零且 SDK 已过期”。 - 更新
WxCpConfigStorage中与引用计数/释放相关的 Javadoc,反映新的生命周期约定。 - 新增
WxCpDefaultConfigImplMsgAuditSdkTest并加入 TestNG suite,覆盖“未过期时不销毁、可复用”等场景。
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java | 引用计数归零时仅在 SDK 过期场景才销毁,避免每次调用都 destroy/init |
| weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java | 同步修复 Redis 配置实现中的销毁条件,保持行为一致 |
| weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java | 更新接口文档以匹配新的生命周期与返回值语义 |
| weixin-java-cp/src/test/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImplMsgAuditSdkTest.java | 新增单测验证 SDK 在未过期且 refCount 归零时不会被销毁、可被复用 |
| weixin-java-cp/src/test/resources/testng.xml | 将新增测试类纳入 TestNG 执行列表 |
| * | ||
| * @param sdk sdk id | ||
| * @return 减少后的引用计数,如果返回0表示SDK已被销毁,如果SDK不匹配返回-1 | ||
| * @return 减少后的引用计数,如果SDK不匹配返回-1 |
Comment on lines
+128
to
+160
| /** | ||
| * 验证:多线程场景下,多个并发调用的引用计数正确性 | ||
| */ | ||
| @Test | ||
| public void testConcurrentRefCounting() throws Exception { | ||
| 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 未过期,全部引用释放后不应被销毁"); | ||
| } |
| Assert.assertEquals(refCountAfterRelease, 0, "引用计数应为 0"); | ||
| } | ||
|
|
||
| /** |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
The ref-counted SDK lifecycle methods (
releaseMsgAuditSdk/decrementMsgAuditSdkRefCount) were unconditionally callingFinance.DestroySdk()whenever the reference count hit zero — regardless of whether the SDK had actually expired. Since each high-level method (e.g.getChatRecordPlainText) acquires and releases exactly one ref, every single call resulted in a full SDK teardown + re-init cycle.Root cause
Fix
Only destroy the SDK when ref count hits zero and the SDK has already expired. While valid, the SDK is kept cached so subsequent calls reuse it without re-initialization.
Expiry-time cleanup still happens naturally:
acquireMsgAuditSdk()returns 0 for expired SDKs, triggeringinitSdk()which callsupdateMsgAuditSdk()— which destroys the old handle before caching the new one.Changes
WxCpDefaultConfigImpl— guardFinance.DestroySdkin bothreleaseMsgAuditSdkanddecrementMsgAuditSdkRefCountwithisMsgAuditSdkExpired()WxCpRedisConfigImpl— same fixWxCpConfigStorage— updated Javadoc to reflect the corrected lifecycle contractWxCpDefaultConfigImplMsgAuditSdkTest— new unit tests covering: SDK preserved after last-ref release when not expired; SDK reuse on nextacquireMsgAuditSdkcall; concurrent ref-count correctnessOriginal prompt
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.