11// SPDX-License-Identifier: Apache-2.0
22// SPDX-FileCopyrightText: Copyright the Vortex contributors
33
4+ use std:: cell:: Cell ;
45use std:: fmt;
56use std:: fmt:: Debug ;
67use 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+
270298impl 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