Skip to content

ADFA-5422: Load each color scheme once, under a lock - #2129

Open
Daniel-ADFA wants to merge 1 commit into
stagefrom
fix/ADFA-5422-color-scheme-load-race
Open

Daniel-ADFA wants to merge 1 commit into
stagefrom
fix/ADFA-5422-color-scheme-load-race

Conversation

@Daniel-ADFA

@Daniel-ADFA Daniel-ADFA commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

ADFA-5422

Fixes the fatal IllegalArgumentException: color id must be positive and bit count is less than 19 from StyleDef.makeStyle() (GlitchTip CODEONTHEGO-K, 42 events, still on C-r-1006).

  • IDEColorScheme.load() is @Synchronized and loads once. Opening a project restores its tabs together, and each editor loaded the same scheme instance on its own IO coroutine; the parallel parses corrupted the non-thread-safe MutableIntIntMap.
  • parseColorValue() no longer catches a putColor() failure and returns the raw ARGB color as a color id. That fallback is what turned a corrupted write into this crash; a failure now reaches loadColorScheme(), which logs it and uses the default scheme.
  • IDEColorSchemeLoadTest: concurrent loads of one scheme, and a second load. Without the fix the eight loader threads hang in MutableIntIntMap.findInsertIndex and a second load doubles the color table (35 to 70).

Nothing reloads an instance on purpose: load() is only reached through IDEColorSchemeProvider.loadColorScheme() while the provider's cached scheme is null, and SchemeParser.parse() builds its own instance.

Reproduce
  • JVM: run IDEColorSchemeLoadTest without the fix.
  • Device (debug build): open two files in a project, go home, force-stop, adb shell am set-debug-app -w com.itsaky.androidide, start the app, jdb -attach and stop thread in com.itsaky.androidide.editor.schemes.IDEColorScheme.load$editor_v8Debug. Two DefaultDispatcher threads stop in load() at the same time; resume lets them parse in parallel. With the fix the second thread waits on the lock until the first returns.

Opening a project restores its editor tabs together, and each editor reads
the color scheme on its own IO coroutine. IDEColorSchemeProvider caches the
scheme in an unsynchronized lazy getter, so two editors can both call
IDEColorScheme.load() on the same instance at once. Both parse into the same
MutableIntIntMap and ++colorId counter, which are not thread-safe.

parseColorValue() caught any failure from putColor() and returned the raw
ARGB color as the color id, so a corrupted write became an out-of-range id
that StyleDef.makeStyle() passed to TextStyle.makeStyle(), which throws
"color id must be positive and bit count is less than 19" (GlitchTip
CODEONTHEGO-K, 42 fatal events, still on C-r-1006). Other interleavings leave
the map's probe loop spinning forever.

load() is now synchronized and returns once the scheme is loaded, so the
second editor waits for the first and reuses its result. Nothing reloads an
instance on purpose: load() is only reached through loadColorScheme() while
the provider's cached scheme is still null. putColor() failures now
propagate to IDEColorSchemeProvider.loadColorScheme(), which logs them and
falls back to the default scheme, instead of surfacing later as a crash.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 8d8031b7-63dd-4780-a7d1-d7958fdf550b

📥 Commits

Reviewing files that changed from the base of the PR and between dbeb7b2 and 65d946b.


📒 Files selected for processing (3)
  • editor/src/main/java/com/itsaky/androidide/editor/schemes/IDEColorScheme.kt
  • editor/src/main/java/com/itsaky/androidide/editor/schemes/internal/parser/common.kt
  • editor/src/test/java/com/itsaky/androidide/editor/schemes/IDEColorSchemeLoadTest.kt

Included review availability: This review used your included allowance. 3 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.



📝 Summary
  • Synchronize IDEColorScheme.load() to prevent duplicate concurrent loads.
  • Propagate color ID allocation failures and use the default scheme.
  • Add tests for repeated and concurrent loads.
  • Risk: failed parsing remains retryable.
  • Test execution results are unavailable.

Walkthrough

Scheme loading is synchronized and stops parsing after a successful load. Color registration errors now propagate when parsing a color ID. Tests cover repeated and concurrent calls to load().

Changes

Color Scheme Loading

Layer / File(s) Summary
Color parsing and load lifecycle
editor/src/main/java/com/itsaky/androidide/editor/schemes/IDEColorScheme.kt, editor/src/main/java/com/itsaky/androidide/editor/schemes/internal/parser/common.kt, editor/src/test/java/com/itsaky/androidide/editor/schemes/IDEColorSchemeLoadTest.kt
load() is synchronized and skips parsing after a successful load. Parsing errors leave the scheme eligible for another attempt. parseColorValue propagates putColor errors when colorId is true. Tests cover reloads and concurrent loads.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix


Merge Risk: ⚪ Minimal · up to 65d94

The change makes color scheme loading safe when several editor tabs load at once, and it routes color registration failures to the existing default-scheme fallback. No concrete merge-blocking risk was found.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly summarizes the main change: it loads each color scheme once and protects loading with synchronization.
Description check Passed The description directly explains the concurrency failure, the code changes, the fallback behavior, and the added tests.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

I’m a rabbit checking colors in the scheme,
One careful load, then no repeats.
Eight little threads can safely arrive,
While parser errors still speak up.
I thump my paws and hop away!

Comment @coderabbitai help to get the list of available commands.

@Daniel-ADFA
Daniel-ADFA requested a review from a team October 11, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants