Skip to content

fix(core): keep full config on rapid reloads - #1495

Merged
tangcent merged 1 commit into
masterfrom
fix/1491-config-reload-cancellation
Nov 10, 2026
Merged

tangcent merged 1 commit into
masterfrom
fix/1491-config-reload-cancellation

Conversation

@tangcent

Copy link
Copy Markdown
Owner

Description

Follow-up to the review on #1491 (merged). It implements the two
"Before merge" cancellation guards and both non-blocking suggestions,
and translates the Chinese comments that PR introduced.

A superseded config reload could publish a partial snapshot and notify
listeners with it, briefly dropping entries from sources that had not
been read yet. LayeredConfigReader.reload() now rethrows
CancellationException instead of logging it as an ordinary source
failure, and DefaultConfigReader.reload() checks
currentCoroutineContext().ensureActive() after loading the new
delegate but before making it active. Ordinary source failures are
still isolated and logged.

The remaining changes are review hygiene with no product behaviour
change: suspendMergeSort moves to a sibling file, the three
${module_path} cases move to an IDE fixture, and the Chinese
comments added by #1491 are now English.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality) — N/A, no new capability
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — N/A, no API or behaviour removal
  • Refactoring (no functional changes) — suspendMergeSort moved to a sibling file
  • Documentation update — KDoc and inline comments translated to English
  • Performance improvement — N/A, no hot path changed
  • Test coverage improvement — 3 new tests plus 3 cases relocated to an IDE fixture

Related Issues

Fixes # — N/A: no open issue to close; this is a follow-up to a merged PR review.

Refs: #1491 — #1491, review comment 6621840788.

Affected Area

  • YApi export — N/A, no export channel touched
  • Postman export — N/A, no export channel touched
  • Markdown export — N/A, no export channel touched
  • Send HTTP request (Call) — N/A, no call path touched
  • API scanning / dashboard — N/A, no scanning or dashboard code touched
  • Rule engine / custom rules / config — config reload, config parser tests
  • Settings / UI — N/A, no settings or UI change
  • Other / not user-facing — comment translation, file move and test relocation

Changes Made

  • core/config/LayeredConfigReader.kt — rethrow CancellationException from the per-source onFailure; real source failures still warn and are skipped.
  • core/config/DefaultConfigReader.kt — split reload() into load, ensureActive(), delegate swap, listener notification.
  • core/config/config/LayeredConfigReaderTest.kt — +2 cases: cancellation propagates and keeps the previous snapshot; ordinary failures remain isolated.
  • core/config/config/DefaultConfigReaderTest.kt — +1 IDE fixture case: a cancelled reload does not notify listeners.
  • core/psi/SuspendMergeSort.kt — new sibling file with the internal function and an English KDoc.
  • core/psi/DefaultPsiClassHelper.kt — function moved out; three Chinese comments at the call site translated.
  • core/psi/SuspendMergeSortTest.kt — Chinese comparator comment translated.
  • core/config/config/parser/ConfigTextParserModulePathTest.kt — new fixture class holding the three ${module_path} cases.
  • core/config/config/parser/ConfigTextParserTest.kt — three cases removed, with a pointer to the new class.

How I Tested

  • ./gradlew test passes — run as ./gradlew cleanTest test: 7669 tests, 0 failures, 0 errors, 10 skipped, 750 result XMLs. The case count is unchanged from before the migration (3 removed, 3 added).
  • Manual verification in a sandbox IDE (./gradlew runIde) — N/A: no UI entry point changed, and the cancellation paths are covered by unit and IDE-fixture tests. The new fixture class was also run in isolation (3/3 PASSED), as were the two pure JUnit classes (42/42 PASSED), which is the isolation problem the review called out.
  • New tests added for new functionality — 3 new tests plus 3 relocated cases in a new fixture class.

Reverse verification: both guards were temporarily reverted and the new tests confirmed to fail (including a cancelled reload must not complete: reloaded), then restored. git diff --check is clean.

Architecture & Threading Checklist

  • New code lives in the right bucket — existing core/config and core/psi packages; SuspendMergeSort.kt is a sibling of its only caller; no new core.* → concrete sibling imports
  • PSI/VFS reads wrapped in read {} / readSync {} — N/A, no new PSI/VFS reads. The existing readSync in ConfigTextParser.activeModuleContentRoot() is untouched; only its tests moved to a fixture that provides an Application.
  • Boundary classes self-protect every public member — N/A, no boundary class or EP implementation touched
  • PSI writes and UI updates go through write {} / swing {} — N/A, no writes and no UI updates
  • No new launch(Dispatchers.Default) from EDT / startup contexts — none added; ensureActive() only inspects the current coroutine context and starts no work

