@@ -18,6 +18,11 @@ use nodedb_physical::physical_plan::PhysicalPlan;
1818
1919use super :: undo:: UndoEntry ;
2020
21+ /// A CRDT delta buffered during a transaction batch: `(delta_bytes, id,
22+ /// collection)`. Deltas accumulate in a scratch buffer and are applied to
23+ /// the LoroDoc only on full-batch success.
24+ type CrdtDelta = ( Vec < u8 > , u64 , String ) ;
25+
2126impl CoreLoop {
2227 /// Execute a transaction batch atomically.
2328 ///
@@ -27,7 +32,6 @@ impl CoreLoop {
2732 ///
2833 /// The Control Plane has already written a single `RecordType::Transaction`
2934 /// WAL record covering all operations before dispatching this batch.
30- #[ allow( clippy:: too_many_lines) ]
3135 pub ( in crate :: data:: executor) fn execute_transaction_batch (
3236 & mut self ,
3337 task : & ExecutionTask ,
@@ -40,16 +44,68 @@ impl CoreLoop {
4044 "transaction batch begin"
4145 ) ;
4246
43- let mut undo_log: Vec < UndoEntry > = Vec :: with_capacity ( plans. len ( ) ) ;
44- let mut crdt_deltas: Vec < ( Vec < u8 > , u64 , String ) > = Vec :: new ( ) ;
47+ let undo_log: Vec < UndoEntry > = Vec :: with_capacity ( plans. len ( ) ) ;
48+ let crdt_deltas: Vec < CrdtDelta > = Vec :: new ( ) ;
49+
50+ let ( last_response, undo_log, crdt_deltas) =
51+ match self . run_sub_plans ( task, tid, plans, undo_log, crdt_deltas) {
52+ Ok ( v) => v,
53+ Err ( resp) => return resp,
54+ } ;
55+
56+ let undo_log = match self . apply_balanced_constraint_check ( task, tid, undo_log) {
57+ Ok ( u) => u,
58+ Err ( resp) => return resp,
59+ } ;
60+
61+ if let Some ( resp) = self . apply_crdt_deltas ( task, tid, crdt_deltas) {
62+ return resp;
63+ }
64+
65+ debug ! (
66+ core = self . core_id,
67+ committed = plans. len( ) ,
68+ "transaction batch committed"
69+ ) ;
70+
71+ self . emit_deferred_writes ( task, undo_log) ;
72+
73+ // Return the last sub-plan payload, but keyed to the outer transaction request.
74+ Response {
75+ request_id : task. request_id ( ) ,
76+ status : Status :: Ok ,
77+ attempt : 1 ,
78+ partial : false ,
79+ payload : last_response. payload ,
80+ watermark_lsn : self . watermark ,
81+ error_code : None ,
82+ }
83+ }
84+
85+ /// Run every sub-plan in order, tracking undo entries and buffered CRDT
86+ /// deltas as it goes.
87+ ///
88+ /// On success, returns the last sub-plan's response plus the accumulated
89+ /// undo log and CRDT delta buffer (for the caller's subsequent commit
90+ /// steps). On failure, rolls back all writes performed so far and
91+ /// returns the terminal error `Response` directly — the caller must
92+ /// return it unchanged.
93+ ///
94+ /// A panic (real or test-injected) during a sub-apply is caught so it
95+ /// routes through the same typed-rollback path instead of unwinding past
96+ /// `undo_log`, which would drop the log without running rollback and
97+ /// leave the shard half-committed.
98+ fn run_sub_plans (
99+ & mut self ,
100+ task : & ExecutionTask ,
101+ tid : u64 ,
102+ plans : & [ PhysicalPlan ] ,
103+ mut undo_log : Vec < UndoEntry > ,
104+ mut crdt_deltas : Vec < CrdtDelta > ,
105+ ) -> Result < ( Response , Vec < UndoEntry > , Vec < CrdtDelta > ) , Response > {
45106 let mut last_response = self . response_ok ( task) ;
46107
47108 for ( i, plan) in plans. iter ( ) . enumerate ( ) {
48- // Wrap the sub-apply + post-apply fail-point in catch_unwind so a
49- // panic (real or test-injected) routes through the typed-rollback
50- // arm below instead of unwinding past `undo_log`. Without this,
51- // a panic between sub-applies would drop the undo log without
52- // running rollback and leave the shard half-committed.
53109 let user_roles = & task. request . user_roles ;
54110 let outcome = catch_unwind ( AssertUnwindSafe ( || {
55111 let r = self . execute_tx_sub_plan (
@@ -118,20 +174,32 @@ impl CoreLoop {
118174 // Discard CRDT scratch buffer (never applied).
119175 drop ( crdt_deltas) ;
120176
121- return Response {
177+ return Err ( Response {
122178 request_id : task. request_id ( ) ,
123179 status : Status :: Error ,
124180 attempt : 1 ,
125181 partial : false ,
126182 payload : crate :: bridge:: envelope:: Payload :: empty ( ) ,
127183 watermark_lsn : self . watermark ,
128184 error_code : Some ( rollback_error_code) ,
129- } ;
185+ } ) ;
130186 }
131187 }
132188 }
133189
134- // Pre-commit: BALANCED constraint check across all inserts in this transaction.
190+ Ok ( ( last_response, undo_log, crdt_deltas) )
191+ }
192+
193+ /// Pre-commit: check the `BALANCED` constraint across all inserts in
194+ /// this transaction. On violation, rolls back and returns the terminal
195+ /// error `Response`; on success, hands the undo log back for the
196+ /// deferred-trigger step.
197+ fn apply_balanced_constraint_check (
198+ & mut self ,
199+ task : & ExecutionTask ,
200+ tid : u64 ,
201+ undo_log : Vec < UndoEntry > ,
202+ ) -> Result < Vec < UndoEntry > , Response > {
135203 if let Err ( error_code) =
136204 self . check_balanced_constraints ( task. request . database_id . as_u64 ( ) , tid, & undo_log)
137205 {
@@ -160,24 +228,35 @@ impl CoreLoop {
160228 }
161229 }
162230 } ;
163- return Response {
231+ return Err ( Response {
164232 request_id : task. request_id ( ) ,
165233 status : Status :: Error ,
166234 attempt : 1 ,
167235 partial : false ,
168236 payload : crate :: bridge:: envelope:: Payload :: empty ( ) ,
169237 watermark_lsn : self . watermark ,
170238 error_code : Some ( rollback_error_code) ,
171- } ;
239+ } ) ;
172240 }
241+ Ok ( undo_log)
242+ }
173243
174- // All sub-plans succeeded. Apply buffered CRDT deltas.
175- // Failure here means the CRDT state is inconsistent with the already-committed
176- // forward writes — return RollbackFailed so the client knows the shard needs
177- // a restart to restore consistency via WAL replay. Never warn-and-continue.
244+ /// Apply all buffered CRDT deltas now that every sub-plan and the
245+ /// `BALANCED` constraint check have succeeded.
246+ ///
247+ /// Failure here means the CRDT state is inconsistent with the
248+ /// already-committed forward writes — returns a `RollbackFailed`
249+ /// `Response` so the client knows the shard needs a restart to restore
250+ /// consistency via WAL replay. Never warn-and-continue.
251+ fn apply_crdt_deltas (
252+ & mut self ,
253+ task : & ExecutionTask ,
254+ tid : u64 ,
255+ crdt_deltas : Vec < CrdtDelta > ,
256+ ) -> Option < Response > {
178257 for ( crdt_idx, ( delta, _peer_id, collection) ) in crdt_deltas. into_iter ( ) . enumerate ( ) {
179- // Second crash -injection point — between forward-write commit and
180- // CRDT apply. WAL replay must roll the CRDT side forward (or roll
258+ // Crash -injection point — between forward-write commit and CRDT
259+ // apply. WAL replay must roll the CRDT side forward (or roll
181260 // forward writes back) to restore consistency.
182261 crate :: fail_point!( "transaction_batch::between_crdt_delta" ) ;
183262
@@ -197,7 +276,7 @@ impl CoreLoop {
197276 "CRDT delta apply failed after forward writes committed; \
198277 shard state unknown — restart required for WAL replay"
199278 ) ;
200- return Response {
279+ return Some ( Response {
201280 request_id : task. request_id ( ) ,
202281 status : Status :: Error ,
203282 attempt : 1 ,
@@ -208,7 +287,7 @@ impl CoreLoop {
208287 entry_index : crdt_idx,
209288 detail : format ! ( "CRDT delta apply failed: {e}" ) ,
210289 } ) ,
211- } ;
290+ } ) ;
212291 }
213292 }
214293 Err ( e) => {
@@ -219,7 +298,7 @@ impl CoreLoop {
219298 "CRDT engine not found after forward writes committed; \
220299 shard state unknown — restart required for WAL replay"
221300 ) ;
222- return Response {
301+ return Some ( Response {
223302 request_id : task. request_id ( ) ,
224303 status : Status :: Error ,
225304 attempt : 1 ,
@@ -230,18 +309,16 @@ impl CoreLoop {
230309 entry_index : crdt_idx,
231310 detail : format ! ( "CRDT engine not available: {e}" ) ,
232311 } ) ,
233- } ;
312+ } ) ;
234313 }
235314 }
236315 }
316+ None
317+ }
237318
238- debug ! (
239- core = self . core_id,
240- committed = plans. len( ) ,
241- "transaction batch committed"
242- ) ;
243-
244- // Emit deferred trigger events for all writes in the committed transaction.
319+ /// Emit deferred trigger events for every write recorded in the
320+ /// committed transaction's undo log.
321+ fn emit_deferred_writes ( & mut self , task : & ExecutionTask , undo_log : Vec < UndoEntry > ) {
245322 use crate :: data:: executor:: core_loop:: deferred:: DeferredWrite ;
246323 let deferred_writes: Vec < DeferredWrite > = undo_log
247324 . into_iter ( )
@@ -286,17 +363,6 @@ impl CoreLoop {
286363 task. request . vshard_id ,
287364 ) ;
288365 }
289-
290- // Return the last sub-plan payload, but keyed to the outer transaction request.
291- Response {
292- request_id : task. request_id ( ) ,
293- status : Status :: Ok ,
294- attempt : 1 ,
295- partial : false ,
296- payload : last_response. payload ,
297- watermark_lsn : self . watermark ,
298- error_code : None ,
299- }
300366 }
301367}
302368
0 commit comments