1 /*
2  * Copyright © 2016 Mozilla Foundation
3  *
4  * This program is made available under an ISC-style license.  See the
5  * accompanying file LICENSE for details.
6  */
7 #define NOMINMAX
8 
9 #include "cubeb_log.h"
10 #include "cubeb_ringbuffer.h"
11 #include <cstdarg>
12 #ifdef _WIN32
13 #include <windows.h>
14 #else
15 #include <time.h>
16 #endif
17 
18 cubeb_log_level g_cubeb_log_level;
19 cubeb_log_callback g_cubeb_log_callback;
20 
21 /** The maximum size of a log message, after having been formatted. */
22 const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
23 /** The maximum number of log messages that can be queued before dropping
24  * messages. */
25 const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
26 /** Number of milliseconds to wait before dequeuing log messages. */
27 #define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
28 
29 /**
30  * This wraps an inline buffer, that represents a log message, that must be
31  * null-terminated.
32  * This class should not use system calls or other potentially blocking code.
33  */
34 class cubeb_log_message {
35 public:
cubeb_log_message()36   cubeb_log_message() { *storage = '\0'; }
cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])37   cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
38   {
39     size_t length = strlen(str);
40     /* paranoia against malformed message */
41     assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
42     if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
43       return;
44     }
45     PodCopy(storage, str, length);
46     storage[length] = '\0';
47   }
get()48   char const * get() { return storage; }
49 
50 private:
51   char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
52 };
53 
54 /** Lock-free asynchronous logger, made so that logging from a
55  *  real-time audio callback does not block the audio thread. */
56 class cubeb_async_logger {
57 public:
58   /* This is thread-safe since C++11 */
get()59   static cubeb_async_logger & get()
60   {
61     static cubeb_async_logger instance;
62     return instance;
63   }
push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])64   void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
65   {
66     cubeb_log_message msg(str);
67     msg_queue.enqueue(msg);
68   }
run()69   void run()
70   {
71     std::thread([this]() {
72       while (true) {
73         cubeb_log_message msg;
74         while (msg_queue.dequeue(&msg, 1)) {
75           LOGV("%s", msg.get());
76         }
77 #ifdef _WIN32
78         Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
79 #else
80         timespec sleep_duration = sleep_for;
81         timespec remainder;
82         do {
83           if (nanosleep(&sleep_duration, &remainder) == 0 || errno != EINTR) {
84             break;
85           }
86           sleep_duration = remainder;
87         } while (remainder.tv_sec || remainder.tv_nsec);
88 #endif
89       }
90     }).detach();
91   }
92   // Tell the underlying queue the producer thread has changed, so it does not
93   // assert in debug. This should be called with the thread stopped.
reset_producer_thread()94   void reset_producer_thread() { msg_queue.reset_thread_ids(); }
95 
96 private:
97 #ifndef _WIN32
98   const struct timespec sleep_for = {
99       CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000,
100       (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000};
101 #endif
cubeb_async_logger()102   cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); }
103   /** This is quite a big data structure, but is only instantiated if the
104    * asynchronous logger is used.*/
105   lock_free_queue<cubeb_log_message> msg_queue;
106 };
107 
108 void
cubeb_async_log(char const * fmt,...)109 cubeb_async_log(char const * fmt, ...)
110 {
111   if (!g_cubeb_log_callback) {
112     return;
113   }
114   // This is going to copy a 256 bytes array around, which is fine.
115   // We don't want to allocate memory here, because this is made to
116   // be called from a real-time callback.
117   va_list args;
118   va_start(args, fmt);
119   char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
120   vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
121   cubeb_async_logger::get().push(msg);
122   va_end(args);
123 }
124 
125 void
cubeb_async_log_reset_threads()126 cubeb_async_log_reset_threads()
127 {
128   if (!g_cubeb_log_callback) {
129     return;
130   }
131   cubeb_async_logger::get().reset_producer_thread();
132 }
133