Skip to content

Commit a70dfcf

Browse files
authored
Avoid iterative drop for shallow expression trees (#9657)
Use recursive drop for shallow expressions. While in general iterative drop might be necessary it's only required for deep trees. --------- Signed-off-by: Robert Kruszewski <github@robertk.io>
1 parent d0a59fe commit a70dfcf

1 file changed

Lines changed: 87 additions & 6 deletions

File tree

vortex-array/src/expr/expression.rs

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-License-Identifier: Apache-2.0
22
// SPDX-FileCopyrightText: Copyright the Vortex contributors
33

4+
use std::cell::Cell;
45
use std::fmt;
56
use std::fmt::Debug;
67
use std::fmt::Display;
@@ -266,7 +267,34 @@ impl Display for Expression {
266267
}
267268
}
268269

269-
/// Iterative drop for expression to avoid stack overflows.
270+
// Switch to iterative cleanup after this many recursive drops.
271+
const MAX_DROP_DEPTH: u32 = 32;
272+
273+
thread_local! {
274+
static DROP_DEPTH: Cell<u32> = const { Cell::new(0) };
275+
}
276+
277+
// Increments the thread-local drop depth until the guard is dropped.
278+
struct DropDepthGuard;
279+
280+
impl DropDepthGuard {
281+
fn enter() -> Option<Self> {
282+
DROP_DEPTH.with(|depth| {
283+
let current = depth.get();
284+
(current < MAX_DROP_DEPTH).then(|| {
285+
depth.set(current + 1);
286+
Self
287+
})
288+
})
289+
}
290+
}
291+
292+
impl Drop for DropDepthGuard {
293+
fn drop(&mut self) {
294+
DROP_DEPTH.with(|depth| depth.set(depth.get() - 1));
295+
}
296+
}
297+
270298
impl Drop for Expression {
271299
fn drop(&mut self) {
272300
let Self::Scalar { children, .. } = self else {
@@ -275,14 +303,67 @@ impl Drop for Expression {
275303
let Some(children) = Arc::get_mut(children) else {
276304
return;
277305
};
306+
if children.is_empty() {
307+
return;
308+
}
278309

279310
let mut children_to_drop = std::mem::take(children);
280-
while let Some(mut child) = children_to_drop.pop() {
281-
if let Self::Scalar { children, .. } = &mut child
282-
&& let Some(expr_children) = Arc::get_mut(children)
283-
{
284-
children_to_drop.append(expr_children);
311+
312+
match DropDepthGuard::enter() {
313+
Some(_guard) => drop(children_to_drop),
314+
None => {
315+
while let Some(mut child) = children_to_drop.pop() {
316+
if let Self::Scalar { children, .. } = &mut child
317+
&& let Some(expr_children) = Arc::get_mut(children)
318+
{
319+
children_to_drop.append(expr_children);
320+
}
321+
}
285322
}
286323
}
287324
}
288325
}
326+
327+
#[cfg(test)]
328+
mod tests {
329+
use std::thread;
330+
331+
use super::*;
332+
use crate::expr::lit;
333+
use crate::expr::not;
334+
335+
fn deep_expression(depth: usize) -> Expression {
336+
let mut expr = lit(true);
337+
for _ in 0..depth {
338+
expr = not(expr);
339+
}
340+
expr
341+
}
342+
343+
#[test]
344+
fn deep_expression_drops_within_a_small_stack() -> VortexResult<()> {
345+
const DEPTH: usize = 100_000;
346+
const STACK_SIZE: usize = 256 * 1024;
347+
348+
let dropper = thread::Builder::new()
349+
.stack_size(STACK_SIZE)
350+
.spawn(|| drop(deep_expression(DEPTH)))?;
351+
352+
assert!(
353+
dropper.join().is_ok(),
354+
"dropping a tree of depth {DEPTH} exhausted a {STACK_SIZE} byte stack"
355+
);
356+
357+
Ok(())
358+
}
359+
360+
#[test]
361+
fn shallow_expression_keeps_shared_children() {
362+
let expr = not(lit(true));
363+
let shared = expr.clone();
364+
365+
drop(expr);
366+
367+
assert_eq!(shared.children().len(), 1);
368+
}
369+
}

0 commit comments

Comments
 (0)