Skip to content
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

插件卸载后已经加载的插件类无法被 unload #5615

Closed
guqing opened this issue Mar 29, 2024 · 10 comments · Fixed by #5855
Closed

插件卸载后已经加载的插件类无法被 unload #5615

guqing opened this issue Mar 29, 2024 · 10 comments · Fixed by #5855
Assignees
Labels
area/core Issues or PRs related to the Halo Core area/plugin Issues or PRs related to the Plugin Provider kind/bug Categorizes issue or PR as related to a bug. priority/critical-urgent Highest priority. Must be actively worked on as someone's top priority right now. triage/needs-information Indicates an issue needs more information in order to work on it.

Comments

@guqing
Copy link
Member

guqing commented Mar 29, 2024

System information

  • 外部访问地址: http://localhost:8090
  • 启动时间: 2024-03-29 16:22
  • 版本:
  • 构建时间:
  • Git Commit:
  • Java: OpenJDK Runtime Environment / 17.0.4.1+9-LTS
  • 数据库: H2 / 2.2.224
  • 操作系统: Mac OS X / 12.6
  • 已激活主题:
  • 已启动插件:

What is the project operation method?

Docker

What happened?

参考 https://github.com/halo-dev/rfcs/blob/main/plugin/pluggable-design.md#%E6%8F%92%E4%BB%B6%E5%8D%B8%E8%BD%BD

启动 Halo 时添加 JVM 参数
-Xlog:class+load=info -Xlog:class+unload=info
安装插件并启动后卸载后手动触发一次 gc 观察日志
只有 load 类的日志,没有unload 类的日志,已加载的类的数量也不会减少,猜测可能是有地方引用到了插件类加载器对象没有释放导致类加载器对象无法被回收所以它加载的类无法被 unload

Relevant log output

No response

Additional information

/kind bug
/area core
/area plugin

@f2c-ci-robot f2c-ci-robot bot added kind/bug Categorizes issue or PR as related to a bug. area/core Issues or PRs related to the Halo Core area/plugin Issues or PRs related to the Plugin Provider labels Mar 29, 2024
@JohnNiang
Copy link
Member

JohnNiang commented Apr 2, 2024

https://bbs.halo.run/d/5243 (In Chinese Forum)

/assign

@JohnNiang
Copy link
Member

JohnNiang commented Apr 2, 2024

目前,我可以确认的是,在插件被卸载的时候,是调用了 PluginClassLoader#close 方法,但并未立即 unload classes。

主要是因为 unload classes 行为是 JVM GC 管理的。我尝试过手动 GC,可以查看到 unload classes 信息,示例如下所示:

[2787.193s][info][class,unload] unloading class run.halo.s3os.S3LinkServiceImpl$TokenState 0x0000007001ed9cc0
[2787.193s][info][class,unload] unloading class software.amazon.awssdk.services.s3.model.CompletedPart 0x0000007001ed9a68

综上,这算是 JVM 默认的行为,不算是 Halo 的 bug。

@guqing
Copy link
Member Author

guqing commented Apr 3, 2024

综上,这算是 JVM 默认的行为,不算是 Halo 的 bug。

可以试试其他的插件,比如应用市场客户端,或者 SEO 插件之类

@JohnNiang
Copy link
Member

我尝试卸载“应用市场”插件,可以看到 unload 信息:

[134.799s][info][class,unload] unloading class run.halo.appstore.license.ActivationList 0x0000000801c30000

通过 VisualVM 手动 GC 后观察到 Classes 的 Total unloaded 指标是有变化的,请看下面的截图:

image

@guqing
Copy link
Member Author

guqing commented Apr 3, 2024

Hi @JohnNiang 让我们来看一下示例:

以下两个插件都确保安装后不曾调用过其提供的 API,且卸载后手动触发过 gc

首先我安装 plugin-cloudinary 插件

启动并卸载后卸载后的类卸载情况如下:
image
此时插件已经卸载了通过生成 heapdump 查看对象引用,可以看到引用对象数为 0
image

应用市场插件

而安装应用市场插件:,启动后,加载如下多的类,这些是会创建对象的:
image
但是卸载后,只有一个 ActivationList 用于 API 返回类型的类被卸载了,这个类只会创建一些临时对象,但是其他的类都没有被卸载:
image
通过生成 Heapdump 可以看到应用市场插件使用的类加载器还在堆内存中存在一个实例
image
它的 Gc Root 引用路径为:
image

结论

因此这个问题,并不是在所有插件中都存在

我猜测原因是应用市场插件有自定义模型,在序列化和反序列化数据后会有类型引用被缓存到 Halo 的 Extension 提供的 Converter 的 ObjectMapper 的 BeanDeserializerBase 中导致此插件的 PluginClassLoader 无法被回收,从而其加载的类及其自身无法被卸载

ObjectMapper 具有多种缓存方式:

  1. 类型缓存:Jackson 会缓存一些类型信息,如类的序列化器和反序列化器。这意味着一旦一个类被序列化或反序列化,它的处理器就会被缓存起来,以便下次使用时直接获取,不需要重新构造。

  2. 属性缓存:对于每个类,Jackson 会分析其属性(包括字段和getter/setter方法),并缓存这些属性的序列化和反序列化策略。

  3. 注解处理:Jackson 通过注解来控制序列化和反序列化的行为。这些注解的解析结果也会被缓存,以避免重复解析。

