@@ -150,5 +150,92 @@ public void Arena_AbsoluteSaturation_HandlesCompactionAndThrowsOOM()
150150 Assert . IsTrue ( fastLane . HasHandle ( hNew ) ,
151151 "The allocator should be able to accept new allocations again after compaction consolidates holes." ) ;
152152 }
153+
154+ /// <summary>
155+ /// Runs 200,000 randomized allocation and free operations with mixed block sizes (16B to 64KB).
156+ /// Proves that out-of-order frees and free-list recycling do not corrupt state or fragment memory to death.
157+ /// </summary>
158+ [ TestMethod ]
159+ [ TestCategory ( "ChaosSafety" ) ]
160+ public void Chaos_200kRandomAllocationsAndFrees_ProvesNoFragmentationOrStateCorrupt ( )
161+ {
162+ // Baseline Snapshot
163+ GC . Collect ( 2 , GCCollectionMode . Forced , true , true ) ;
164+ GC . WaitForPendingFinalizers ( ) ;
165+ GC . Collect ( 2 , GCCollectionMode . Forced , true , true ) ;
166+ var initialManagedMemory = GC . GetTotalMemory ( true ) ;
167+
168+ var config = MemoryManagerConfig . CreateForBulkProcessing ( 32 * 1024 * 1024 ) ; // 32 MB Budget
169+ var activeHandles = new List < MemoryHandle > ( ) ;
170+ var rng = new Random ( 42 ) ; // Deterministic seed for repeatable test runs
171+
172+ const int totalOperations = 200_000 ;
173+
174+ {
175+ using var arena = new MemoryArena ( config ) ;
176+
177+ for ( var i = 0 ; i < totalOperations ; i ++ )
178+ {
179+ // 70% chance to allocate, 30% chance to free random existing handle
180+ if ( activeHandles . Count < 100 && rng . NextDouble ( ) < 0.70 )
181+ {
182+ var size = rng . Next ( 16 , 64 * 1024 ) ; // Mixed sizes: 16B to 64KB
183+
184+ try
185+ {
186+ var handle = arena . Allocate ( size ) ;
187+
188+ // Immediately resolve pointer and verify address validity
189+ var ptr = arena . Resolve ( handle ) ;
190+ Assert . AreNotEqual ( IntPtr . Zero , ptr , "Resolved pointer must never be null." ) ;
191+
192+ activeHandles . Add ( handle ) ;
193+ }
194+ catch ( OutOfMemoryException )
195+ {
196+ // Expected when memory reaches 100% capacity threshold.
197+ // Free a random handle to make room for subsequent iterations.
198+ if ( activeHandles . Count > 0 )
199+ {
200+ var indexToFree = rng . Next ( activeHandles . Count ) ;
201+ arena . Free ( activeHandles [ indexToFree ] ) ;
202+ activeHandles . RemoveAt ( indexToFree ) ;
203+ }
204+ }
205+ }
206+ else if ( activeHandles . Count > 0 )
207+ {
208+ // Free a random handle out-of-order to create memory fragmentation gaps
209+ var indexToFree = rng . Next ( activeHandles . Count ) ;
210+ arena . Free ( activeHandles [ indexToFree ] ) ;
211+ activeHandles . RemoveAt ( indexToFree ) ;
212+ }
213+
214+ if ( i % 1000 == 0 )
215+ {
216+ arena . TickFrame ( ) ;
217+ }
218+ }
219+
220+ // Clean up all remaining active handles before arena disposal
221+ foreach ( var handle in activeHandles )
222+ {
223+ arena . Free ( handle ) ;
224+ }
225+ activeHandles . Clear ( ) ;
226+
227+ } // arena.Dispose() executes here
228+
229+ // Final Leak Check
230+ GC . Collect ( 2 , GCCollectionMode . Forced , true , true ) ;
231+ GC . WaitForPendingFinalizers ( ) ;
232+ GC . Collect ( 2 , GCCollectionMode . Forced , true , true ) ;
233+
234+ var finalManagedMemory = GC . GetTotalMemory ( false ) ;
235+ var managedDelta = Math . Abs ( finalManagedMemory - initialManagedMemory ) ;
236+
237+ Assert . IsTrue ( managedDelta < 150 * 1024 * 1024 ,
238+ $ "Memory leak detected! Retained { managedDelta / ( 1024.0 * 1024.0 ) : F2} MB after Chaos Test.") ;
239+ }
153240 }
154241}
0 commit comments