Logging Checklist

  • Exactly one channel per event — a real source failure is still reported once through LOG.warn; a cancellation is rethrown, not logged
  • No LOG.error, LOG.debug, LOG.trace, println, or printStackTrace() in production code — none added
  • Throwables passed as the last argument, never stringified; no silent runCatching{}.getOrNull() or empty catch on meaningful operations — LayeredConfigReader still logs the throwable, and cancellation is now rethrown instead of swallowed. CachedResourceResolver.get() has a pre-existing runCatching{}.getOrNull(); the new ensureActive() guards its caller, and changing that resolver is deliberately left out of scope.

General Checklist

  • My code follows the project's architecture principles
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas — the cancellation rationale is in the tests and KDoc; comments are in English
  • I have made corresponding changes to the documentation — N/A, no user-facing docs affected; only KDoc and inline comments were translated
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Screenshots (if applicable)

N/A — no UI change.

Additional Notes

* fix(core): propagate config reload cancellation

Editing config files in quick succession could leave the plugin
running with a partial config: a superseded reload still published
the sources it had managed to read and notified listeners, so rules
from the remaining sources briefly disappeared.

Root cause: `LayeredConfigReader.reload()` wraps each source in
`runCatching` to isolate failures. A `CancellationException` raised
while waiting on an IntelliJ read action was logged as an ordinary
source failure, so the cancelled reload ran to completion.
`DefaultConfigReader.reload()` then replaced the active delegate and
fired `ConfigReloadListener` without checking whether its coroutine
was still active. The remote config loader can hide the cancellation
entirely: it catches it and returns a fallback.

Solution: rethrow `CancellationException` from the per-source
`onFailure` while keeping warn-and-skip for real source failures,
and call `currentCoroutineContext().ensureActive()` after the new
delegate has loaded but before it becomes active and listeners are
notified. Add tests for both paths plus a guard that ordinary
failures are still isolated. Both guards were reverse-verified:
removing either one makes the new tests fail.

* refactor(psi): extract suspendMergeSort

Move the suspending merge sort out of `DefaultPsiClassHelper.kt` into
a sibling `SuspendMergeSort.kt`, keeping it `internal`. It has no PSI
dependency and already has its own tests. Translate its KDoc and the
comparator test comment to English, matching the rest of the
codebase.

* test(config): move module_path tests to a fixture

The three `${module_path}` cases call `readSync`, which needs an
IntelliJ application. They only passed when another fixture test had
already set one up, so a focused run of `ConfigTextParserTest` failed.
Move them to `ConfigTextParserModulePathTest`, based on the project's
light IDE fixture, so they pass in isolation too.

* docs(psi): translate field ordering comments

Translate the last three Chinese comments added by #1491 at the
`suspendMergeSort` call site. All six Chinese comment lines from that
PR are now English.

Impact: ordinary source-failure isolation is unchanged. Apart from
the two cancellation guards, this branch is test, refactor and
comment only. The changes above address the review on #1491: both
"Before merge" requests and both non-blocking suggestions. Verified
with `./gradlew cleanTest test`: 7669 tests, 0 failures, 0 errors,
10 skipped.

Refs: #1491
@github-actions github-actions Bot added the type: bug Something isn't working label Nov 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📦 Plugin has been packaged for this PR. You can download it from the GitHub Actions workflow run by clicking on the "Artifacts" dropdown:

  • easy-yapi-3.2.6.252.0.zip — IntelliJ IDEA 2025.2+ (officially supported)
  • easy-yapi-3.2.6.221.251.zip — IntelliJ IDEA 2022.1 – 2025.1 (best-effort, not verified on older IDEs)

Each artifact is a ready-to-install plugin zip — pick the one matching your IDEA version and install it via Settings → Plugins → ⚙ → Install Plugin from Disk. No extraction needed.

@codecov

codecov Bot commented Nov 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.153%. Comparing base (4e071e0) to head (853fdfc).

Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##            master     #1495       +/-   ##
=============================================
+ Coverage   70.114%   70.153%   +0.039%     
=============================================
  Files          525       526        +1     
  Lines        30499     30502        +3     
  Branches      7179      7180        +1     
=============================================
+ Hits         21384     21398       +14     
+ Misses        6350      6345        -5     
+ Partials      2765      2759        -6     
Flag Coverage Δ
unittests 70.153% <100.000%> (+0.039%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...angcent/easyapi/core/config/DefaultConfigReader.kt 78.947% <100.000%> (+1.170%) ⬆️
...angcent/easyapi/core/config/LayeredConfigReader.kt 98.039% <100.000%> (+4.039%) ⬆️
...tangcent/easyapi/core/psi/DefaultPsiClassHelper.kt 66.541% <ø> (-0.738%) ⬇️
...com/itangcent/easyapi/core/psi/SuspendMergeSort.kt 100.000% <100.000%> (ø)

... and 3 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e071e0...853fdfc. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tangcent
tangcent merged commit 008f8e1 into master Nov 10, 2026
14 checks passed
@tangcent
tangcent deleted the fix/1491-config-reload-cancellation branch November 10, 2026 23:40
@tangcent tangcent mentioned this pull request Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant