Skip to content

【企业微信】修复会话存档 SDK 每次 API 调用后被销毁并重新初始化的问题#3934

Open
Copilot wants to merge 5 commits into
developfrom
copilot/fix-jvm-crash-issue
Open

【企业微信】修复会话存档 SDK 每次 API 调用后被销毁并重新初始化的问题#3934
Copilot wants to merge 5 commits into
developfrom
copilot/fix-jvm-crash-issue

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 17, 2026

releaseMsgAuditSdk / decrementMsgAuditSdkRefCount 方法在引用计数归零时无条件调用 Finance.DestroySdk(),与 SDK 是否已过期无关。由于每个高层方法(如 getChatRecordPlainText)恰好获取并释放一个引用,导致每次 API 调用都触发完整的 SDK 销毁 + 重新初始化循环。

根本原因

// 修复前:每次最后一个引用释放时都销毁 SDK
if (newCount == 0 && this.msgAuditSdk == sdk) {
    Finance.DestroySdk(sdk);   // ← 每次 API 调用结束后都会触发
    this.msgAuditSdk = 0;
}

修复方案

仅当引用计数归零 SDK 已过期时才销毁 SDK。SDK 有效期间保留缓存,供后续调用直接复用,无需重新初始化。

// 修复后:仅在过期时销毁
if (newCount == 0 && this.msgAuditSdk == sdk && isMsgAuditSdkExpired()) {
    Finance.DestroySdk(sdk);
    this.msgAuditSdk = 0;
}

过期清理仍能自然触发:acquireMsgAuditSdk() 对已过期 SDK 返回 0,从而触发 initSdk()initSdk() 调用 updateMsgAuditSdk() 在缓存新句柄前销毁旧句柄。

变更内容

  • WxCpDefaultConfigImpl — 在 releaseMsgAuditSdkdecrementMsgAuditSdkRefCount 中均加入 isMsgAuditSdkExpired() 条件守卫
  • WxCpRedisConfigImpl — 同上
  • WxCpConfigStorage — 更新 Javadoc,反映正确的生命周期约定;完善 decrementMsgAuditSdkRefCount@return 说明(SDK 不匹配或引用计数已为 0 时均返回 -1);合并 develop 分支中新增的 @Deprecated 标注
  • WxCpDefaultConfigImplMsgAuditSdkTest — 新增单元测试,覆盖以下场景:
    • SDK 未过期时引用计数归零后不被销毁
    • 释放后下次调用可直接复用已缓存 SDK
    • 多次串行获取/释放的引用计数正确性(testMultipleAcquireAndReleaseSequential
    • SDK 已过期且引用计数归零时正确进入销毁分支(testReleaseMsgAuditSdkShouldDestroyWhenExpired,在无原生库的测试环境中通过捕获 UnsatisfiedLinkError 验证代码路径)
Original prompt

This section details on the original issue you should resolve

<issue_title>【企业微信】最新的会话存档相关API解决JVM崩溃问题的commit,是否存在频繁初始化及频繁销毁sdk的问题</issue_title>
<issue_description>如上,我看每个方法都执行完就消费,是否有影响?

Image</issue_description>

Comments on the Issue (you are @copilot in this section)

Custom agent used: 全部用中文
需要用中文,包括PR标题和分析总结过程


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

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
Copilot AI requested a review from binarywang March 17, 2026 07:32
@binarywang binarywang marked this pull request as ready for review March 17, 2026 07:33
Copilot AI review requested due to automatic review settings March 17, 2026 07:33
@binarywang binarywang removed their request for review March 17, 2026 07:34
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Mar 17, 2026

🤖 Augment PR Summary

总结:本 PR 修复企业微信会话存档(MsgAudit)SDK 在每次 API 调用后被反复销毁并重新初始化的问题,通过引用计数 + 过期判断实现 SDK 复用。

主要变更:

  • WxCpDefaultConfigImplWxCpRedisConfigImpl 中,releaseMsgAuditSdk/decrementMsgAuditSdkRefCount 仅在“引用计数归零且 SDK 已过期”时才调用 Finance.DestroySdk()
  • 当 SDK 未过期且引用计数降到 0 时,保留 SDK 句柄与过期时间,供后续调用直接复用,避免频繁 init/destroy 循环。
  • 更新 WxCpConfigStorage 的 Javadoc,明确“归零不一定销毁”的生命周期契约。
  • 新增 WxCpDefaultConfigImplMsgAuditSdkTest,覆盖:有效 SDK 的 acquire 行为、过期 SDK 的 acquire 返回值、release 后不销毁与再次复用等核心场景。
  • testng.xml 中加入上述新测试类。

技术要点:销毁动作由“最后一次释放”改为“最后一次释放且已过期”,而过期后的清理依旧会在后续重新初始化(updateMsgAuditSdk 更新句柄)过程中自然发生,从而降低 SDK 生命周期抖动。

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

* 验证:多线程场景下,多个并发调用的引用计数正确性
*/
@Test
public void testConcurrentRefCounting() throws Exception {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里方法名/注释强调“多线程/并发”,但实际是单线程顺序调用 acquireMsgAuditSdk()/releaseMsgAuditSdk(),无法覆盖竞态条件下的正确性。可能会让读者误以为已有并发测试覆盖,建议确认是否需要补充真实并发场景或调整描述。

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

/**
* 减少会话存档SDK的引用计数
* 当引用计数降为0时,自动销毁SDK以释放资源
* 当引用计数降为0且SDK已过期时,才自动销毁SDK以释放资源
Copy link
Copy Markdown

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

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
}

/**
@binarywang binarywang added this to the 4.8.3 milestone Apr 11, 2026
@binarywang binarywang modified the milestones: 4.8.3, 4.8.4 May 11, 2026
@binarywang
Copy link
Copy Markdown
Owner

@copilot 更新当前PR的标题和描述为中文,并解决合并冲突问题

@binarywang
Copy link
Copy Markdown
Owner

@copilot apply changes based on the comments in this thread

- 解决与 develop 分支的合并冲突(WxCpConfigStorage.java)
- 合并 develop 中对引用计数接口方法增加的 @deprecated 标注
- 修正测试方法名:testConcurrentRefCounting → testMultipleAcquireAndReleaseSequential,
  去除误导性的'并发'描述,改为准确的串行多次获取/释放验证
- 新增 testReleaseMsgAuditSdkShouldDestroyWhenExpired:验证 SDK 过期且
  引用计数归零时,代码路径正确进入销毁分支(在无原生库的测试环境中
  通过捕获 UnsatisfiedLinkError 证明 Finance.DestroySdk 被调用)
- 完善 decrementMsgAuditSdkRefCount 的 @return Javadoc 描述

Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 13, 2026

@copilot 更新当前PR的标题和描述为中文,并解决合并冲突问题

已完成(commit a8d6c1a):

  1. 解决合并冲突:与 develop 分支合并,解决了 WxCpConfigStorage.java 中的冲突,保留了 develop 新增的 @Deprecated 标注,同时保留了本 PR 中对 @return 描述的改进。

  2. 修复代码审查反馈

    • testConcurrentRefCounting 重命名为 testMultipleAcquireAndReleaseSequential,去除了误导性的"并发"描述
    • 新增 testReleaseMsgAuditSdkShouldDestroyWhenExpired 测试用例,覆盖"SDK 过期且引用计数归零时应进入销毁分支"的逻辑(由于 Finance.DestroySdk 是原生方法,在无原生库的 CI 环境中通过捕获 UnsatisfiedLinkError 来验证代码路径正确进入销毁分支,并断言引用计数已提前递减;在有原生库的环境中则直接断言字段清零)
    • 完善了 decrementMsgAuditSdkRefCount@return Javadoc,补充了"引用计数已为0时也返回-1"的说明

全部 9 个测试通过,checkstyle 检查无误。

Copilot AI changed the title Fix WeChat CP MsgAudit SDK being destroyed and re-initialized on every API call 【企业微信】修复会话存档 SDK 每次 API 调用后被销毁并重新初始化的问题 May 13, 2026
Copilot AI requested a review from binarywang May 13, 2026 02:02
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 13, 2026

@copilot apply changes based on the comments in this thread

代码评审中的三项变更(Javadoc @return 描述补充、测试方法重命名、补充过期销毁测试用例)已在上一次提交 a8d6c1a 中全部完成。

本次另外同步了文档(commit ed121eb):

  • docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md:将"计数归零时:SDK 被自动释放"修正为"计数归零且 SDK 已过期时才销毁,未过期时保留缓存供复用",与实现语义一致
  • docs/CP_MSG_AUDIT_THREADLOCAL_LIFECYCLE_REFACTOR.md:标注原"频繁初始化/销毁"问题已在 4.8.3.B+ 中修复

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【企业微信】最新的会话存档相关API解决JVM崩溃问题的commit,是否存在频繁初始化及频繁销毁sdk的问题

3 participants