Skip to content

fix(la): Registry-Races — richtungsbewusstes Inflight, geordnetes Last-Wins (#110) - #115

Merged
Toqsick merged 4 commits into
mainfrom
night/pool-a
Oct 1, 2026
Merged

Toqsick merged 4 commits into
mainfrom
night/pool-a

Conversation

@Toqsick

@Toqsick Toqsick commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Basis-SHA: 506eb88 (main) · Branch: night/pool-a · 3 Commits · 3 Dateien, +381/−27

Commits: b13bdd7 (Implementierung) · 530ea0f (Review-Fix) · c35b964 (Handoff #110-A).

Pool-Position A aus Issue #110 — Registry-Race-Härtung in packages/la_core. Umgesetzt im Nachtlauf 2026-09-30 (Queue-Automation, Worktree-isoliert), am 2026-10-01 interaktiv fertiggestellt: Fix aus dem Korrektheits-Review, Handoff-Sektion, Gate-Frischlauf, RED-Belege, Push/PR.

Das Problem (#110)

ModuleRegistry._inflight hielt nur eine Future je Modul-ID. Dadurch teilte ein activate die laufende deactivate-Future derselben ID still als eigenes Ergebnis, und ein Modul konnte über einem bereits gestoppten Abhängigen starten. Beides ist ein Renn-Fenster der Modul-Lebenszyklen.

Der Fix

  • Richtungsbewusstes Single-Flight (_Inflight trägt _Direction {activate, deactivate}): nur gleichgerichtete Aufrufe teilen sich eine Future.
  • Geordnetes Last-Wins: ein Gegenrichtungs-Aufruf reiht sich hinter den laufenden Vorgang ein und startet danach — activate nach einem deactivate startet wirklich neu.
  • Dep-Re-Check nach jedem await in _activateSubtree: ein Start über einem inzwischen gestoppten Dep rollt sich zurück (_rollbackStart) und wirft ModuleRegistryError.
  • _inflight wird ausschließlich von _run geräumt (identical-Guard), damit eine eingereihte Gegenrichtung unter demselben Key auf den Vorgänger warten kann.
  • Review-Fix 530ea0f: deactivateAll übersprang Module im stopping-Übergang und verletzte damit Last-Wins (Regression gegen Basis). Guard gewidmet auf _states[id] == ModuleState.started || _inflight.containsKey(id).

Tests — packages/la_core/test/module_registry_ordering_test.dart (neu, 6 Fälle)

Echte Completer-Gates, kein fake_async (Repo-Regel):

  1. activate nach laufendem deactivate derselben ID endet started
  2. deactivate nach laufendem activate derselben ID endet stopped
  3. activate eines Abhängigen startet den Dep nach dessen laufendem deactivate neu
  4. gestartetes Modul über inzwischen gestopptem Dep wird zurückgerollt und wirft
  5. paralleles activate zweier Abhängiger startet den gemeinsamen Dep genau einmal
  6. deactivateAll stoppt ein Modul im Übergang trotz eingequeuter Aktivierung (Last-Wins)

RED-Belege: gegen Basis 506eb88 sind Tests 1–4 rot (Test 5 Basis-Verhalten, grün). Gegen b13bdd7 (vor dem Review-Fix) ist Test 6 rot (Expected: stopped, Actual: started).

Gates — tatsächlich ausgeführt (2026-10-01, auf 530ea0f)

Gate Ausgabe
la_core dart analyze No issues found!
la_core dart format --output=none --set-exit-if-changed . Formatted 25 files (0 changed)
la_core dart test +67: All tests passed! (61 Basis + 5 ordering + 1 Regression)
bash tool/check-versions.sh version 0.8.0 is consistent
Root dart format --output=none --set-exit-if-changed lib test Formatted 123 files (0 changed)
flutter analyze No issues found!
flutter test +208: All tests passed!
additional/python unittest discover Ran 53 tests / OK

Review

Dual-Review (2 parallele Subagenten) auf 506eb88..b13bdd7:

  • A (Korrektheit): CHANGES_REQUIRED — 1 major (deactivateAll-Last-Wins, mit reproduzierbarem Interleaving und Basis-Vergleich) → Fix 530ea0f → scoped Re-Review: ADDRESSED, keine neue Breakage ⇒ APPROVED.
  • B (Sicherheit/Scope/Spec): Scope exakt die 2 la_core-Dateien; öffentliche API-Fläche unverändert (nur neue Private); lib/ nutzt ModuleRegistry nicht; keine Flutter-/dart:io-Kopplung; polkit-Trinität und Command-Queue unberührt; keine Secrets. Einziges Finding war die fehlende Handoff-Sektion (Vertragspflicht) — nach Ergänzung READY_FOR_PR.

Bewusst offen

deactivateAll deckt Module, die beim Aufruf starting sind (noch nicht in _activationOrder), weiterhin nicht ab — pre-existing, außerhalb des Diff-Scopes. Issue #110 Position C (Spawn-PATH-Fixierung) braucht eine menschliche Sicherheitsentscheidung.

Rückfallplan

git revert 530ea0f b13bdd7 genügt — 2 la_core-Dateien (1 Implementierung + 1 Test), kein Produktionscode außerhalb la_core, keine Unit, kein Packaging, polkit-Trinität unberührt.

Kein Merge ohne gesonderte Freigabe.

🤖 Generated with Claude Code

Toqsick and others added 3 commits October 1, 2026 00:12
…t-Wins (#110)

activate/deactivate teilten sich unter _inflight[id] eine Future, wodurch
gegenlaeufige Uebergaenge still koaleszierten: activate nach laufendem
deactivate galt faelschlich als gestartet, und ein Abhaengiger konnte ueber
einem bereits gestoppten Dep starten.

- _Inflight traegt die Richtung; nur gleichgerichtete Aufrufe nutzen
  Single-Flight, Gegenrichtungen reihen sich dahinter ein (Last-Wins).
- Re-Check der Dep-States nach jedem await; ein Start ueber einem inzwischen
  gestoppten Dep rollt sich zurueck und wirft ModuleRegistryError.
- _inflight wird ausschliesslich von _run geraeumt.

Tests: 5 neue Faelle in module_registry_ordering_test.dart mit echten
Completer-Gates (kein Fake-Async), 4 davon RED gegen origin/main.
Module im Übergang (stopping/starting) mit gequeuter Gegenrichtungs-Aktivierung wurden von deactivateAll übersprungen, wodurch die spätere Aktivierung gewann und das Modul started endete. Der Guard erfasst jetzt auch laufende Vorgänge (_inflight), sodass sich die Deaktivierung hinter die gequeute Aktivierung einreiht.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 1, 2026 16:01

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Dependency rechecking and indirect dependent stops still contain reproducible lifecycle race conditions.

Review effort: Balanced
Findings: 2 Medium severity · 1 Low severity

Open (3)
What changed in this PR

Hardens la_core module lifecycle ordering against activation/deactivation races.

Changes:

  • Adds direction-aware per-module operation queues and dependency rechecks.
  • Adds six gated concurrency regression tests.
  • Documents implementation, verification, and rollback details.
File Description
packages/​la_core/​lib/​src/​module_registry.dart Implements lifecycle queuing, rollback, and deactivateAll handling.
packages/​la_core/​test/​module_registry_ordering_test.dart Adds race-ordering tests using Completer gates.
docs/​mla-next/​VERIFY.md Records handoff and verification evidence.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +186 to 187
_requireStarted(id, depId);
}
Comment on lines 252 to +254
if (_states[active] == ModuleState.started &&
_transitivelyDependsOn(active, id)) {
await (_inflight[active] ??= _stopModule(active));
await _stopModule(active);
for (final id in List.of(_activationOrder).reversed) {
if (_states[id] == ModuleState.started) {
await (_inflight[id] ??= _stopModule(id));
// Auch Module im Uebergang (stopping/starting) erfassen: haengt dort
…) aufgelöst

Co-Authored-By: Claude Code <noreply@anthropic.com>
@Toqsick
Toqsick merged commit 86ac454 into main Oct 1, 2026
1 check passed
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