GCC Code Coverage Report


Directory: ./
File: lib/geogram/basic/process_unix.cpp
Date: 2026-09-07 02:25:23
Exec Total Coverage
Lines: 45 122 36.9%
Functions: 10 21 47.6%
Branches: 8 73 11.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/basic/common.h>
41
42 #ifdef GEO_OS_UNIX
43
44 #include <geogram/basic/process.h>
45 #include <geogram/basic/process_private.h>
46 #include <geogram/basic/logger.h>
47 #include <geogram/basic/progress.h>
48 #include <geogram/basic/line_stream.h>
49
50 #include <sstream>
51 #include <pthread.h>
52 #include <unistd.h>
53 #include <limits.h>
54 #include <fenv.h>
55 #include <sys/types.h>
56 #include <sys/stat.h>
57 #include <sys/time.h>
58 #include <sys/resource.h>
59 #include <unistd.h>
60 #include <signal.h>
61 #include <fcntl.h>
62 #include <string.h>
63 #include <stdio.h>
64 #include <new>
65
66 // MUSL does not have execinfo (so we won't have backtrace with MUSL)
67 #if defined(__has_include)
68 #if __has_include(<execinfo.h>)
69 #include <execinfo.h>
70 #define HAS_EXECINFO
71 #endif
72 #endif
73
74 // detect MUSL that does not have feenableexcepts()/desisableexcepts()
75 #ifdef __linux__
76 #ifndef _GNU_SOURCE
77 #define _GNU_SOURCE
78 #include <features.h>
79 #ifndef __USE_GNU
80 #define __MUSL__
81 #endif
82 #undef _GNU_SOURCE
83 #else
84 #include <features.h>
85 #ifndef __USE_GNU
86 #define __MUSL__
87 #endif
88 #endif
89 #endif
90
91 #ifdef GEO_OS_APPLE
92 #include <mach-o/dyld.h>
93 #ifdef __x86_64
94 #include <xmmintrin.h>
95 #endif
96 #endif
97
98 #ifdef GEO_OS_EMSCRIPTEN
99 #include <emscripten.h>
100 #include <emscripten/threading.h>
101 #endif
102
103 #ifndef GEO_TBB
104 #define GEO_USE_PTHREAD_MANAGER
105 #endif
106
107 // Suppresses a warning with CLANG when sigaction is used.
108 #if defined(__clang__)
109 #pragma clang diagnostic ignored "-Wunknown-pragmas"
110 #pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
111 #endif
112
113 namespace {
114
115 using namespace GEO;
116
117 #ifdef GEO_OS_ANDROID
118
119 /**
120 * \brief Get the number of cores under Android
121 * \retval the number of cores if the request succeeds
122 * \retval -1 otherwise
123 * \internal
124 * sysconf(_SC_NPROCESSORS_ONLN) and sysconf(_SC_NPROCESSORS_CONF)
125 * is bugged under Android, see:
126 * https://code.google.com/p/android/issues/detail?id=26490
127 */
128 int android_get_number_of_cores() {
129 FILE* fp;
130 int res, i = -1, j = -1;
131 /* open file */
132 fp = fopen("/sys/devices/system/cpu/present", "r");
133 if(fp == 0) {
134 return -1; /* failure */
135 }
136
137 /* read and interpret line */
138 res = fscanf(fp, "%d-%d", &i, &j);
139
140 /* close file */
141 fclose(fp);
142
143 /* interpret result */
144 if(res == 1 && i == 0) {
145 /* single-core */
146 return 1;
147 }
148
149 if(res == 2 && i == 0) {
150 /* 2+ cores */
151 return j + 1;
152 }
153
154 return -1; /* failure */
155 }
156
157 #endif
158
159 #ifdef GEO_USE_PTHREAD_MANAGER
160
161 /**
162 * \brief POSIX Thread ThreadManager
163 * \details
164 * PThreadManager is an implementation of ThreadManager that uses POSIX
165 * threads for running concurrent threads and control critical sections.
166 */
167 class GEOGRAM_API PThreadManager : public ThreadManager {
168 public:
169 /**
170 * \brief Creates and initializes the POSIX ThreadManager
171 */
172 249 PThreadManager() {
173 249 pthread_attr_init(&attr_);
174 249 pthread_attr_setdetachstate(&attr_, PTHREAD_CREATE_JOINABLE);
175 249 }
176
177 /** \copydoc GEO::ThreadManager::maximum_concurrent_threads() */
178 2817 index_t maximum_concurrent_threads() override {
179 2817 return Process::number_of_cores();
180 }
181
182
183 protected:
184 /** \brief PThreadManager destructor */
185 996 ~PThreadManager() override {
186 498 pthread_attr_destroy(&attr_);
187 996 }
188
189 /**
190 * \brief Pthread_create callback for running a thread
191 * \details This function is passed a void pointer \p thread to a
192 * Thread and invokes the Thread function run().
193 * \param[in] thread_in void pointer to the Thread to be executed.
194 * \return always null pointer.
195 * \see Thread::run()
196 */
197 12200 static void* run_thread(void* thread_in) {
198 Thread* thread = reinterpret_cast<Thread*>(thread_in);
199 // Sets the thread-local-storage instance pointer, so
200 // that Thread::current() can retrieve it.
201 set_current_thread(thread);
202 12200 thread->run();
203 12200 return nullptr;
204 }
205
206 /** \copydoc GEO::ThreadManager::run_concurrent_threads() */
207 2817 void run_concurrent_threads (
208 ThreadGroup& threads, index_t max_threads
209 ) override {
210 // TODO: take max_threads into account
211 geo_argused(max_threads);
212
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2817 times.
2817 thread_impl_.resize(threads.size());
214
2/2
✓ Branch 0 taken 12200 times.
✓ Branch 1 taken 2817 times.
30034 for(index_t i = 0; i < threads.size(); i++) {
215 Thread* T = threads[i];
216 set_thread_id(T,i);
217 12200 pthread_create(
218 12200 &thread_impl_[i], &attr_, &run_thread, T
219 );
220 }
221
2/2
✓ Branch 0 taken 12200 times.
✓ Branch 1 taken 2817 times.
27217 for(index_t i = 0; i < threads.size(); ++i) {
222 12200 pthread_join(thread_impl_[i], nullptr);
223 }
224
225 2817 }
226
227 private:
228 pthread_attr_t attr_;
229 std::vector<pthread_t> thread_impl_;
230 };
231
232 #endif
233
234 /**
235 * \brief Abnormal termination handler
236 * \details If \p message is
237 * non null, the following message is printed before exiting.
238 * <em>Abnormal program termination: message</em>
239 * \param[in] message optional message to print
240 */
241 GEO_NORETURN_DECL void abnormal_program_termination(
242 const char* message = nullptr
243 ) GEO_NORETURN;
244
245 void abnormal_program_termination(const char* message) {
246 if(message != nullptr) {
247 // Do not use Logger here!
248 std::cout
249 << "Abnormal program termination: "
250 << message << std::endl;
251 }
252 exit(1);
253 }
254
255 /**
256 * \brief Signal handler
257 * \details The handler exits the application
258 * \param[in] signal signal number
259 */
260 GEO_NORETURN_DECL void signal_handler(int signal) GEO_NORETURN;
261
262 void signal_handler(int signal) {
263 const char* sigstr = strsignal(signal);
264 std::ostringstream os;
265 os << "received signal " << signal << " (" << sigstr << ")";
266 Process::os_print_stack_trace();
267 abnormal_program_termination(os.str().c_str());
268 }
269
270 /**
271 * \brief Floating point error handler
272 * \details The handler exits the application
273 * \param[in] signal signal number
274 * \param[in] si signal information structure
275 * \param[in] data additional data (unused)
276 */
277 GEO_NORETURN_DECL void fpe_signal_handler(
278 int signal, siginfo_t* si, void* data
279 ) GEO_NORETURN;
280
281 void fpe_signal_handler(int signal, siginfo_t* si, void* data) {
282 geo_argused(signal);
283 geo_argused(data);
284 const char* error;
285 switch(si->si_code) {
286 case FPE_INTDIV:
287 error = "integer divide by zero";
288 break;
289 case FPE_INTOVF:
290 error = "integer overflow";
291 break;
292 case FPE_FLTDIV:
293 error = "floating point divide by zero";
294 break;
295 case FPE_FLTOVF:
296 error = "floating point overflow";
297 break;
298 case FPE_FLTUND:
299 error = "floating point underflow";
300 break;
301 case FPE_FLTRES:
302 error = "floating point inexact result";
303 break;
304 case FPE_FLTINV:
305 error = "floating point invalid operation";
306 break;
307 case FPE_FLTSUB:
308 error = "subscript out of range";
309 break;
310 default:
311 error = "unknown";
312 break;
313 }
314
315 std::ostringstream os;
316 os << "floating point exception detected: " << error;
317 abnormal_program_termination(os.str().c_str());
318 }
319
320 /**
321 * \brief Interrupt signal handler
322 * \details The handler cancels the current task if any or exits the
323 * program.
324 */
325 void sigint_handler(int) {
326 if(Progress::current_progress_task() != nullptr) {
327 Progress::cancel();
328 } else {
329 exit(1);
330 }
331 }
332
333 /**
334 * \brief Catches uncaught C++ exceptions
335 */
336 GEO_NORETURN_DECL void terminate_handler() GEO_NORETURN;
337
338 void terminate_handler() {
339 abnormal_program_termination("function terminate() was called");
340 }
341
342 /**
343 * \brief Catches allocation errors
344 */
345 GEO_NORETURN_DECL void memory_exhausted_handler() GEO_NORETURN;
346
347 void memory_exhausted_handler() {
348 abnormal_program_termination("memory exhausted");
349 }
350 }
351
352 /****************************************************************************/
353
354 namespace GEO {
355
356 namespace Process {
357
358 249 bool os_init_threads() {
359 #ifdef GEO_USE_PTHREAD_MANAGER
360
1/2
✓ Branch 2 taken 249 times.
✗ Branch 3 not taken.
249 Logger::out("Process")
361 << "Using posix threads"
362 << std::endl;
363 249 set_thread_manager(new PThreadManager);
364 249 return true;
365 #else
366 return false;
367 #endif
368 }
369
370 void os_brute_force_kill() {
371 kill(getpid(), SIGKILL);
372 }
373
374 249 index_t os_number_of_cores() {
375 #if defined(GEO_OS_ANDROID)
376 int nb_cores = android_get_number_of_cores();
377 geo_assert(nb_cores > 0);
378 return index_t(nb_cores);
379 #elif defined(GEO_OS_EMSCRIPTEN)
380 # ifdef __EMSCRIPTEN_PTHREADS__
381 return index_t(emscripten_num_logical_cores());
382 # else
383 return 1;
384 # endif
385 #else
386 249 return index_t(sysconf(_SC_NPROCESSORS_ONLN));
387 #endif
388 }
389
390 size_t os_used_memory() {
391 #ifdef GEO_OS_APPLE
392 size_t result = 0;
393 struct rusage usage;
394 if(0 == getrusage(RUSAGE_SELF, &usage)) {
395 result = (size_t) usage.ru_maxrss;
396 }
397 return result;
398 #else
399 // The following method seems to be more
400 // reliable than getrusage() under Linux.
401 // It works for both Linux and Android.
402 size_t result = 0;
403 LineInput in("/proc/self/status");
404 while(!in.eof() && in.get_line()) {
405 in.get_fields();
406 if(in.field_matches(0,"VmSize:")) {
407 result = size_t(in.field_as_uint(1)) * size_t(1024);
408 break;
409 }
410 }
411 return result;
412
413 /*
414 const char* statm_path = "/proc/self/statm";
415 unsigned long size,resident,share,text,lib,data,dt;
416 FILE *F = fopen(statm_path,"r");
417 if(F == nullptr) {
418 perror(statm_path);
419 abort();
420 }
421 if(
422 fscanf(F,"%ld %ld %ld %ld %ld %ld %ld",
423 &size,&resident,&share,&text,&lib,&data,&dt
424 ) != 7
425 ) {
426 perror(statm_path);
427 abort();
428 }
429 fclose(f);
430 */
431 #endif
432 }
433
434 size_t os_max_used_memory() {
435 // The following method seems to be more
436 // reliable than getrusage() under Linux.
437 // It works for both Linux and Android.
438 size_t result = 0;
439 LineInput in("/proc/self/status");
440
441 // Some versions of Unix may not have the proc
442 // filesystem (or a different organization)
443 if(!in.OK()) {
444 return result;
445 }
446
447 while(!in.eof() && in.get_line()) {
448 in.get_fields();
449 if(in.field_matches(0,"VmPeak:")) {
450 result = size_t(in.field_as_uint(1)) * size_t(1024);
451 break;
452 }
453 }
454 return result;
455 }
456
457 249 bool os_enable_FPE(bool flag) {
458 #if defined(GEO_OS_APPLE) || defined(GEO_OS_EMSCRIPTEN) || defined(__MUSL__)
459 geo_argused(flag);
460 #else
461 int excepts = 0
462 // | FE_INEXACT // inexact result
463 | FE_DIVBYZERO // division by zero
464 | FE_UNDERFLOW // result not representable due to underflow
465 | FE_OVERFLOW // result not representable due to overflow
466 | FE_INVALID // invalid operation
467 ;
468
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249 times.
249 if(flag) {
469 feenableexcept(excepts);
470 } else {
471 249 fedisableexcept(excepts);
472 }
473 #endif
474 249 return true;
475 }
476
477 249 bool os_enable_cancel(bool flag) {
478
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 249 times.
249 if(flag) {
479 signal(SIGINT, sigint_handler);
480 } else {
481 249 signal(SIGINT, SIG_DFL);
482 }
483 249 return true;
484 }
485
486 /**
487 * \brief Installs signal handlers
488 * \details
489 * On Unix, this installs handlers for the standard signals.
490 * On Windows, this also installs all kind of exception handling
491 * routines that prevent the application from being blocked by a bad
492 * assertion, a runtime check or runtime error.
493 */
494 249 void os_install_signal_handlers() {
495 // Install signal handlers
496 249 signal(SIGSEGV, signal_handler);
497 249 signal(SIGILL, signal_handler);
498 249 signal(SIGBUS, signal_handler);
499
500 // Use sigaction for SIGFPE as it provides more details
501 // about the error.
502 struct sigaction sa, old_sa;
503 249 sa.sa_flags = SA_SIGINFO;
504 249 sa.sa_sigaction = fpe_signal_handler;
505 249 sigemptyset(&sa.sa_mask);
506 249 sigaction(SIGFPE, &sa, &old_sa);
507
508 // Install uncaught c++ exception handlers
509 249 std::set_terminate(terminate_handler);
510
511 // Install memory allocation handler
512 249 std::set_new_handler(memory_exhausted_handler);
513 249 }
514
515
516 /**
517 * \brief Gets the full path to the current executable.
518 */
519 std::string os_executable_filename() {
520 char buff[PATH_MAX];
521 #ifdef GEO_OS_APPLE
522 uint32_t len=PATH_MAX;
523 if (_NSGetExecutablePath(buff, &len) == 0) {
524 std::string filename(buff);
525 size_t pos = std::string::npos;
526 while( (pos=filename.find("/./")) != std::string::npos ) {
527 filename.replace(pos, 3, "/");
528 }
529 return filename;
530 }
531 return std::string("");
532 #else
533 ssize_t len = ::readlink("/proc/self/exe", buff, sizeof(buff)-1);
534 if (len != -1) {
535 buff[len] = '\0';
536 return std::string(buff);
537 }
538 return std::string("");
539 #endif
540 }
541
542 void os_print_stack_trace() {
543 #ifdef HAS_EXECINFO
544 constexpr int MAX_STACK_FRAMES=128;
545 static void *stack_traces[MAX_STACK_FRAMES];
546 int i, trace_size = 0;
547 char **messages = nullptr;
548 trace_size = backtrace(stack_traces, MAX_STACK_FRAMES);
549 messages = backtrace_symbols(stack_traces, trace_size);
550 for (i = 0; i < trace_size; ++i) {
551 fprintf(stderr,"Stacktrace: %s\n",messages[i]);
552 }
553 if (messages != nullptr) {
554 free(messages);
555 }
556 #else
557 fprintf(stderr,"Stacktrace not available on platform\n");
558 #endif
559 }
560 }
561
562 }
563
564 #else
565
566 // Declare a dummy variable so that
567 // MSVC does not complain that it
568 // generated an empty object file.
569 int dummy_process_unix_compiled = 1;
570
571 #endif
572