From 9834b29e65328d744c0d2a254673c3e950015268 Mon Sep 17 00:00:00 2001 From: Jakub Zika Date: Tue, 23 Jun 2026 09:24:08 +0200 Subject: [PATCH] Make auto-clear task list check atomic with CAS retry The all-tasks-done? check was performed outside the CAS loop, so a concurrent task mutation between the check and the write could clear newly-added pending/in-progress tasks. Move the check inside the CAS retry loop so clearing only occurs when the predicate holds for the exact state being replaced, consistent with how all op-* functions validate inside mutate-task!. --- src/eca/features/tools/task.clj | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/eca/features/tools/task.clj b/src/eca/features/tools/task.clj index 8f06f066a..6237355cb 100644 --- a/src/eca/features/tools/task.clj +++ b/src/eca/features/tools/task.clj @@ -269,9 +269,14 @@ "Clear the task list when all tasks are done. Returns task-details of the cleared state if clearing occurred, nil otherwise." [db* chat-id] - (when (all-tasks-done? (get-task @db* chat-id)) - (mutate-task! db* chat-id (fn [_] {:state empty-task})) - (task-details empty-task))) + (loop [] + (let [db @db* + state (get-task db chat-id)] + (when (all-tasks-done? state) + (let [new-db (assoc-in db [:chats chat-id :task] empty-task)] + (if (compare-and-set! db* db new-db) + (task-details empty-task) + (recur))))))) ;; --- Operations ---