Skip to content

Module reloadSelf() overrides silently skip the framework reload: harden the contract so the class of defect cannot recur #419

Description

@wisdommen

Summary

Eleven of the sixteen product modules override UltiToolsPlugin.reloadSelf(). Nine of those overrides never call super.reloadSelf(), so /ul reload for those modules logs "configuration reloaded" while reloading nothing. Two modules (UltiLogin, UltiWorlds) were fixed in Phase 13 of the 6.3.0 milestone after the defect was found independently in each — the same cause, twice. This issue tracks the remaining nine and proposes closing the class of defect in the framework rather than fixing it a tenth time.

Measured (2026-09-06, all sixteen module repositories at their current heads)

Module Overrides reloadSelf() Calls super.reloadSelf()
UltiBackup yes no
UltiCleaner yes no
UltiEssentials yes no
UltiMail yes no
UltiRecipe yes no
UltiRemoteBag yes no
UltiSideBar yes no
UltiSocial yes no
UltiTrade yes no
UltiLogin yes yes (fixed, UltiKits/UltiLogin#18)
UltiWorlds yes yes (fixed, UltiKits/UltiWorlds#16)
UltiBot, UltiChat, UltiEconomy, UltiKits, UltiMenu no override inherits the framework behaviour

Every one of the nine non-calling overrides has the same body shape — a single getLogger().info(i18n("<Module> 配置已重载!")) line.

What super.reloadSelf() does (abstracts/UltiToolsPlugin.java, current alpha): getConfigManager().reloadConfigs(this), then re-creates the language catalogue, then ConditionalRegistrationEvaluator.reportDrift(this). The method's own javadoc already states that an override which skips super "will not get this report" — the contract is documented, and nine modules break it.

Why this belongs in the framework, not in nine more module pull requests

The two fixes already landed are one line each (super.reloadSelf();) plus a test that fails when the line is removed. Repeating that nine more times leaves the defect class in place: the next module author who overrides reloadSelf() to add a log line reproduces it. This milestone's core value is that a declared surface must do what it declares; a hook whose contract depends on every override remembering one call does not.

Proposed direction (maintainer decision — two shapes, one breaking and one not)

  1. Template method (breaking, structural). Make the reload entry point the framework calls non-overridable, and expose a protected void onReload() hook that modules override instead. Every existing override then fails to compile, which is the point — the defect cannot be written. Cost: a public-API change on UltiToolsPlugin (COMPATIBILITY.md entry; all eleven overriding modules need a one-line rename), so it belongs in its own phase, not appended to Phase 13.
  2. Runtime detection (non-breaking, interim). Have super.reloadSelf() set a per-call sentinel and have the framework's reload dispatcher log a WARNING naming the module when an override returns without the sentinel set. Silent no-op becomes a loud no-op without changing any signature. Does not prevent the defect, only exposes it.

Option 1 is the root-cause fix; option 2 is what to ship if option 1 waits for a major release.

Module issues

UltiKits/UltiBackup, UltiKits/UltiCleaner, UltiKits/UltiEssentials, UltiKits/UltiMail, UltiKits/UltiRecipe, UltiKits/UltiRemoteBag, UltiKits/UltiSideBar, UltiKits/UltiSocial, UltiKits/UltiTrade — one issue each, linked back here (numbers listed in the first comment below once created).

Found by

Phase 13 of the 6.3.0 milestone, 2026-09-06: after UltiLogin#13 and UltiWorlds#10 were both traced to this cause, the remaining fourteen modules were measured with grep -rl 'void reloadSelf' src/main/java plus a per-method scan for super.reloadSelf. The maintainer decided on the same day to track the nine here rather than widen Phase 13, whose scope is the defects the ecosystem acceptance pass found.

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