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