Skip to content

fix: drain commit batch when an action throws to avoid InnerCommit hang#120

Merged
ttu merged 2 commits into
masterfrom
fix-commitactionhandler-failed-action
Jul 19, 2026
Merged

fix: drain commit batch when an action throws to avoid InnerCommit hang#120
ttu merged 2 commits into
masterfrom
fix-commitactionhandler-failed-action

Conversation

@ttu

@ttu ttu commented May 14, 2026

Copy link
Copy Markdown
Owner

If a single action's Parse or HandleAction throws, the foreach exited before enqueueing callbacks for the remaining batch members. Their Ready callbacks never fired, hanging InnerCommit's wait loop.

Now per-action failures are caught and recorded; every action gets a callback, and updateState is skipped if any action threw.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed batch commit processing so a failed action no longer prevents other actions in the same batch from completing.
    • Callbacks now receive the appropriate failure when an individual action or state update encounters an error.
    • Added a changelog entry documenting this correction.

If a single action's Parse or HandleAction throws, the foreach exited
before enqueueing callbacks for the remaining batch members. Their
Ready callbacks never fired, hanging InnerCommit's wait loop.

Now per-action failures are caught and recorded; every action gets a
callback, and updateState is skipped if any action threw.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The commit handler now catches failures for individual actions, marks their callbacks as failed, continues processing the batch, and skips state updates when an action failed. State-update exceptions are propagated to callbacks. The changelog records this fix.

Changes

Commit batch error handling

Layer / File(s) Summary
Isolate failures during batch processing
JsonFlatFileDataStore/CommitActionHandler.cs, CHANGELOG.md
Per-action exceptions are captured and reported while remaining actions continue processing; state updates run only when actions succeed, and state-update failures are propagated. The changelog records the behavior change.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: draining the commit batch on action failures to prevent InnerCommit hangs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-commitactionhandler-failed-action

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
JsonFlatFileDataStore/CommitActionHandler.cs (1)

44-83: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Isolate per-action exceptions and avoid aborting the entire batch on a single failure.

The current implementation shares a single actionException across the entire batch and skips updateState if any action fails. Because batches group independent, concurrent caller requests as an I/O optimization, this causes two critical issues:

  1. Exception leakage: The shared actionException is overwritten if multiple actions fail, and the final exception is passed to all callers in the batch.
  2. Collateral damage: A single failing action (e.g., a malformed update) aborts the entire batch, causing unrelated concurrent operations to fail and lose their changes.

To fix this, track exceptions per-action using a parallel queue, and only skip the final Ready success state if a batch-level I/O error occurs in updateState.

🐛 Proposed fix
-            Exception actionException = null;
+            var actionExceptions = new Queue<Exception>();
 
             foreach (var action in batch)
             {
                 try
                 {
                     var (actionSuccess, updatedJson) = action.HandleAction(JObject.Parse(jsonText));
 
                     callbacks.Enqueue((action, actionSuccess));
+                    actionExceptions.Enqueue(null);
 
                     if (actionSuccess)
                         jsonText = updatedJson;
                 }
                 catch (Exception e)
                 {
-                    // Record the failure but keep draining the batch so every caller's Ready
-                    // callback fires — otherwise InnerCommit's wait loop would hang forever.
-                    actionException = e;
                     callbacks.Enqueue((action, false));
+                    actionExceptions.Enqueue(e);
                 }
             }
 
             var updateSuccess = false;
+            Exception batchException = null;
 
-            if (actionException == null)
-            {
-                try
-                {
-                    updateSuccess = updateState(jsonText);
-                }
-                catch (Exception e)
-                {
-                    actionException = e;
-                }
-            }
+            try
+            {
+                updateSuccess = updateState(jsonText);
+            }
+            catch (Exception e)
+            {
+                batchException = e;
+            }
 
             foreach (var (cbAction, cbSuccess) in callbacks)
             {
-                cbAction.Ready(updateSuccess ? cbSuccess : false, actionException);
+                var actionEx = actionExceptions.Dequeue();
+                cbAction.Ready(batchException != null ? false : (updateSuccess ? cbSuccess : false), batchException ?? actionEx);
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@JsonFlatFileDataStore/CommitActionHandler.cs` around lines 44 - 83, Update
the batch processing around the action loop and final Ready callbacks to track
each action’s exception alongside its callback result, rather than sharing
actionException across the batch. Continue processing all actions and call
updateState with successful updates even when an individual action fails; only a
batch-level updateState exception should force every Ready callback to report
failure. Pass each action’s own exception to its corresponding cbAction.Ready
call, preserving unrelated actions’ results.
🧹 Nitpick comments (1)
JsonFlatFileDataStore/CommitActionHandler.cs (1)

50-50: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Avoid redundant JSON parsing in the batch loop.

Currently, HandleAction returns a serialized JSON string, forcing JObject.Parse(jsonText) to be called up to 50 times per batch. For large JSON files, this O(N) redundant parsing degrades throughput.

Consider refactoring the DataStore.CommitAction.HandleAction delegate to return the mutated JObject directly instead of a string, so the parsed object can be passed to subsequent actions in the batch without re-parsing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@JsonFlatFileDataStore/CommitActionHandler.cs` at line 50, Refactor the
CommitAction.HandleAction delegate and its implementations to return the mutated
JObject rather than a serialized string. In CommitActionHandler’s batch loop,
parse jsonText once, pass the resulting object through successive HandleAction
calls, and serialize only when the final result must be persisted, preserving
the existing actionSuccess behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@JsonFlatFileDataStore/CommitActionHandler.cs`:
- Around line 44-83: Update the batch processing around the action loop and
final Ready callbacks to track each action’s exception alongside its callback
result, rather than sharing actionException across the batch. Continue
processing all actions and call updateState with successful updates even when an
individual action fails; only a batch-level updateState exception should force
every Ready callback to report failure. Pass each action’s own exception to its
corresponding cbAction.Ready call, preserving unrelated actions’ results.

---

Nitpick comments:
In `@JsonFlatFileDataStore/CommitActionHandler.cs`:
- Line 50: Refactor the CommitAction.HandleAction delegate and its
implementations to return the mutated JObject rather than a serialized string.
In CommitActionHandler’s batch loop, parse jsonText once, pass the resulting
object through successive HandleAction calls, and serialize only when the final
result must be persisted, preserving the existing actionSuccess behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ec963e3-f828-42ce-98b5-a2390258b1d9

📥 Commits

Reviewing files that changed from the base of the PR and between bcd7962 and 6ac04af.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • JsonFlatFileDataStore/CommitActionHandler.cs

@ttu
ttu merged commit e56140f into master Jul 19, 2026
3 checks 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.

1 participant