Skip to content

浮窗插件按钮生命周期缺陷:插件注销时机导致退出崩溃/干净退出后按钮消失/启动误删,本体无安全注销点 #273

Description

@PANDAJSR

版本:v3.0.0-alpha.2-Nonomi-bf53388(HEAD) · Windows 10 · 1920×1080 · 自包含 exe

现象

配合任意会注册浮窗按钮的插件(如 SecScore Launcher)可复现三件事:

  1. 退出/重启应用时崩溃;
  2. 把注销提前到 AppStopping 修掉崩溃后,每次干净退出,插件的浮窗按钮会从设置里消失,下次启动不再显示;
  3. 偶发:启动时浮窗首次刷新早于插件注册,也会误删该按钮的显示开关。

Bug 1 —— 退出崩溃(本体没有「安全注销点」)

插件在 hosted service StopAsync 里注销按钮是常规写法;但宿主 App.StopAsync()SecRandom/App.axaml.cs)的执行次序是:
触发 AppStopping (1155)DisposePluginsAsync (1167)host.StopAsync() (1183,此刻才停 hosted service,插件注销发生在这)host.Dispose() (1193)IAppHost.Host = null (1204)

注销会触发 FloatingWindowButtonRegistry.Changed → 宿主 FloatingWindowButtonRegistry_OnChangedDispatcher.Post(RefreshItems) 排队;该任务晚于 IAppHost.Host = null 才执行 → GetVisiblePluginButtonsFloatingWindow.axaml.cs:130)调用 IAppHost.GetService<IFloatingWindowButtonRegistry>(),而 IAppHost.GetService 在 Host 为 null 时直接抛 ArgumentException("Service ... is null!")IAppHost.cs:21-27),应用在退出阶段崩溃并写 crash dump。

崩溃堆栈(data/crashes/*.txt):

System.ArgumentException: Service ...IFloatingWindowButtonRegistry is null!
   at IAppHost.GetService[T]()
   at FloatingWindow.GetVisiblePluginButtons(...)
   at FloatingWindow.RefreshItems()
   at Avalonia.Threading.DispatcherOperation.InvokeCore()

另一份为 ObjectDisposedException(处置时 App.TrySaveConfigForCrashRecovery 访问已 Dispose 的 IServiceProvider)。

注:构造函数(同文件 :79)用的是空安全的 TryGetService,:130 却用会抛异常的 GetService

Bug 2 —— 干净退出后按钮消失

把注销提前到 AppStopping 后不再崩,但 RefreshItems 在 Host 存活时执行,GetVisiblePluginButtons 的「过期清理」(FloatingWindow.axaml.cs:133-141)会把注册表里已不存在的 id 从 settings.jsonvisible_plugin_button_ids 删除并保存 → 干净退出一次,按钮就永久消失(下次启动插件虽重新注册,但显示开关已被删)。

也就是说宿主没有给插件「关闭时安全注销且不影响持久化配置」的窗口:该清理的本意是「插件被移除」,不应在应用停机阶段触发。

Bug 3 —— 启动偶发误删

启动时浮窗首次 RefreshItems 若早于插件 hosted service 完成注册(注册表瞬时为空),同样会误删新插件的 id。

建议修复(本体)

  1. FloatingWindow.GetVisiblePluginButtons 改用 TryGetService,Host 为 null(停机阶段)时返回空而不抛异常;或在 RefreshItems 入口先判 IAppHost.Host == null
  2. 停机阶段的按钮注销不应触发「过期清理/持久化删除」:停机期间跳过该清理;或当某插件按钮注册而其 id 不在列表时自动补回而非删除。
  3. TrySaveConfigForCrashRecovery 在 Host 已 Dispose / 为 null 时应直接跳过。

在以上修复前,插件侧只能以「注册后不注销」规避(浮窗按钮注册表为进程级、不落盘,进程退出即消失,可接受)。

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions