{"record":{"id":"035af7973dedb3ff","repo":"binarywang/WxJava","slug":"sdk-closeallsdks","errorCode":null,"errorMessage":"线程 [{}] 获取会话存档SDK失败，请检查是否已调用 closeAllSdks()","messagePattern":"线程 \\[(.+?)\\] 获取会话存档SDK失败，请检查是否已调用 closeAllSdks\\(\\)","errorType":"exception","errorClass":"WxErrorException","httpStatus":null,"severity":"error","filePath":"weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java","lineNumber":89,"sourceCode":"  }\n\n  /**\n   * 获取当前线程的 SDK，不存在则初始化。\n   * SDK 在线程内跨调用复用，无需每次重新初始化。\n   *\n   * @return sdk id\n   * @throws WxErrorException 初始化失败时抛出异常\n   */\n  private long getOrInitThreadLocalSdk() throws WxErrorException {\n    Long sdk = threadLocalSdk.get();\n    if (sdk != null && sdk > 0) {\n      // 校验句柄是否仍受管理：closeAllSdks() 后其他线程 ThreadLocal 可能保留已销毁的 id\n      if (managedSdks.contains(sdk)) {\n        return sdk;\n      }\n      log.warn(\"线程 [{}] 发现已失效的会话存档SDK句柄 sdk={}，请检查调用逻辑\", Thread.currentThread().getName(), sdk);\n      threadLocalSdk.remove();\n      throw new WxErrorException(\"线程 [\" + Thread.currentThread().getName() + \"] 获取会话存档SDK失败，请检查是否已调用 closeAllSdks()\");\n    }\n    long newSdk = createSdk();\n    threadLocalSdk.set(newSdk);\n    managedSdks.add(newSdk);\n    log.info(\"线程 [{}] 初始化会话存档SDK成功，sdk={}\", Thread.currentThread().getName(), newSdk);\n    return newSdk;\n  }\n\n  /**\n   * 创建并初始化一个新的会话存档 SDK 实例。\n   * <p>通常通过 {@link #getOrInitThreadLocalSdk()} 间接调用以复用 ThreadLocal 中的实例；\n   * 旧版直接暴露 sdk 的 API（如 {@link #getChatDatas}）也会直接调用本方法，此时 SDK 由调用方自行管理。</p>\n   * <p>Finance.loadingLibraries() 底层依赖 System.load()，JVM 保证同一库不重复加载，多线程并发调用安全。</p>\n   */\n  private long createSdk() throws WxErrorException {\n    WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();\n\n    String configPath = configStorage.getMsgAuditLibPath();","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/binarywang/WxJava/blob/1c43293a3c2c9d7e91304b6d037fb017f680d0c6/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java#L71-L107","documentation":"Thrown as WxErrorException (checked) in getOrInitThreadLocalSdk when the ThreadLocal contains a non-null SDK handle that is no longer in the managedSdks set. This means closeAllSdks() was called (likely by another thread) which destroyed and unregistered all SDK handles, but the current thread's ThreadLocal still holds a stale reference to an already-destroyed native SDK. After logging a warning and removing the stale handle, the method refuses to proceed and throws.","triggerScenarios":"Thread A calls getOrInitThreadLocalSdk() after Thread B (or the same thread in a different lifecycle phase) called closeAllSdks(). Thread A's ThreadLocal still holds the old SDK id, which is no longer tracked in managedSdks because closeAllSdks removed it. This is a lifecycle management issue, not a normal runtime error.","commonSituations":"Application uses a thread pool (e.g., Tomcat request threads, scheduled executor) where threads are reused. After closeAllSdks() is called (e.g., during shutdown or config refresh), pooled threads that previously used the SDK still hold the destroyed handle in their ThreadLocal. On the next use, this error fires.","solutions":["After calling closeAllSdks(), do not reuse threads that previously held SDK handles without clearing their state. Alternatively, restart the thread pool.","Catch this specific error and retry: the exception text contains '获取会话存档SDK失败'. On retry, getOrInitThreadLocalSdk will create a fresh SDK since the ThreadLocal was cleared.","Review the lifecycle: only call closeAllSdks() during full application shutdown, not during runtime config refresh."],"exampleFix":"// before\ncpService.getMsgAuditService().closeAllSdks();\n// thread pool reuses threads; next call on a stale thread throws\ncpService.getMsgAuditService().getDecryptData(sdk, chatData, 1);\n\n// after - catch and retry since ThreadLocal is cleared after throw\ntry {\n  cpService.getMsgAuditService().getDecryptData(sdk, chatData, 1);\n} catch (WxErrorException e) {\n  if (e.getMessage().contains(\"获取会话存档SDK失败\")) {\n    // ThreadLocal was cleared, retry will init fresh SDK\n    cpService.getMsgAuditService().getDecryptData(sdk, chatData, 1);\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    cpService.getMsgAuditService().getDecryptData(sdk, chatData, pkcs1);\n} catch (WxErrorException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"获取会话存档SDK失败\")) {\n        // ThreadLocal was cleared by this exception, retry will init a fresh SDK\n        cpService.getMsgAuditService().getDecryptData(sdk, chatData, pkcs1);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Do not call closeAllSdks() during runtime; reserve it for full application shutdown.","If using a thread pool, consider clearing ThreadLocals or restarting threads after closeAllSdks().","Catch this specific error and retry once, since the ThreadLocal is cleared on throw."],"tags":["weixin-cp","msg-audit","finance-sdk","thread-local","lifecycle","concurrency"],"backgroundTag":null,"analyzedSha":"1c43293a3c2c9d7e91304b6d037fb017f680d0c6","analyzedAt":"2026-08-14T02:29:11.060Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}