liboom 1.0.1
Debugging helper library with Memory leak detection
oomfill.c
Go to the documentation of this file.
1
17#include "liboom/oomfill.h" /* OOM simulation */
18
19#include <assert.h>
20#include <stdio.h> /* printf */
21#include <stdlib.h> /* abort() */
22#include <errno.h> /* errno */
23#include <sys/resource.h> /* setrlimit/getrlimit */
24#include <string.h> /* strerror() */
25#include <alloca.h> /* alloca() */
26#include <unistd.h> /* sysconf() */
27
33#define RAMBLOCKS_MAX 1000
34
38static void* _oomblocks[RAMBLOCKS_MAX] = {0};
39
41static int checked_getrlimit(int resource, struct rlimit *rlim) {
42 /* Get current limit values */
43 if (getrlimit(resource, rlim) != 0) {
44 /* Can occur, thus not ignored, but impossible to trigger for gcov/lcov */
45 fprintf (stderr,"%s:%d getrlimit() failed with errno=%d %s\n",
46 __FILE__,__LINE__, errno,strerror(errno));
47 abort();
48 }
49 return 0;
50}
51
60static size_t oomfill_fill_preinit(const size_t minHeap, const size_t minStack) {
61 (void)minHeap;
62 (void)minStack;
63 assert(NULL==_oomblocks[0]);
64 return 0;
65}
66
96size_t (*oomfill_fill)(const size_t minHeap, const size_t minStack)=oomfill_fill_preinit;
97
106static void oomfill_free_preinit() {
107 assert(NULL==_oomblocks[0]);
108}
109
125void (*oomfill_free)()=oomfill_free_preinit;
126
135static size_t oomfill_enable_preinit(const size_t softlimit) {
136 (void)softlimit;
137
138 /* Should not be called without configuration */
139 fprintf(stderr,"%s:%d oomfill_enable called without oomfill_config before\n",__FILE__,__LINE__);
140 abort();
141}
142
171size_t (*oomfill_enable)(const size_t softlimit)=oomfill_enable_preinit;
172
181static size_t oomfill_disable_preinit() {
182 /* Should not be called before configuration */
183 fprintf(stderr,"%s:%d oomfill_disable called without oomfill_config before\n",__FILE__,__LINE__);
184 abort();
185}
186
210size_t (*oomfill_disable)()=oomfill_disable_preinit;
211
223static size_t oomfill_getbiggestblock(void** p_ramblock) {
224 /* This function could receive an optimized "max" value and return the final
225 * "max" value to the caller. So, it could be used as the next invocation
226 * starting "max" value. This would save few loop iterations, at the cost of
227 * extra stack usage, I chose to not pass this value as a parameter to avoid
228 * consuming stack.
229 * The function starts to search between 0..rlim_cur which has very little
230 * impact given the Olog2 complexity. It could be optimized and start to
231 * search between 0..sysconf(AVPHYSPAGE*PAGESIZE), assuming that sysconf is
232 * faster than few loop iterations. */
233
234 /* Use static to avoid stack allocation/free */
235 static size_t max,cur;
236 static struct rlimit limit;
237
238 assert(NULL!=p_ramblock);
239 assert(NULL==*p_ramblock);
240
241 /* Get the current limits */
242 checked_getrlimit(RLIMIT_AS, &limit);
243 max = limit.rlim_cur;
244
245 /* Restart the whole process if cur can not be allocated at the end */
246 while ((max>0)&&(NULL==*p_ramblock)) {
247 static size_t min;
248 /* Iterate quickly (Olog2) to converge to the biggest available RAM block */
249 min = 0;
250 while (max>min) {
251 cur = min+(max-min)/2; /* To avoid overflow */
252 if (NULL==(*p_ramblock = malloc(cur))) {
253 max = cur;
254 } else {
255 min = cur+1;
256 free(*p_ramblock);
257 }
258 }
259 cur -= 1;
260 *p_ramblock=malloc(cur);
261 }
262
263 return cur;
264}
265
273static size_t oomfill_fill_postinit(const size_t minHeap, const size_t minStack) {
274 unsigned int l_numblock;
275 size_t l_sum;
276 void* volatile l_reservedheap;
277 void* l_reservedstack;
278
279 /* Probably a mistake in the test code, do not accept despite we could */
280 if (NULL!=_oomblocks[0]) {
281 /* Dirty hack to free some RAM and allow abort to SIGABRT */
282 free(_oomblocks[0]);
283 fprintf (stderr,"%s:%d oomblocks are already allocated\n", __FILE__,__LINE__);
284 abort();
285 }
286
287 /* Reserve/Protect stack bytes which will be auto freed at return */
288 /* A failure means a stackoverflow, which is not recoverable and will abort
289 * the process anyway. */
290 if (0<minStack)
291 l_reservedstack=alloca(minStack);
292 (void)l_reservedstack;
293
294 /* Reserve/Protect heap bytes which will be released before return */
295 l_reservedheap=NULL;
296 if (0<minHeap)
297 if (NULL==(l_reservedheap=malloc(minHeap))) {
298 /* This will fail if minHeap is higher than rlimit */
299 fprintf(stderr,"%s:%d Failed to reserve minheap bytes\n",__FILE__,__LINE__);
300 abort();
301 }
302
303 /* Find and allocate the biggest available RAM blocks until no mre RAM
304 * available or all _oomblocks are allocated */
305 l_numblock=0;
306 l_sum = 0;
307 l_sum += oomfill_getbiggestblock(&(_oomblocks[l_numblock++]));
308 while ((NULL!=_oomblocks[l_numblock-1])&&((RAMBLOCKS_MAX-1)>l_numblock))
309 l_sum += oomfill_getbiggestblock(&(_oomblocks[l_numblock++]));
310 /* Either already NULL, which stopped the while loop or reached the last
311 * slot in the table, which stopped the while loop and the slot has to be
312 * set to NULL */
313 _oomblocks[l_numblock]=NULL;
314
315 /* There can be less than 4 bytes available at this point !!! */
316
317 /* Make the protected heap bytes available again */
318 if (NULL!=l_reservedheap)
319 free(l_reservedheap);
320
321 return l_sum;
322}
323
331static void oomfill_free_postinit() {
332 unsigned int l_i;
333
334 /* Despite we could manage, abort to help detecting mistakes in test code */
335 if (NULL==_oomblocks[0]) {
336 fprintf(stderr,"%s:%d no blocks to free in oomfill_free.\n",__FILE__,__LINE__);
337 abort();
338 }
339
340 /* Actually free allocated blocks and set their pointer to NULL */
341 l_i = 0;
342 while ((l_i<(RAMBLOCKS_MAX-1))&&(_oomblocks[l_i])) {
343 free(_oomblocks[l_i]);
344 _oomblocks[l_i] = NULL;
345 l_i+=1;
346 }
347}
348
356static size_t oomfill_enable_postinit(const size_t softlimit) {
357 struct rlimit limit;
358 size_t l_softlimit = softlimit;
359
360 /* Probably a bug in oomfill */
361 assert(NULL==_oomblocks[0]);
362
363 /* Get current limit values */
364 checked_getrlimit(RLIMIT_AS, &limit);
365
366 /* Check the requested value */
367 if (0==l_softlimit) {
368 /* Defaults to available physical RAM if requested 0 */
369 l_softlimit = limit.rlim_max;
370 };
371
372 /* Soft limit available RAM */
373 limit.rlim_cur = l_softlimit;
374
375 /* Abort if setrlimit fails to avoid RAM bombing */
376 if (setrlimit(RLIMIT_AS, &limit) != 0) {
377 fprintf (stderr,"%s:%d setrlimit(cur=%lu, max=%lu) with errno=%d %s\n",
378 __FILE__,__LINE__,
379 (unsigned long)limit.rlim_cur, (unsigned long)limit.rlim_max,
380 errno,strerror(errno));
381 abort();
382 }
383
384 /* Activate fill/free functions */
385 oomfill_fill = oomfill_fill_postinit;
386 oomfill_free = oomfill_free_postinit;
387
388 /* Return the actual current soft limit */
389 return limit.rlim_cur;
390}
391
399static size_t oomfill_disable_postinit() {
400 struct rlimit limit;
401
402 if (NULL!=_oomblocks[0]) {
403 fprintf(stderr,"%s:%d RAM still allocated while calling oomfill_disable\n",__FILE__,__LINE__);
404 abort();
405 }
406
407 /* Do not allow calling disable if not enabled to detect test mistakes */
408 if ((oomfill_fill!=oomfill_fill_postinit)||(oomfill_free!=oomfill_free_postinit)) {
409 fprintf(stderr,"%s:%d Impossible to disable oomfill if not previously enabled\n",__FILE__,__LINE__);
410 abort();
411 }
412
413 /* Reset the soft limit to hard limit */
414 oomfill_enable_postinit(0);
415
416 /* Get current limit values */
417 checked_getrlimit(RLIMIT_AS, &limit);
418
419 /* Restore disabled functors */
420 oomfill_fill = oomfill_fill_preinit;
421 oomfill_free = oomfill_free_preinit;
422
423 /* Return the actual current soft limit, which is equal to hard limit */
424 return limit.rlim_cur;
425}
426
457size_t oomfill_config(const size_t hardlimit) {
458 struct rlimit limit;
459 size_t l_avail;
460 size_t l_hardlimit = hardlimit;
461
462 /* Probably a test implementation mistake */
463 if (NULL!=_oomblocks[0]) {
464 fprintf(stderr,"%s:%d Calling oomfill_config with allocated RAM blocks is not allowed.\n",
465 __FILE__,__LINE__);
466 abort();
467 }
468
469 /* Find *installed* physical RAM, 0 in case of failure */
470 l_avail = (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE));
471
472 /* Get current limit values */
473 checked_getrlimit(RLIMIT_AS, &limit);
474 if (0==l_hardlimit) {
475 /* Defaults to available physical RAM or already set rlimit
476 * if 0 requested */
477 l_hardlimit=(limit.rlim_max<l_avail?limit.rlim_max:l_avail);
478 } else if (l_hardlimit>l_avail) {
479 /* Fails if request is over available physical RAM to avoir swapping */
480 fprintf(stderr,"%s:%d Requesing a limit %lu bigger than *installed* RAM %lu is not allowed.\n",
481 __FILE__,__LINE__,
482 (unsigned long)l_hardlimit,(unsigned long)l_avail);
483 abort();
484 }
485
486 /* Hard limit available RAM to hardlimit globally with no way back */
487 limit.rlim_cur = l_hardlimit;
488 limit.rlim_max = l_hardlimit;
489
490 /* Abort if setrlimit fails to avoid RAM bombing */
491 if (setrlimit(RLIMIT_AS, &limit) != 0) {
492 fprintf (stderr,"%s:%d setrlimit(cur=%lu, max=%lu) with errno=%d %s\n",
493 __FILE__,__LINE__, (unsigned long)limit.rlim_cur,
494 (unsigned long)limit.rlim_max, errno,strerror(errno));
495 /* Get current limit values */
496 checked_getrlimit(RLIMIT_AS, &limit);
497 fprintf (stderr,"%s:%d getrlimit() is cur=%lu, max=%lu\n",
498 __FILE__,__LINE__, (unsigned long)limit.rlim_cur,
499 (unsigned long)limit.rlim_max);
500 abort();
501 }
502
503 /* Activate enable/disable functions */
504 oomfill_enable = oomfill_enable_postinit;
505 oomfill_disable = oomfill_disable_postinit;
506
507 /* Return the configured limit hard=soft */
508 return limit.rlim_max;
509}
510
512 return (oomfill_fill==oomfill_fill_postinit?true:false);
513}
514/* vim: set tw=80: */
size_t(* oomfill_disable)()
Stops the current oomfill.
Definition: oomfill.c:210
#define RAMBLOCKS_MAX
Maximum number of fragmented blocks to allocate.
Definition: oomfill.c:33
size_t(* oomfill_enable)(const size_t softlimit)
Starts a new oomfill environment or reconfigure the soft limit.
Definition: oomfill.c:171
size_t(* oomfill_fill)(const size_t minHeap, const size_t minStack)
Starts an almost OOM single and simple test.
Definition: oomfill.c:96
void(* oomfill_free)()
Ends a single simple OOM test.
Definition: oomfill.c:125
size_t oomfill_config(const size_t hardlimit)
Sets the oomfill helpers hard rlimit and enables the oomfill helper features.
Definition: oomfill.c:457
bool oomfill_enabled()
Definition: oomfill.c:511
Library safely (rlimited) consumming RAM to trigger OOMs (only on POSIX systems)
void * malloc(size_t size)
stdc malloc stub function
Definition: oomstub.c:87