Line data Source code
1 : /** @file oom.c
2 : * @brief
3 : * @author François Cerbelle (Fanfan), francois@cerbelle.net
4 : *
5 : * Check this implementation https://github.com/bkthomps/Containers/blob/master/tst/test.c
6 : *
7 : * @internal
8 : * Created: 21/06/2024
9 : * Revision: none
10 : * Last modified: 2024-11-27 22:26
11 : * Compiler: gcc
12 : * Organization: Cerbelle.net
13 : * Copyright: Copyright (c) 2024, François Cerbelle
14 : *
15 : * This source code is released for free distribution under the terms of the
16 : * GNU General Public License as published by the Free Software Foundation.
17 : */
18 :
19 : #include "oom.h" /* OOM simulation */
20 :
21 : #ifndef __GNUC__
22 : # ifndef __asm__
23 : # define __asm__ asm
24 : # endif
25 : # ifndef __sync_synchronize
26 : # define __sync_synchronize void
27 : # endif
28 : #endif
29 :
30 : #include "debug/assert.h" /* libdebug's Assertions */
31 : #include <sys/resource.h> /* setrlimit */
32 : #include <errno.h> /* errno */
33 : #include <string.h> /* strerror() */
34 : #include <stdio.h> /* printf */
35 : #include <malloc.h> /* malloc_trim() */
36 : #include <stdlib.h> /* abort() */
37 : #include <alloca.h> /* alloca() */
38 :
39 : #ifndef _GNU_SOURCE
40 : #define _GNU_SOURCE
41 : #endif
42 : #include <unistd.h> /* sysconf() */
43 :
44 : /** Maximum number of fragmented blocks to allocate
45 : *
46 : * I never reached more than 350 on my systems.
47 : * The value needs to be less than UINT_MAX
48 : * */
49 : #define RAMBLOCKS_MAX 1000
50 :
51 : /** RAM eating pointer
52 : *
53 : * Used to store the huge memory block used to fill the available memory */
54 : static void* _oomblocks[RAMBLOCKS_MAX] = {0};
55 :
56 : /** getrlimit helper with abort on fail */
57 162908 : static int checked_getrlimit(int resource, struct rlimit *rlim)
58 : {
59 : /* Get current limit values */
60 162908 : if (getrlimit(resource, rlim) != 0) {
61 : /* Can occur, thus not ignored, but impossible to trigger for gcov/lcov */
62 0 : fprintf (stderr,"%s:%d getrlimit() failed with errno=%d %s\n",
63 0 : __FILE__,__LINE__, errno,strerror(errno));
64 0 : abort();
65 : }
66 162908 : return 0;
67 : }
68 :
69 : /** Disabled fill function
70 : *
71 : * Used as oomtest_fill when disabled. Thus, the same test can be surrounded by
72 : * fill/free, but the actual RAM pressure will or will not be activated.
73 : *
74 : * \sa oomtest_fill
75 : * \sa oomtest_free
76 : * */
77 389 : static size_t oomtest_fill_preinit(const size_t minHeap, const size_t minStack)
78 : {
79 : (void)minHeap;
80 : (void)minStack;
81 : ASSERT(NULL==_oomblocks[0]);
82 389 : return 0;
83 : }
84 :
85 : /* Documented in header file */
86 : size_t (*oomtest_fill)(const size_t minHeap, const size_t minStack)=oomtest_fill_preinit;
87 :
88 : /** Disabled free function
89 : *
90 : * Used as oomtest_free when disabled. Thus, the same test can be surrounded by
91 : * fill/free, but the actual RAM pressure will or will not be activated.
92 : *
93 : * \sa oomtest_free
94 : * \sa oomtest_fill
95 : * */
96 388 : static void oomtest_free_preinit()
97 : {
98 : ASSERT(NULL==_oomblocks[0]);
99 388 : }
100 :
101 : /* Documented in header file */
102 : void (*oomtest_free)()=oomtest_free_preinit;
103 :
104 : /** Disabled oomtest_enable function
105 : *
106 : * Used as oomtest_enable before oomtest_config was invoked, which should never
107 : * happen and will fail to detect mistakes in the test code.
108 : *
109 : * \sa oomtest_enable
110 : * \sa oomtest_disable
111 : * */
112 8 : static size_t oomtest_enable_preinit(const size_t softlimit)
113 : {
114 : (void)softlimit;
115 :
116 : /* Should not be called without configuration */
117 8 : fprintf(stderr,"%s:%d oomtest_enable called without oomtest_config before\n",__FILE__,__LINE__);
118 8 : abort();
119 : }
120 :
121 : /* Documented in header file */
122 : size_t (*oomtest_enable)(const size_t softlimit)=oomtest_enable_preinit;
123 :
124 : /** Disabled oomtest_disable function
125 : *
126 : * Used as oomtest_disable before oomtest_config was invoked, which should never
127 : * happen and will fail to detect mistakes in the test code.
128 : *
129 : * \sa oomtest_enable
130 : * \sa oomtest_disable
131 : * */
132 2 : static size_t oomtest_disable_preinit()
133 : {
134 : /* Should not be called before configuration */
135 2 : fprintf(stderr,"%s:%d oomtest_disable called without oomtest_config before\n",__FILE__,__LINE__);
136 2 : abort();
137 : }
138 :
139 : /* Documented in header file */
140 : size_t (*oomtest_disable)()=oomtest_disable_preinit;
141 :
142 : /** Finds biggest available RAM block and allocate it
143 : *
144 : * This functions tries to find and allocate the biggest available memory block
145 : * by dichotomy (Olog2 complexity) between 0 and rlim_cur soft limit. It returns
146 : * the pointer.
147 : *
148 : * \param [in,out] The allocated pointer if successfull, NULL otherwise
149 : *
150 : * \return The size of the allocated block, 0 if not block was allocated.
151 : *
152 : * */
153 131874 : static size_t oomtest_getbiggestblock(void** p_ramblock)
154 : {
155 : /* This function could receive an optimized "max" value and return the final
156 : * "max" value to the caller. So, it could be used as the next invocation
157 : * starting "max" value. This would save few loop iterations, at the cost of
158 : * extra stack usage, I chose to not pass this value as a parameter to avoid
159 : * consuming stack.
160 : * The function starts to search between 0..rlim_cur which has very little
161 : * impact given the Olog2 complexity. It could be optimized and start to
162 : * search between 0..sysconf(AVPHYSPAGE*PAGESIZE), assuming that sysconf is
163 : * faster than few loop iterations. */
164 :
165 : /* Use static to avoid stack allocation/free */
166 : static size_t max,cur;
167 : static struct rlimit limit;
168 :
169 : ASSERT(NULL!=p_ramblock);
170 : ASSERT(NULL==*p_ramblock);
171 :
172 : /* Get the current limits */
173 131874 : checked_getrlimit(RLIMIT_AS, &limit);
174 131874 : max = limit.rlim_cur;
175 :
176 : /* Restart the whole process if cur can not be allocated at the end */
177 266465 : while ((max>0)&&(NULL==*p_ramblock)) {
178 : static size_t min;
179 : /* Iterate quickly (Olog2) to converge to the biggest available RAM block */
180 134591 : min = 0;
181 3193454 : while (max>min) {
182 3058863 : cur = min+(max-min)/2; /* To avoid overflow */
183 3058863 : if (NULL==(*p_ramblock = malloc(cur))) {
184 2565106 : max = cur;
185 : } else {
186 493757 : min = cur+1;
187 493757 : free(*p_ramblock);
188 : }
189 : }
190 134591 : cur -= 1;
191 134591 : *p_ramblock=malloc(cur);
192 : }
193 :
194 131874 : return cur;
195 : }
196 :
197 : /** Enabled fill function
198 : *
199 : * Used as oomtest_fill when enabled.
200 : *
201 : * \sa oomtest_fill
202 : * \sa oomtest_free
203 : * */
204 4894 : static size_t oomtest_fill_postinit(const size_t minHeap, const size_t minStack)
205 : {
206 : unsigned int l_numblock;
207 : size_t l_sum;
208 : void* volatile l_reservedheap;
209 : void* l_reservedstack;
210 :
211 : /* Probably a mistake in the test code, do not accept despite we could */
212 4894 : if (NULL!=_oomblocks[0]) {
213 : /* Dirty hack to free some RAM and allow abort to SIGABRT */
214 30 : free(_oomblocks[0]);
215 30 : fprintf (stderr,"%s:%d oomblocks are already allocated\n", __FILE__,__LINE__);
216 30 : abort();
217 : }
218 :
219 : /* Reserve/Protect stack bytes which will be auto freed at return */
220 : /* A failure means a stackoverflow, which is not recoverable and will abort
221 : * the process anyway. */
222 4864 : if (0<minStack)
223 4864 : l_reservedstack=alloca(minStack);
224 : (void)l_reservedstack;
225 :
226 : /* Reserve/Protect heap bytes which will be released before return */
227 4864 : l_reservedheap=NULL;
228 4864 : if (0<minHeap)
229 4485 : if (NULL==(l_reservedheap=malloc(minHeap))) {
230 : /* This will fail if minHeap is higher than rlimit */
231 2 : fprintf(stderr,"%s:%d Failed to reserve minheap bytes\n",__FILE__,__LINE__);
232 2 : abort();
233 : }
234 :
235 : /* Find and allocate the biggest available RAM blocks until no mre RAM
236 : * available or all _oomblocks are allocated */
237 4862 : l_numblock=0;
238 4862 : l_sum = 0;
239 4862 : l_sum += oomtest_getbiggestblock(&(_oomblocks[l_numblock++]));
240 131874 : while ((NULL!=_oomblocks[l_numblock-1])&&((RAMBLOCKS_MAX-1)>l_numblock))
241 127012 : l_sum += oomtest_getbiggestblock(&(_oomblocks[l_numblock++]));
242 : /* Either already NULL, which stopped the while loop or reached the last
243 : * slot in the table, which stopped the while loop and the slot has to be
244 : * set to NULL */
245 4862 : _oomblocks[l_numblock]=NULL;
246 :
247 : /* There can be less than 4 bytes available at this point !!! */
248 :
249 : /* Make the protected heap bytes available again */
250 4862 : if (NULL!=l_reservedheap)
251 4483 : free(l_reservedheap);
252 :
253 4862 : return l_sum;
254 : }
255 :
256 : /** Enabled free function
257 : *
258 : * Used as oomtest_free when enabled
259 : *
260 : * \sa oomtest_free
261 : * \sa oomtest_fill
262 : * */
263 4836 : static void oomtest_free_postinit()
264 : {
265 : unsigned int l_i;
266 :
267 : /* Despite we could manage, abort to help detecting mistakes in test code */
268 4836 : if (NULL==_oomblocks[0]) {
269 4 : fprintf(stderr,"%s:%d no blocks to free in oomtest_free.\n",__FILE__,__LINE__);
270 4 : abort();
271 : }
272 :
273 : /* Actually free allocated blocks and set their pointer to NULL */
274 4832 : l_i = 0;
275 131512 : while ((l_i<(RAMBLOCKS_MAX-1))&&(_oomblocks[l_i])) {
276 126680 : free(_oomblocks[l_i]);
277 126680 : _oomblocks[l_i] = NULL;
278 126680 : l_i+=1;
279 : }
280 : /* These barrier protections are probably useless */
281 4832 : malloc_trim(0);
282 : /* SW barrier (compiler only) */
283 4832 : __asm__ volatile("": : :"memory");
284 : /* HW barrier (CPU instruction) */
285 4832 : __sync_synchronize();
286 4832 : }
287 :
288 : /** Enabled oomtest_enable function
289 : *
290 : * Used as oomtest_enable after oomtest_config was invoked
291 : *
292 : * \sa oomtest_enable
293 : * \sa oomtest_disable
294 : * */
295 19446 : static size_t oomtest_enable_postinit(const size_t softlimit)
296 : {
297 : struct rlimit limit;
298 19446 : size_t l_softlimit = softlimit;
299 :
300 : /* Probably a bug in oomtest */
301 : ASSERT(NULL==_oomblocks[0]);
302 :
303 : /* Get current limit values */
304 19446 : checked_getrlimit(RLIMIT_AS, &limit);
305 :
306 : /* Check the requested value */
307 19446 : if (0==l_softlimit) {
308 : /* Defaults to available physical RAM if requested 0 */
309 7515 : l_softlimit = limit.rlim_max;
310 : };
311 :
312 : /* Soft limit available RAM */
313 19446 : limit.rlim_cur = l_softlimit;
314 :
315 : /* Abort if setrlimit fails to avoid RAM bombing */
316 19446 : if (setrlimit(RLIMIT_AS, &limit) != 0) {
317 4 : fprintf (stderr,"%s:%d setrlimit(cur=%lu, max=%lu) with errno=%d %s\n",
318 : __FILE__,__LINE__,
319 2 : (unsigned long)limit.rlim_cur, (unsigned long)limit.rlim_max,
320 2 : errno,strerror(errno));
321 2 : abort();
322 : }
323 :
324 : /* Activate fill/free functions */
325 19444 : oomtest_fill = oomtest_fill_postinit;
326 19444 : oomtest_free = oomtest_free_postinit;
327 :
328 : /* Return the actual current soft limit */
329 19444 : return limit.rlim_cur;
330 : }
331 :
332 : /** Enabled oomtest_disable function
333 : *
334 : * Used as oomtest_disable after oomtest_config was invoked
335 : *
336 : * \sa oomtest_enable
337 : * \sa oomtest_disable
338 : * */
339 7518 : static size_t oomtest_disable_postinit()
340 : {
341 : struct rlimit limit;
342 :
343 7518 : if (NULL!=_oomblocks[0]) {
344 0 : fprintf(stderr,"%s:%d RAM still allocated while calling oomtest_disable\n",__FILE__,__LINE__);
345 0 : abort();
346 : }
347 :
348 : /* Do not allow calling disable if not enabled to detect test mistakes */
349 7518 : if ((oomtest_fill!=oomtest_fill_postinit)||(oomtest_free!=oomtest_free_postinit)) {
350 6 : fprintf(stderr,"%s:%d Impossible to disable oomtest if not previously enabled\n",__FILE__,__LINE__);
351 6 : abort();
352 : }
353 :
354 : /* Reset the soft limit to hard limit */
355 7512 : oomtest_enable_postinit(0);
356 :
357 : /* Get current limit values */
358 7512 : checked_getrlimit(RLIMIT_AS, &limit);
359 :
360 : /* Restore disabled functors */
361 7512 : oomtest_fill = oomtest_fill_preinit;
362 7512 : oomtest_free = oomtest_free_preinit;
363 :
364 : /* Return the actual current soft limit, which is equal to hard limit */
365 7512 : return limit.rlim_cur;
366 : }
367 :
368 : /* Documented in header file */
369 4074 : size_t oomtest_config(const size_t hardlimit)
370 : {
371 : struct rlimit limit;
372 : size_t l_avail;
373 4074 : size_t l_hardlimit = hardlimit;
374 :
375 : /* Probably a test implementation mistake */
376 4074 : if (NULL!=_oomblocks[0]) {
377 0 : fprintf(stderr,"%s:%d Calling oomtest_config with allocated RAM blocks is not allowed.\n",
378 : __FILE__,__LINE__);
379 0 : abort();
380 : }
381 :
382 : /* Find *installed* physical RAM, 0 in case of failure */
383 4074 : l_avail = (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE));
384 :
385 : /* Get current limit values */
386 4074 : checked_getrlimit(RLIMIT_AS, &limit);
387 4074 : if (0==l_hardlimit) {
388 : /* Defaults to available physical RAM or already set rlimit
389 : * if 0 requested */
390 3 : l_hardlimit=(limit.rlim_max<l_avail?limit.rlim_max:l_avail);
391 4071 : } else if (l_hardlimit>l_avail) {
392 : /* Fails if request is over available physical RAM to avoir swapping */
393 2 : fprintf(stderr,"%s:%d Requesing a limit %lu bigger than *installed* RAM %lu is not allowed.\n",
394 : __FILE__,__LINE__,
395 : (unsigned long)l_hardlimit,(unsigned long)l_avail);
396 2 : abort();
397 : }
398 :
399 : /* Hard limit available RAM to hardlimit globally with no way back */
400 4072 : limit.rlim_cur = l_hardlimit;
401 4072 : limit.rlim_max = l_hardlimit;
402 :
403 : /* Abort if setrlimit fails to avoid RAM bombing */
404 4072 : if (setrlimit(RLIMIT_AS, &limit) != 0) {
405 4 : fprintf (stderr,"%s:%d setrlimit(cur=%lu, max=%lu) with errno=%d %s\n",
406 2 : __FILE__,__LINE__, (unsigned long)limit.rlim_cur,
407 2 : (unsigned long)limit.rlim_max, errno,strerror(errno));
408 : /* Get current limit values */
409 2 : checked_getrlimit(RLIMIT_AS, &limit);
410 2 : fprintf (stderr,"%s:%d getrlimit() is cur=%lu, max=%lu\n",
411 2 : __FILE__,__LINE__, (unsigned long)limit.rlim_cur,
412 2 : (unsigned long)limit.rlim_max);
413 2 : abort();
414 : }
415 :
416 : /* Activate enable/disable functions */
417 4070 : oomtest_enable = oomtest_enable_postinit;
418 4070 : oomtest_disable = oomtest_disable_postinit;
419 :
420 : /* Return the configured limit hard=soft */
421 4070 : return limit.rlim_max;
422 : }
423 :
424 1363 : unsigned char oomtest_enabled()
425 : {
426 1363 : return (oomtest_fill==oomtest_fill_postinit?1:0);
427 : }
|