44//! Simple memory allocation tracking for tests
55//!
66//! This module provides a global allocator wrapper that tracks memory usage statistics.
7- //! Unlike the previous tracking-allocator implementation, this does not rely on tracing
8- //! spans, ensuring all allocations are captured regardless of span propagation.
97
108use std:: alloc:: { GlobalAlloc , Layout } ;
9+ use std:: future:: Future ;
1110use std:: sync:: atomic:: { AtomicIsize , AtomicUsize , Ordering } ;
12- use std:: sync:: Mutex ;
1311
1412/// Statistics about memory allocations
1513#[ derive( Debug , Clone , Copy , Default ) ]
@@ -143,6 +141,82 @@ unsafe impl<A: GlobalAlloc> GlobalAlloc for TrackingAllocator<A> {
143141 }
144142}
145143
144+ /// Record allocation statistics while executing a function
145+ ///
146+ /// This function automatically:
147+ /// - Acquires a lock to prevent concurrent tests from interfering
148+ /// - Resets allocation counters before execution
149+ /// - Executes the provided closure
150+ /// - Returns the allocation statistics
151+ ///
152+ /// # Example
153+ ///
154+ /// ```rust
155+ /// use lance_testing::allocator::record_allocations;
156+ ///
157+ /// let stats = record_allocations(|| {
158+ /// // Allocate some memory
159+ /// let v: Vec<u8> = vec![0; 1024 * 1024]; // 1MB
160+ /// drop(v);
161+ /// });
162+ ///
163+ /// assert!(stats.total_bytes_allocated >= 1024 * 1024);
164+ /// ```
165+ pub fn record_allocations < F , R > ( f : F ) -> AllocStats
166+ where
167+ F : FnOnce ( ) -> R ,
168+ {
169+ // Acquire lock to prevent concurrent tests from interfering
170+ let _guard = ALLOC_TEST_MUTEX . blocking_lock ( ) ;
171+
172+ // Reset stats before execution
173+ GLOBAL_STATS . reset ( ) ;
174+
175+ // Execute the closure
176+ let _ = f ( ) ;
177+
178+ // Return the stats
179+ GLOBAL_STATS . get_stats ( )
180+ }
181+
182+ /// Record allocation statistics while executing a future
183+ ///
184+ /// This function automatically:
185+ /// - Acquires a lock to prevent concurrent tests from interfering
186+ /// - Resets allocation counters before execution
187+ /// - Executes the provided closure
188+ /// - Returns the allocation statistics
189+ ///
190+ /// # Example
191+ ///
192+ /// ```rust
193+ /// use lance_testing::allocator::record_allocations_async;
194+ ///
195+ /// let stats = record_allocations_async(async {
196+ /// // Allocate some memory
197+ /// let v: Vec<u8> = vec![0; 1024 * 1024]; // 1MB
198+ /// drop(v);
199+ /// });
200+ ///
201+ /// assert!(stats.total_bytes_allocated >= 1024 * 1024);
202+ /// ```
203+ pub async fn record_allocations_async < F , R > ( f : F ) -> AllocStats
204+ where
205+ F : Future < Output = R > ,
206+ {
207+ // Acquire lock to prevent concurrent tests from interfering
208+ let _guard = ALLOC_TEST_MUTEX . lock ( ) . await ;
209+
210+ // Reset stats before execution
211+ GLOBAL_STATS . reset ( ) ;
212+
213+ // Execute the closure
214+ let _ = f. await ;
215+
216+ // Return the stats
217+ GLOBAL_STATS . get_stats ( )
218+ }
219+
146220/// Get current allocation statistics
147221///
148222/// Returns statistics about memory allocations made by Lance's Rust code.
@@ -153,24 +227,22 @@ unsafe impl<A: GlobalAlloc> GlobalAlloc for TrackingAllocator<A> {
153227/// - Allocations made by other native libraries (e.g., PyArrow, NumPy)
154228/// - Memory-mapped files or shared memory
155229///
156- /// For comprehensive memory tracking in Python tests, use the
157- /// `lance.testing.track_memory()` context manager which combines these stats
158- /// with `tracemalloc` and PyArrow's memory pool statistics.
230+ /// **Note**: For tests, prefer using `record_allocations()` which automatically
231+ /// handles locking and reset.
159232pub fn get_alloc_stats ( ) -> AllocStats {
160233 GLOBAL_STATS . get_stats ( )
161234}
162235
163236/// Reset allocation statistics to zero
164237///
165- /// This resets all counters. Note that for accurate tracking in tests,
166- /// you should use the `#[track_alloc]` macro which ensures single-threaded
167- /// execution and automatic reset/collection.
238+ /// **Note**: For tests, prefer using `record_allocations()` which automatically
239+ /// handles locking and reset.
168240pub fn reset_alloc_stats ( ) {
169241 GLOBAL_STATS . reset ( ) ;
170242}
171243
172244/// Mutex to ensure single-threaded execution of tests that track allocations
173245///
174246/// This prevents stats from different tests running in parallel from mixing.
175- /// The `#[track_alloc]` macro uses this automatically.
176- pub static ALLOC_TEST_MUTEX : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
247+ /// The `record_allocations()` function uses this automatically.
248+ static ALLOC_TEST_MUTEX : tokio :: sync :: Mutex < ( ) > = tokio :: sync :: Mutex :: const_new ( ( ) ) ;
0 commit comments