[Stabiliteit][P1] Instellingenbestand op Windows/Linux niet atomair; één afgebroken schrijfbeurt breekt elke volgende start #2628

Closed
opened 2026-10-28 16:31:42 +00:00 by brenno · 0 comments
Owner

Symptoom

Na een crash, een stroomstoring of het afsluiten van een vastgelopen OciDeck via Taakbeheer kan het gebeuren dat OciDeck bij elke volgende start opnieuw om toestemming vraagt, alle instellingen kwijt is, en bij elke wijziging "Instelling opslaan is mislukt" meldt — zonder uitleg of herstelroute.

Oorzaak

Op Windows en Linux slaat shared_preferences alles op in één JSON-bestand. In shared_preferences_windows 2.4.1 (lib/shared_preferences_windows.dart):

  • schrijven is localDataFile.writeAsStringSync(stringMap) (regel 394): niet atomair (het bestand wordt in-place afgekapt en herschreven), synchroon op de UI-isolate, en bij elke set… het hele bestand;
  • lezen is json.decode(readAsStringSync()) (regel 361-363) zonder foutafhandeling.

Een schrijfbeurt die halverwege wordt afgebroken laat een leeg of half bestand achter. Daarna gooit elke SharedPreferences.getInstance() een FormatException:

  • SettingsNotifier._load() wordt zonder await of catch aangeroepen (lib/state/settings_provider.dart:141): de fout gaat naar de zone-handler (in release onzichtbaar), de app draait op standaardwaarden;
  • de toestemmingspoort leest "fail closed" (lib/state/consent_provider.dart:68-76) en vraagt dus elke start opnieuw; het wegschrijven van de toestemming faalt ook, dus dat blijft zo;
  • elke _persist (settings_provider.dart:366) faalt en toont de snackbar.

Twee gelijktijdige OciDeck-processen (#2627) vergroten de kans, omdat ze door elkaar heen hetzelfde bestand herschrijven.

Voorgestelde oplossing

  1. Zelfherstel bij lezen: één wrapper rond SharedPreferences.getInstance(); bij een FormatException het bestand hernoemen naar shared_preferences.json.corrupt-<tijd>, leeg verder gaan, en de gebruiker één keer melden: "Je instellingen waren beschadigd en zijn teruggezet; een kopie staat in …". Hetzelfde patroon als RecoveryService.loadAll al gebruikt (quarantaine in plaats van verwijderen).
  2. Atomair schrijven: overweeg op desktop een eigen, kleine voorkeurenopslag op basis van de bestaande writeStringAtomic (tijdelijk bestand + rename, met de Windows-terugval), achter dezelfde interfaces (ConsentStore bestaat al). Dat haalt ook de synchrone schrijfbeurten van de UI-isolate.
  3. _load()-fouten in SettingsNotifier vangen en zichtbaar maken.

Wat overeind moet blijven

Geheimen blijven in de sleutelhanger (niet in dit bestand); de toestemmingspoort blijft fail-closed voor het geval dat het bestand er gewoon nog niet is.

Toets

Test met een afgekapt shared_preferences.json: de app start, vraagt één keer toestemming, kan daarna instellingen opslaan, en de beschadigde kopie staat ernaast.

Onderdeel van de prestatie- en stabiliteitsinspectie van 28-10-2026 (EPIC #2637).

## Symptoom Na een crash, een stroomstoring of het afsluiten van een vastgelopen OciDeck via Taakbeheer kan het gebeuren dat OciDeck bij **elke** volgende start opnieuw om toestemming vraagt, alle instellingen kwijt is, en bij elke wijziging "Instelling opslaan is mislukt" meldt — zonder uitleg of herstelroute. ## Oorzaak Op Windows en Linux slaat `shared_preferences` alles op in één JSON-bestand. In `shared_preferences_windows` 2.4.1 (`lib/shared_preferences_windows.dart`): - **schrijven** is `localDataFile.writeAsStringSync(stringMap)` (regel 394): niet atomair (het bestand wordt in-place afgekapt en herschreven), synchroon op de UI-isolate, en bij elke `set…` het hele bestand; - **lezen** is `json.decode(readAsStringSync())` (regel 361-363) zonder foutafhandeling. Een schrijfbeurt die halverwege wordt afgebroken laat een leeg of half bestand achter. Daarna gooit elke `SharedPreferences.getInstance()` een `FormatException`: - `SettingsNotifier._load()` wordt zonder `await` of `catch` aangeroepen (`lib/state/settings_provider.dart:141`): de fout gaat naar de zone-handler (in release onzichtbaar), de app draait op standaardwaarden; - de toestemmingspoort leest "fail closed" (`lib/state/consent_provider.dart:68-76`) en vraagt dus elke start opnieuw; het wegschrijven van de toestemming faalt ook, dus dat blijft zo; - elke `_persist` (`settings_provider.dart:366`) faalt en toont de snackbar. Twee gelijktijdige OciDeck-processen (#2627) vergroten de kans, omdat ze door elkaar heen hetzelfde bestand herschrijven. ## Voorgestelde oplossing 1. **Zelfherstel bij lezen**: één wrapper rond `SharedPreferences.getInstance()`; bij een `FormatException` het bestand hernoemen naar `shared_preferences.json.corrupt-<tijd>`, leeg verder gaan, en de gebruiker één keer melden: "Je instellingen waren beschadigd en zijn teruggezet; een kopie staat in …". Hetzelfde patroon als `RecoveryService.loadAll` al gebruikt (quarantaine in plaats van verwijderen). 2. **Atomair schrijven**: overweeg op desktop een eigen, kleine voorkeurenopslag op basis van de bestaande `writeStringAtomic` (tijdelijk bestand + rename, met de Windows-terugval), achter dezelfde interfaces (`ConsentStore` bestaat al). Dat haalt ook de synchrone schrijfbeurten van de UI-isolate. 3. `_load()`-fouten in `SettingsNotifier` vangen en zichtbaar maken. ## Wat overeind moet blijven Geheimen blijven in de sleutelhanger (niet in dit bestand); de toestemmingspoort blijft fail-closed voor het geval dat het bestand er gewoon nog niet is. ## Toets Test met een afgekapt `shared_preferences.json`: de app start, vraagt één keer toestemming, kan daarna instellingen opslaan, en de beschadigde kopie staat ernaast. Onderdeel van de prestatie- en stabiliteitsinspectie van 28-10-2026 (EPIC #2637).
brenno 2026-10-29 12:19:51 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#2628
No description provided.