From 7940a8fc66f18a1a6b8b961044270db215dc82da Mon Sep 17 00:00:00 2001 From: marcos Date: Thu, 6 Aug 2026 03:12:27 +0000 Subject: [PATCH] fix: guard Corrections against async read from compose all() returns List.copyOf(entries), which iterates; add()/load() structurally modify. Once Task 10 wires all() into runTaskAsynchronously and Task 11 wires add() from the main-thread /ia corrigir command, the race throws ConcurrentModificationException. Guarded by entries' own monitor like Conversations: file parse and YAML save stay outside the lock, only fast in-memory work is under it. --- .../marcospaulo/canalhandia/Corrections.java | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/main/java/dev/marcospaulo/canalhandia/Corrections.java b/src/main/java/dev/marcospaulo/canalhandia/Corrections.java index 476291b..8b3041d 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Corrections.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Corrections.java @@ -22,6 +22,15 @@ final class Corrections { } private final File file; + /** + * Read by {@link #all()} from the async answer thread (Ai.compose) and + * mutated by {@link #add()}/{@link #load()} from the main thread (/ia + * corrigir). Guarded by its own monitor, like {@link Conversations}: only + * fast, in-memory work happens under the lock — file parsing and the YAML + * save stay outside it — so {@link #all()}'s {@code List.copyOf} never + * races a structural change and throws no + * {@code ConcurrentModificationException}. + */ private final List entries = new ArrayList<>(); Corrections(File file) { @@ -30,26 +39,30 @@ final class Corrections { } void load() { - entries.clear(); - if (!file.exists()) { - return; - } - YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file); - for (String key : yaml.getKeys(false)) { - String question = yaml.getString(key + ".pergunta"); - String answer = yaml.getString(key + ".resposta"); - if (question != null && answer != null) { - entries.add(new Entry(question, answer)); + YamlConfiguration yaml = file.exists() ? YamlConfiguration.loadConfiguration(file) : null; + synchronized (entries) { + entries.clear(); + if (yaml == null) { + return; + } + for (String key : yaml.getKeys(false)) { + String question = yaml.getString(key + ".pergunta"); + String answer = yaml.getString(key + ".resposta"); + if (question != null && answer != null) { + entries.add(new Entry(question, answer)); + } } } } void add(String question, String answer) { - entries.add(new Entry(question, answer)); YamlConfiguration yaml = new YamlConfiguration(); - for (int i = 0; i < entries.size(); i++) { - yaml.set("c" + i + ".pergunta", entries.get(i).question()); - yaml.set("c" + i + ".resposta", entries.get(i).answer()); + synchronized (entries) { + entries.add(new Entry(question, answer)); + for (int i = 0; i < entries.size(); i++) { + yaml.set("c" + i + ".pergunta", entries.get(i).question()); + yaml.set("c" + i + ".resposta", entries.get(i).answer()); + } } try { yaml.save(file); @@ -59,7 +72,9 @@ final class Corrections { } List all() { - return List.copyOf(entries); + synchronized (entries) { + return List.copyOf(entries); + } } /** Corrections sharing at least one significant word with the question. */