Testing Applications in Low Memory Conditions

The question of whether it is necessary to check what returns mallocis controversial and always generates heated debate.



Some people think that we should try to handle all kinds of runtime errors, incl. and OOM situations. Others believe that there is still little that can be done with OOM, and it is better to let the application just crash. On the side of the second group of people, there is also the fact that the additional OOM processing logic is difficult to test. And if the code is not tested, then it almost certainly does not work.



I totally agree that you shouldn't implement error handling logic that you are not going to test. It almost certainly won't improve anything, or even worse, it will ruin everything.



The question of whether or not to try to handle OOM situations in libraries / applications is controversial and we will not touch on it here. As part of this publication, I just want to share my experience of how you can test the implemented logic for handling OOM situations in applications written in C / C ++. The conversation will be about Linux and macOS operating systems. For a number of reasons, Windows will be bypassed.



Introduction



We all wish that OOM never happened, but in real life this is not always possible due to the following reasons:



  • RAM is always limited.
  • SWAP is not always enabled.
  • Applications do not always behave adequately and sometimes try to allocate unrealistically large amounts of memory interfering with themselves and others.
  • 32-bit applications still exist.
  • overcommit is not always enabled.
  • Memory consumption can be limited using ulimit, for example.
  • LD_PRELOAD .


, , , OOM . , , :



  • , - .
  • OOM . .
  • . , .


, , SQLite. , . . SQLite .



, , , . OOM Killer, , . , C++, .



1.



, OOM . my_malloc my_free malloc free.



my_free . my_realloc.



my_malloc malloc . my_malloc , NULL .



, :



  • 3rd party .
  • malloc . - strdup.
  • malloc’ , , .
  • C++ malloc free.


- .



2.



Linux LD_PRELOAD. . malloc. , malloc/realloc/free (weak). , macOS LD_PRELOAD, DYLD_INSERT_LIBRARIES.



, , LD_PRELOAD DYLD_INSERT_LIBRARIES malloc/realloc NULL .



, "" . , .



, "" , , . :



  • main. , .
  • Runtime macOS " ". , , , .
  • printf macOS SIGSEGV/SIGBUS.
  • , std::bad_alloc, . , , , OOM. std::terminate. .
  • std::thread std::terminate macOS.


UPDATE: Travis CI , macOS / Xcode , std::bad_alloc , std::thread std::terminate.



Overthrower. - malloc NULL. Overthrower - , .



3.



main



, main , main runtime . main, , .. - main.



, main . Overthrower, OOM . Overthrower , .



:



  • activateOverthrower
  • deactivateOverthrower


:



#ifdef __cplusplus
extern "C" {
#endif
void activateOverthrower() __attribute__((weak));
unsigned int deactivateOverthrower() __attribute__((weak));
#ifdef __cplusplus
}
#endif


.



Overthrower LD_PRELOAD, NULL, , .



, , :



int main(int argc, char** argv)
{
    activateOverthrower();
    // Some code we want to test ...
    deactivateOverthrower();
}


activateOverthrower/deactivateOverthrower , :



TEST(Foo, Bar)
{
    activateOverthrower();
    // Some code we want to test ...
    deactivateOverthrower();
}


, -, , Overthrower , :



#ifdef __cplusplus
extern "C" {
#endif
void pauseOverthrower(unsigned int duration) __attribute__((weak));
void resumeOverthrower() __attribute__((weak));
#ifdef __cplusplus
}
#endif


:



TEST(Foo, Bar)
{
    activateOverthrower();
    // Some code we want to test ...
    pauseOverthrower(0);
    // Some fragile code we can not fix ...
    resumeOverthrower();
    // Some code we want to test ...
    deactivateOverthrower();
}


Overthrower .





__cxa_allocate_exception, , , malloc, NULL. , Linux, malloc, __cxa_allocate_exception (emergency buffer), , . .



macOS , , , , std::bad_alloc, std::terminate.



UPDATE: , macOS / Xcode .



, , , __cxa_allocate_exception malloc. - Overthrower’ malloc. Overthrower malloc __cxa_allocate_exception.



, , , macOS __cxa_atexit, Linux dlerror. .



Overthrower , malloc free. Overthrower’ , activateOverthrower deactivateOverthrower , :



overthrower got deactivation signal.
overthrower will not fail allocations anymore.
overthrower has detected not freed memory blocks with following addresses:
0x0000000000dd1e70  -       2  -         128
0x0000000000dd1de0  -       1  -         128
0x0000000000dd1030  -       0  -         128
^^^^^^^^^^^^^^^^^^  |  ^^^^^^  |  ^^^^^^^^^^
      pointer       |  malloc  |  block size
                    |invocation|
                    |  number  |


Overthrower , , .



Overthrower’, , valgrind. , OOM. , , Overthrower . Overthrower , , deactivateOverthrower , stderr .





Overthrower 3 :



  • Random — rand() % duty_cycle == 0. duty_cycle, .
  • Step — (malloc_seq_num >= delay), delay .


<--- delay --->
--------------+
              |
              | All further allocations fail
              |
              +------------------------------


  • Pulse — (malloc_seq_num > delay && malloc_seq_num <= delay + duration), delay duration .


<--- delay --->
--------------+                +------------------------------
              |                |
              |                | All further allocations pass
              |                |
              +----------------+
              <--- duration --->


:



  • OVERTHROWER_STRATEGY
  • OVERTHROWER_SEED
  • OVERTHROWER_DUTY_CYCLE
  • OVERTHROWER_DELAY
  • OVERTHROWER_DURATION


activateOverthrower. , Overthrower , /dev/urandom.



README.md.





  • Overthrower malloc /.
  • .
  • Overthrower .
  • Overthrower .
  • Overthrower , .
  • Overthrower’ .
  • Overthrower Overthrower-aware . , .
  • Overthrower itself is tested on Ubuntu (since 14.04) and macOS (since Sierra (10.12) and Xcode 8.3). During testing, Overthrower tries to drop itself, among other things.
  • If a real OOM appears in the system, Overthrower does everything possible not to fall himself.



All Articles