33
44#![ cfg( feature = "dhat-compat" ) ]
55
6+ use std:: sync:: Mutex ;
7+
68use mod_alloc:: dhat_compat:: { ad_hoc_event, AdHocStats , Alloc , HeapStats , Profiler } ;
79
810#[ global_allocator]
911static ALLOC : Alloc = Alloc ;
1012
13+ // `live_count` / `curr_blocks` is a process-wide counter shared
14+ // across every test in this binary. Cargo runs tests in parallel
15+ // by default, so a test that allocates and a test that reads the
16+ // counter race continuously. We serialise *every* test in this
17+ // file behind one mutex so the precise-relative claims about
18+ // `curr_blocks` in `live_block_count_rises_and_falls` are not
19+ // disturbed by allocations from sibling tests running in parallel
20+ // threads. The file's total runtime stays under a second so
21+ // sequential execution costs nothing.
22+ static TEST_LOCK : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
23+
24+ fn lock ( ) -> std:: sync:: MutexGuard < ' static , ( ) > {
25+ TEST_LOCK . lock ( ) . unwrap_or_else ( |p| p. into_inner ( ) )
26+ }
27+
1128#[ inline( never) ]
1229fn workload ( n : usize ) -> Vec < Vec < u8 > > {
1330 let mut keep: Vec < Vec < u8 > > = Vec :: with_capacity ( n) ;
@@ -19,6 +36,7 @@ fn workload(n: usize) -> Vec<Vec<u8>> {
1936
2037#[ test]
2138fn alloc_swap_pattern_compiles_and_tracks_total_bytes ( ) {
39+ let _g = lock ( ) ;
2240 let before = HeapStats :: get ( ) ;
2341 let kept = workload ( 16 ) ;
2442 let after = HeapStats :: get ( ) ;
@@ -35,28 +53,43 @@ fn alloc_swap_pattern_compiles_and_tracks_total_bytes() {
3553
3654#[ test]
3755fn live_block_count_rises_and_falls ( ) {
38- let baseline = HeapStats :: get ( ) . curr_blocks ;
56+ let _g = lock ( ) ;
57+
58+ // `curr_blocks` is a process-wide instantaneous counter. The
59+ // test harness runs worker threads that allocate and
60+ // deallocate outside our mutex, and `max_blocks` (the
61+ // high-water mark) may already exceed any modest delta we
62+ // can produce — so neither `before+N` nor `max + N` is a
63+ // race-free assertion.
64+ //
65+ // What IS provable: while we *own* `kept_count` live
66+ // allocations, the process-wide `curr_blocks` count
67+ // must include them — it cannot drop below `kept_count`
68+ // because we have not released them yet.
3969 let kept = workload ( 8 ) ;
40- let peak = HeapStats :: get ( ) ;
4170 let kept_count = kept. len ( ) ;
71+ let with_kept = HeapStats :: get ( ) ;
4272 drop ( kept) ;
43- let after = HeapStats :: get ( ) ;
73+ let after_drop = HeapStats :: get ( ) ;
4474
4575 assert ! (
46- peak . curr_blocks >= baseline + kept_count,
47- "curr_blocks {} should be at least baseline {} + {}" ,
48- peak . curr_blocks ,
49- baseline ,
76+ with_kept . curr_blocks >= kept_count,
77+ "while {} vecs are alive, curr_blocks ({}) must be >= {}" ,
78+ kept_count ,
79+ with_kept . curr_blocks ,
5080 kept_count
5181 ) ;
5282 assert ! (
53- after. curr_blocks <= peak. curr_blocks,
54- "curr_blocks should fall after drop"
83+ after_drop. max_blocks >= with_kept. max_blocks,
84+ "max_blocks is monotonic; after_drop ({}) must be >= with_kept ({})" ,
85+ after_drop. max_blocks,
86+ with_kept. max_blocks
5587 ) ;
5688}
5789
5890#[ test]
5991fn profiler_drop_writes_file_with_dhat_json_shape ( ) {
92+ let _g = lock ( ) ;
6093 let path = std:: env:: temp_dir ( ) . join ( format ! (
6194 "mod-alloc-dhat-compat-test-{}-{}.json" ,
6295 std:: process:: id( ) ,
@@ -93,6 +126,7 @@ fn profiler_drop_writes_file_with_dhat_json_shape() {
93126
94127#[ test]
95128fn testing_mode_suppresses_drop_write ( ) {
129+ let _g = lock ( ) ;
96130 let path = std:: env:: temp_dir ( ) . join ( format ! (
97131 "mod-alloc-dhat-compat-testing-{}-{}.json" ,
98132 std:: process:: id( ) ,
@@ -119,6 +153,7 @@ fn testing_mode_suppresses_drop_write() {
119153
120154#[ test]
121155fn ad_hoc_event_accumulates_counts_and_weights ( ) {
156+ let _g = lock ( ) ;
122157 let before = AdHocStats :: get ( ) ;
123158 ad_hoc_event ( 7 ) ;
124159 ad_hoc_event ( 3 ) ;
@@ -129,6 +164,7 @@ fn ad_hoc_event_accumulates_counts_and_weights() {
129164
130165#[ test]
131166fn trim_backtraces_accepts_oversize_value_without_panic ( ) {
167+ let _g = lock ( ) ;
132168 // 100 > walker cap of 8 — must not panic, must still build.
133169 let _p = Profiler :: builder ( )
134170 . testing ( )
@@ -138,6 +174,7 @@ fn trim_backtraces_accepts_oversize_value_without_panic() {
138174
139175#[ test]
140176fn profiler_new_heap_constructs_and_drops_cleanly ( ) {
177+ let _g = lock ( ) ;
141178 // Default `dhat-heap.json` write to CWD would litter the
142179 // workspace, so we route it to tmp instead.
143180 let path = std:: env:: temp_dir ( ) . join ( format ! (
0 commit comments