1use std::cell::{Cell, RefCell};
20use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
21use std::rc::Rc;
22
23use crate::host_boundary::is_host_boundary_abort_payload;
24use crate::node::{
25 boundary_root_for, clear_all_deferred_boundary_root, clear_deferred_boundary_root,
26 drain_committed_boundary_root, BatchTarget, BoundaryRoot, Core,
27};
28use crate::protocol::{AnyValue, Wave};
29
30pub struct BatchCtx {
32 rolled_back: Rc<Cell<bool>>,
33}
34
35impl BatchCtx {
36 pub fn rollback(&self) {
38 self.rolled_back.set(true);
39 }
40}
41
42struct ActiveBatch {
43 targets: Vec<BatchTarget>,
44 order: Vec<usize>,
45 deferred: Vec<Option<Wave<AnyValue>>>,
46 boundary_roots: Vec<BoundaryRoot>,
47 committed: Rc<Cell<bool>>,
48 rolled_back: Rc<Cell<bool>>,
49 collecting: bool,
50}
51
52impl ActiveBatch {
53 fn new(committed: Rc<Cell<bool>>, rolled_back: Rc<Cell<bool>>) -> Self {
54 Self {
55 targets: Vec::new(),
56 order: Vec::new(),
57 deferred: Vec::new(),
58 boundary_roots: Vec::new(),
59 committed,
60 rolled_back,
61 collecting: true,
62 }
63 }
64}
65
66thread_local! {
67 static ACTIVE_BATCH: RefCell<Option<ActiveBatch>> = const { RefCell::new(None) };
68}
69
70pub(crate) fn boundary_drains_blocked() -> bool {
73 ACTIVE_BATCH.with(|b| b.borrow().is_some())
74}
75
76pub(crate) fn collecting_batch() -> bool {
77 ACTIVE_BATCH.with(|b| b.borrow().as_ref().is_some_and(|batch| batch.collecting))
78}
79
80pub(crate) fn active_batch_committed_token() -> Option<Rc<Cell<bool>>> {
84 ACTIVE_BATCH.with(|b| b.borrow().as_ref().map(|batch| batch.committed.clone()))
85}
86
87pub(crate) fn register_boundary_root(target: &Core) {
91 ACTIVE_BATCH.with(|b| {
92 let mut active = b.borrow_mut();
93 let Some(batch) = active.as_mut() else {
94 return;
95 };
96 if batch.boundary_roots.iter().any(|r| r.same_graph(target)) {
97 return;
98 }
99 batch.boundary_roots.push(boundary_root_for(target));
100 });
101}
102
103pub(crate) fn defer_to_batch(target: &Core, wave: Wave<AnyValue>) -> bool {
106 ACTIVE_BATCH.with(|b| {
107 let mut active = b.borrow_mut();
108 let Some(batch) = active.as_mut() else {
109 return false;
110 };
111 if !batch.collecting {
112 return false;
113 }
114 let index = match batch.targets.iter().position(|entry| entry.matches(target)) {
115 Some(index) => index,
116 None => {
117 let Some(target) = BatchTarget::from_core(target) else {
118 return false;
119 };
120 let index = batch.targets.len();
121 batch.targets.push(target);
122 batch.deferred.push(None);
123 batch.order.push(index);
124 index
125 }
126 };
127 batch.deferred[index] = Some(wave);
128 true
129 })
130}
131
132pub(crate) fn committed_after_batch_for_target(target: &Core) -> Option<Rc<Cell<bool>>> {
135 ACTIVE_BATCH.with(|b| {
136 let active = b.borrow();
137 let batch = active.as_ref()?;
138 if !batch.collecting
139 || !batch.targets.iter().enumerate().any(|(index, existing)| {
140 existing.matches(target) && batch.deferred.get(index).is_some_and(Option::is_some)
141 })
142 {
143 return None;
144 }
145 Some(batch.committed.clone())
146 })
147}
148
149struct BatchFinish {
150 targets: Vec<BatchTarget>,
151 order: Vec<usize>,
152 deferred: Vec<Option<Wave<AnyValue>>>,
153 boundary_roots: Vec<BoundaryRoot>,
154 committed: Rc<Cell<bool>>,
155 explicit_rollback: bool,
156}
157
158fn take_for_finish() -> BatchFinish {
159 ACTIVE_BATCH.with(|b| {
160 let mut active = b.borrow_mut();
161 let batch = active.as_mut().expect("batch frame installed");
162 batch.collecting = false;
163 BatchFinish {
164 targets: std::mem::take(&mut batch.targets),
165 order: std::mem::take(&mut batch.order),
166 deferred: std::mem::take(&mut batch.deferred),
167 boundary_roots: std::mem::take(&mut batch.boundary_roots),
168 committed: batch.committed.clone(),
169 explicit_rollback: batch.rolled_back.get(),
170 }
171 })
172}
173
174fn clear_active() {
175 ACTIVE_BATCH.with(|b| {
176 *b.borrow_mut() = None;
177 });
178}
179
180fn commit(targets: &[BatchTarget], deferred: Vec<Option<Wave<AnyValue>>>) {
181 for (target_index, wave) in deferred.into_iter().enumerate() {
182 if let Some(wave) = wave {
183 targets[target_index].commit_batched_wave(wave);
184 }
185 }
186}
187
188fn rollback(targets: &[BatchTarget], order: Vec<usize>) {
189 for target_index in order {
190 targets[target_index].rollback_batched();
191 }
192}
193
194fn boundary_graph_roots(
195 targets: &[BatchTarget],
196 order: &[usize],
197 deferred: &[Option<Wave<AnyValue>>],
198) -> Vec<BoundaryRoot> {
199 let mut roots: Vec<BoundaryRoot> = Vec::new();
200 let mut push_unique = |target: &BatchTarget| {
201 let root = target.boundary_root();
202 if roots.iter().any(|r| r.same_root(&root)) {
203 return;
204 }
205 roots.push(root);
206 };
207 for target_index in order {
208 if let Some(target) = targets.get(*target_index) {
209 push_unique(target);
210 }
211 }
212 for (target_index, wave) in deferred.iter().enumerate() {
213 if wave.is_some() {
214 let Some(target) = targets.get(target_index) else {
215 continue;
216 };
217 push_unique(target);
218 }
219 }
220 roots
221}
222
223fn take_late_boundary_roots() -> Vec<BoundaryRoot> {
224 ACTIVE_BATCH.with(|b| {
225 let mut active = b.borrow_mut();
226 active
227 .as_mut()
228 .map(|batch| std::mem::take(&mut batch.boundary_roots))
229 .unwrap_or_default()
230 })
231}
232
233fn append_boundary_roots(
234 task_roots: &mut Vec<BoundaryRoot>,
235 core_roots: &[BoundaryRoot],
236 boundary_roots: Vec<BoundaryRoot>,
237) {
238 for root in boundary_roots {
239 if core_roots.iter().any(|r| r.same_root(&root))
240 || task_roots.iter().any(|r| r.same_root(&root))
241 {
242 continue;
243 }
244 task_roots.push(root);
245 }
246}
247
248fn clear_boundary_for_roots(core_roots: &[BoundaryRoot], task_roots: &[BoundaryRoot]) {
249 for root in core_roots {
250 clear_deferred_boundary_root(root);
251 }
252 for root in task_roots {
253 clear_deferred_boundary_root(root);
254 }
255}
256
257fn drain_boundary_for_roots(core_roots: &[BoundaryRoot], task_roots: &[BoundaryRoot]) {
258 let mut escaped: Option<Box<dyn std::any::Any + Send>> = None;
259 for root in core_roots {
260 let result = catch_unwind(AssertUnwindSafe(|| drain_committed_boundary_root(root)));
261 if let Err(payload) = result {
262 if is_host_boundary_abort_payload(payload.as_ref()) {
263 clear_all_boundary_for_roots(core_roots, task_roots);
264 resume_unwind(payload);
265 }
266 remember_first_boundary_panic(&mut escaped, Err(payload));
267 }
268 }
269 for root in task_roots {
270 let result = catch_unwind(AssertUnwindSafe(|| drain_committed_boundary_root(root)));
271 if let Err(payload) = result {
272 if is_host_boundary_abort_payload(payload.as_ref()) {
273 clear_all_boundary_for_roots(core_roots, task_roots);
274 resume_unwind(payload);
275 }
276 remember_first_boundary_panic(&mut escaped, Err(payload));
277 }
278 }
279 if let Some(e) = escaped {
280 std::panic::resume_unwind(e);
281 }
282}
283
284fn clear_all_boundary_for_roots(core_roots: &[BoundaryRoot], task_roots: &[BoundaryRoot]) {
285 for root in core_roots {
286 clear_all_deferred_boundary_root(root);
287 }
288 for root in task_roots {
289 clear_all_deferred_boundary_root(root);
290 }
291}
292
293fn remember_first_boundary_panic(
294 escaped: &mut Option<Box<dyn std::any::Any + Send>>,
295 result: std::thread::Result<()>,
296) {
297 if let Err(e) = result {
298 if escaped.is_none() {
299 *escaped = Some(e);
300 }
301 }
302}
303
304fn rollback_and_cleanup(
305 targets: Vec<BatchTarget>,
306 order: Vec<usize>,
307 mut task_roots: Vec<BoundaryRoot>,
308) -> std::thread::Result<()> {
309 let core_roots = boundary_graph_roots(&targets, &order, &[]);
310 let result = catch_unwind(AssertUnwindSafe(|| rollback(&targets, order)));
311 let late_roots = take_late_boundary_roots();
312 append_boundary_roots(&mut task_roots, &core_roots, late_roots);
313 clear_active();
314 clear_boundary_for_roots(&core_roots, &task_roots);
315 result
316}
317
318pub fn batch<R>(f: impl FnOnce(&BatchCtx) -> R) -> R {
322 if let Some(rolled_back) =
323 ACTIVE_BATCH.with(|b| b.borrow().as_ref().map(|batch| batch.rolled_back.clone()))
324 {
325 return f(&BatchCtx { rolled_back });
326 }
327
328 let committed = Rc::new(Cell::new(false));
329 let rolled_back = Rc::new(Cell::new(false));
330 ACTIVE_BATCH.with(|b| {
331 *b.borrow_mut() = Some(ActiveBatch::new(committed.clone(), rolled_back.clone()));
332 });
333
334 let ctx = BatchCtx {
335 rolled_back: rolled_back.clone(),
336 };
337 let result = catch_unwind(AssertUnwindSafe(|| f(&ctx)));
338 let finish = take_for_finish();
339 let boundary_core_roots =
340 boundary_graph_roots(&finish.targets, &finish.order, &finish.deferred);
341 let mut boundary_task_roots = Vec::new();
342 append_boundary_roots(
343 &mut boundary_task_roots,
344 &boundary_core_roots,
345 finish.boundary_roots,
346 );
347
348 match result {
349 Ok(value) => {
350 if finish.explicit_rollback {
351 if let Err(payload) =
352 rollback_and_cleanup(finish.targets, finish.order, boundary_task_roots)
353 {
354 resume_unwind(payload);
355 }
356 } else {
357 let commit_result = catch_unwind(AssertUnwindSafe(|| {
358 commit(&finish.targets, finish.deferred)
359 }));
360 let late_roots = take_late_boundary_roots();
361 append_boundary_roots(&mut boundary_task_roots, &boundary_core_roots, late_roots);
362 if let Err(payload) = commit_result {
363 clear_active();
364 clear_boundary_for_roots(&boundary_core_roots, &boundary_task_roots);
365 resume_unwind(payload);
366 }
367 finish.committed.set(true);
368 clear_active();
369 drain_boundary_for_roots(&boundary_core_roots, &boundary_task_roots);
370 }
371 value
372 }
373 Err(payload) => {
374 if let Err(rollback_payload) =
375 rollback_and_cleanup(finish.targets, finish.order, boundary_task_roots)
376 {
377 resume_unwind(rollback_payload);
378 }
379 resume_unwind(payload);
380 }
381 }
382}