@@ -331,6 +331,104 @@ static void test_hg_grow(void)
331331 shm_free (p [i ]);
332332}
333333
334+ /*
335+ * A process leaving for good (auto-scaling down) must give back what it holds
336+ * in the shared arena: its cached cells and the unused rest of the chunks it
337+ * carved. The arena's real_used counts whole carved blocks, so it returns to
338+ * its baseline only if every block the child touched drained. The size is one
339+ * the idle processes of a test run do not use.
340+ */
341+ #define HGT_EXIT_SIZE 20000
342+ #define HGT_EXIT_CELLS 100
343+
344+ static int hgt_child_exit (void * arg )
345+ {
346+ void * p [HGT_EXIT_CELLS ];
347+ int i ;
348+
349+ for (i = 0 ; i < HGT_EXIT_CELLS ; i ++ ) {
350+ p [i ] = shm_malloc (HGT_EXIT_SIZE );
351+ if (!p [i ]) {
352+ LM_ERR ("shm_malloc(%d) failed\n" , HGT_EXIT_SIZE );
353+ return -1 ;
354+ }
355+ }
356+ for (i = 0 ; i < HGT_EXIT_CELLS ; i ++ )
357+ shm_free (p [i ]);
358+
359+ /* report first: the exit below does not return */
360+ * hgt_verdict = HGT_PASS ;
361+ dynamic_process_final_exit ();
362+ return 0 ;
363+ }
364+
365+ static void test_hg_exit_returns_cells (void )
366+ {
367+ struct hg_block * hb = shm_block ;
368+ unsigned long base = hb -> real_used ;
369+ int waited ;
370+
371+ if (!hgt_in_child ("hg exit" , hgt_child_exit , NULL )) {
372+ ok (0 , "a process leaving for good gives back its cells" );
373+ return ;
374+ }
375+
376+ /* the child gives its cells back right before it exits */
377+ for (waited = 0 ; hb -> real_used > base && waited < HGT_WAIT_US ;
378+ waited += HGT_STEP_US )
379+ usleep (HGT_STEP_US );
380+
381+ ok (hb -> real_used <= base , "a process leaving for good gives back its "
382+ "cached cells and unused chunk tails (real_used %lu -> %lu KB)" ,
383+ base >> 10 , hb -> real_used >> 10 );
384+ }
385+
386+ /*
387+ * A double free must be refused and counted, never hand the same memory to
388+ * two callers: the same cell freed twice in a row (it stays on the freeing
389+ * process's private list, which the shared pool's free map cannot see), and
390+ * a large-tier fragment freed twice.
391+ */
392+ static int hgt_child_double_free (void * arg )
393+ {
394+ unsigned long size = (unsigned long )arg ;
395+ void * p , * a , * b ;
396+
397+ p = shm_malloc (size );
398+ if (!p )
399+ return -1 ;
400+ shm_free (p );
401+ shm_free (p );
402+
403+ a = shm_malloc (size );
404+ b = shm_malloc (size );
405+ if (!a || !b || a == b ) {
406+ LM_ERR ("after a double free of %p: two allocations returned %p "
407+ "and %p\n" , p , a , b );
408+ return -1 ;
409+ }
410+ shm_free (a );
411+ shm_free (b );
412+ return 0 ;
413+ }
414+
415+ static void test_hg_double_free (void )
416+ {
417+ struct hg_block * hb = shm_block ;
418+ unsigned long c0 ;
419+
420+ c0 = hb -> corrupt [HG_C_DOUBLE_FREE ];
421+ ok (hgt_in_child ("hg double free" , hgt_child_double_free ,
422+ (void * )200UL ) && hb -> corrupt [HG_C_DOUBLE_FREE ] == c0 + 1 ,
423+ "a cell freed twice in a row is refused and counted, not handed "
424+ "out twice" );
425+
426+ c0 = hb -> corrupt [HG_C_DOUBLE_FREE ];
427+ ok (hgt_in_child ("hg large double free" , hgt_child_double_free ,
428+ (void * )(200UL * 1024 )) && hb -> corrupt [HG_C_DOUBLE_FREE ] == c0 + 1 ,
429+ "a large fragment freed twice is refused and counted" );
430+ }
431+
334432static unsigned long hgt_corruption (void )
335433{
336434 unsigned long t = hg_corrupt_total ((struct hg_block * )shm_block ) +
@@ -359,10 +457,14 @@ void test_hg_malloc(void)
359457 test_hg_cross_process_shm ();
360458 test_hg_large ();
361459 test_hg_grow ();
460+ test_hg_exit_returns_cells ();
362461
363462 ok (hgt_corruption () == 0 , "no corruption detected in the parent's view "
364463 "of the shm and pkg arenas" );
365464
465+ /* last: these count a detection on purpose */
466+ test_hg_double_free ();
467+
366468 shm_free ((void * )hgt_verdict );
367469}
368470
0 commit comments