@JohnNiang
Copy link
Member

如果确实因为 ObjectMapper 持有了 classes,那这个问题似乎不是那么好解决。

另外,如果插件卸载后 classes 没有被 unload,目前会出现哪些比较严重的问题呢?如果引发的问题不是很严重,我建议暂时忽略这个问题。

@guqing
Copy link
Member Author

guqing commented Apr 7, 2024

如果确实因为 ObjectMapper 持有了 classes,那这个问题似乎不是那么好解决。

另外,如果插件卸载后 classes 没有被 unload,目前会出现哪些比较严重的问题呢?如果引发的问题不是很严重,我建议暂时忽略这个问题。

经排查确实是这个原因导致的,插件如果定义了自定义模型,但是插件卸载之后 JSONExtensionConverter 的 ObjectMapper 的 ConcurrentHashMap<JavaType, JsonDeserializer<Object>> _rootDeserializers 还缓存了插件自定义模型的类型导致插件PluginClassLoader 实例及其加载的类都无法被 unload。

插件类加载器现在的模式是先从插件找类找不到,在从 parent 找,因此还是会加载比较多类的,卸载之后会一直有 PluginClassLoader 实例在堆内存中,目前可能出现的问题是:

  1. 内存泄漏:如果卸载插件比较频繁比如升级/启动插件之类的会残留过多的实例在堆内存无法回收,以及被加载的类无法回收,会随着插件升级或卸载次数累积,随着时间的推移,如果多次加载和卸载插件而未能回收这些类加载器,应用程序占用的内存会持续增加,最终可能导致 OutOfMemoryError,影响应用程序的稳定性和性能。
  2. 类和数据的不一致:可能会出现启动相同插件后被碰撞到旧的类加载器实例导致使用使用旧代码,比如:
    • 插件开发时 reload 已经改了的代码不会生效,让开发者疑惑或自我怀疑
    • 升级插件后等同于没有升级,重启 halo 后会正常
    • 插件有问题后出现升级插件无效比如需要重启 halo 才能正常的问题
  3. 另外的问题但目前可以忽略
    • 性能下降: 随着无法回收的对象数量增加,垃圾回收器需要花费更多时间来处理这些对象,即使它们实际上已经是不可达的。这会导致应用程序的响应时间变长,降低整体性能,尤其是在进行全堆垃圾回收时。

目前我能想到的就是这些,其他的暂不清楚

@guqing
Copy link
Member Author

guqing commented Apr 7, 2024

目前或许可以通过以下几种方式之一解决:

  1. 分离实例:为每个插件提供独立的 ObjectMapper 实例,而不是共用一个全局实例
  2. 重置或重建实例:在插件卸载的过程中,重置或重新创建共用的 ObjectMapper 实例。虽然这种方法可能对性能有一定影响,因为需要重新初始化缓存,但它可以作为一种简单有效的解决方案来避免类加载器泄漏。
  3. 清理缓存:修改或扩展 ObjectMapper 类,增加一个方法或接口来显式清理特定类加载器加载的类的缓存项。

@ruibaby ruibaby added the priority/critical-urgent Highest priority. Must be actively worked on as someone's top priority right now. label Apr 17, 2024
@ruibaby
Copy link
Member

ruibaby commented Apr 17, 2024

建议再排查一下这个问题,今天遇到一个疑似这个问题导致的错误。具体是升级了某个插件,导致插件的接口无法正常请求,重启插件无法解决,最后只能重启 Halo。

相关日志:halo-log-2024-04-17 18_59.log

@JohnNiang
Copy link
Member

建议再排查一下这个问题,今天遇到一个疑似这个问题导致的错误。具体是升级了某个插件,导致插件的接口无法正常请求,重启插件无法解决,最后只能重启 Halo。

相关日志:halo-log-2024-04-17 18_59.log

从日志中观察到,可能和 pf4j/pf4j#576 有关系。

@JohnNiang JohnNiang added the triage/needs-information Indicates an issue needs more information in order to work on it. label Apr 23, 2024
f2c-ci-robot bot pushed a commit that referenced this issue May 7, 2024
…ependent plugin (#5855)

#### What type of PR is this?

/kind bug
/area plugin
/area core

#### What this PR does / why we need it:

This PR resolves the problem that some plugins could not be used after upgrading dependent plugin.

#### Which issue(s) this PR fixes:

Fixes #5615

#### Special notes for your reviewer:

1. Install plugin [app-store](https://www.halo.run/store/apps/app-VYJbF)
2. Install plugin [backup](https://www.halo.run/store/apps/app-dHakX) and activate it
3. Disable plugin app-store
4. Check the features of plugin backup
5. Enable plugin app-store
6. Check the features of plugin backup
7. Upgrade plugin app-store with the any versions
8. Check the features of plugin backup

#### Does this PR introduce a user-facing change?

```release-note
修复因升级应用市场插件导致部分插件意外停止的问题
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/core Issues or PRs related to the Halo Core area/plugin Issues or PRs related to the Plugin Provider kind/bug Categorizes issue or PR as related to a bug. priority/critical-urgent Highest priority. Must be actively worked on as someone's top priority right now. triage/needs-information Indicates an issue needs more information in order to work on it.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants