Skip to content

[#406] Architecture review remediation: headless automation, plugin lifecycle, CLI dispatch & EDT safety - #407

Merged
vbmacher merged 4 commits into
developfrom
feature-314
Jul 2, 2026
Merged

[#406] Architecture review remediation: headless automation, plugin lifecycle, CLI dispatch & EDT safety#407
vbmacher merged 4 commits into
developfrom
feature-314

Conversation

@vbmacher

@vbmacher vbmacher commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Overview

Primarily remediates findings from a full architecture & implementation review of the runtime and plugin platform (tracked in #406): fixes correctness bugs in the headless/automation path, tightens module boundaries and plugin lifecycle, pins the build toolchain, and bootstraps docs/adr.

Two smaller, pre-existing commits ride along on this branch — see Also included below.

[#406] Architecture review remediation

Bug fixes (correctness)

  • Headless automation NPE (High). ApplicationApiImpl no longer rejects a null GUI (this.gui = gui), matching the plugin contract where CPU plugins already guard on getGUI() == null. Fixes NullPointerException in automation --no-gui.
  • CLI subcommand dispatch (High). Runner now implements Callable<Integer> with a single picocli execution (executeArgs/normalizeArgs), removing the double-parse + runsSomeCommand flag that made inherited options placed after the automation subcommand silently launch the GUI. Also removes scattered System.exit.
  • Lost-notification hang (High). Automation guards resultStateLock.wait() with a state predicate so a fast-halting program cannot notify before the wait and hang forever.

Boundaries & lifecycle

  • Plugin lifecycle (Medium). VirtualComputer.close() destroys plugins in reverse init order (devices → CPU → memory → compiler), idempotently, then owns and closes the plugin URLClassLoader, then closes config.
  • Runtime → GUI boundary (Medium). New EmulationProgress port (with NONE no-op); Automation no longer imports gui.dialogs.AutoDialog, which now implements the port.
  • EDT safety (Medium). CPU status panels marshal Swing updates onto the EDT (8080, brainduck, ram, rasp, ssem, z80).
  • byte-mem visibility (Low). Memory/bank fields made volatile.

Build & docs

  • Toolchain pin (Medium). Java toolchain pinned to JavaLanguageVersion.of(11).
  • Release snapshot guard (Medium). New checkNoSnapshots + releaseDist Gradle tasks fail release packaging on any -SNAPSHOT dependency.
  • ADRs (Low). New docs/adr/ with ADR-0001…0007 (ADR process, plugin-loading contract, context interface hashing, project format, headless mode, plugin lifecycle & classloader ownership, automation-progress UI boundary). docs/code.adoc / docs/components.adoc aligned re: context registration.

Tests

New AutomationBoundaryTest; updated AutomationTest, RunnerTest, AutomationCommandTest, ApplicationApiImplTest, VirtualComputerTest (destroy order + classloader close), PluginLoaderTest.

Also included (separate commits on this branch)

Testing

  • ./gradlew build
  • Manual: emuStudio -cn BrainDuck automation --no-gui -w 800 completes without NPE.

Notes

Closes #406

vbmacher and others added 3 commits July 2, 2026 10:02
- Convert CPU frequency kHz->Hz before resampling; getCPUFrequency()
  reports kHz, so audio ran ~1000x too slow in real emulation
- Read selected register from 0xFFFD (address/latch), not write-only
  data port 0xBFFD, per ZX Spectrum wiring
- Envelope step is 32*EP CPU cycles (fE=clock/256/EP over 16 steps),
  not 512*EP which ran 16x too slow

Tests updated to pass kHz; add sample-rate regression test.

Refs #398

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix correctness bugs in the headless/automation path and tighten
module boundaries, plugin lifecycle, build and docs.

- Headless automation NPE: ApplicationApiImpl allows null GUI
- CLI dispatch: Runner -> Callable<Integer>, drop double-parse flag
- Automation: guard wait() against lost-notification hang
- VirtualComputer: reverse-order plugin destroy() + close classloader
- New EmulationProgress port removes runtime -> GUI import leak
- EDT-safe CPU status panels (8080, brainduck, ram, rasp, ssem, z80)
- byte-mem: volatile memory/bank fields for cross-thread visibility
- Pin Java 11 toolchain; add checkNoSnapshots/releaseDist tasks
- Add docs/adr with ADR-0001..0007
- Tests: AutomationBoundaryTest plus updated runtime/CLI suites

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vbmacher vbmacher changed the title Feature 314 [#406] Architecture review remediation: headless automation, plugin lifecycle, CLI dispatch & EDT safety Jul 2, 2026
RunnerTest exercises `Runner.executeArgs("automation", "--help")`.
AutomationCommand had no --help option, so the arg was unmatched and,
with `collectErrors(true)` set on the executed CommandLine, picocli's
RunLast fell through to running the parent `Runner.call()`, which builds
the Swing GUI and shows the modal computer-chooser dialog. Under Cacio
(headless=false) the modal dialog blocked forever, hanging the test JVM
and the whole `./gradlew build` (2h+ on CI vs ~2.5min normally).

- AutomationCommand: add mixinStandardHelpOptions so `automation -h/--help`
  prints subcommand help and returns 0 without touching the GUI.
- Runner.createCommandLine: drop collectErrors(true). It is only useful
  for lenient parse inspection but makes execute() silently launch the
  GUI on any subcommand parse error; normal handling now prints usage
  and returns a non-zero exit code instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@vbmacher
vbmacher merged commit 40e07e3 into develop Jul 2, 2026
1 check passed
@vbmacher
vbmacher deleted the feature-314 branch July 2, 2026 13:39
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.

Architecture review remediation: headless automation, plugin lifecycle, CLI dispatch & EDT safety

1 participant