cubeb_log.cpp (7053B)
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 "cubeb_tracing.h" 12 #include <cstdarg> 13 #ifdef _WIN32 14 #include <windows.h> 15 #else 16 #include <time.h> 17 #endif 18 19 std::atomic<cubeb_log_level> g_cubeb_log_level; 20 std::atomic<cubeb_log_callback> g_cubeb_log_callback; 21 22 /** The maximum size of a log message, after having been formatted. */ 23 const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; 24 /** The maximum number of log messages that can be queued before dropping 25 * messages. */ 26 const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40; 27 /** Number of milliseconds to wait before dequeuing log messages. */ 28 const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10; 29 30 void 31 cubeb_noop_log_callback(char const * /* fmt */, ...) 32 { 33 } 34 35 /** 36 * This wraps an inline buffer, that represents a log message, that must be 37 * null-terminated. 38 * This class should not use system calls or other potentially blocking code. 39 */ 40 class cubeb_log_message { 41 public: 42 cubeb_log_message() { *storage = '\0'; } 43 cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) 44 { 45 size_t length = strlen(str); 46 /* paranoia against malformed message */ 47 assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE); 48 if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) { 49 return; 50 } 51 PodCopy(storage, str, length); 52 storage[length] = '\0'; 53 } 54 char const * get() { return storage; } 55 56 private: 57 char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]{}; 58 }; 59 60 /** Lock-free asynchronous logger, made so that logging from a 61 * real-time audio callback does not block the audio thread. */ 62 class cubeb_async_logger { 63 public: 64 /* This is thread-safe since C++11 */ 65 static cubeb_async_logger & get() 66 { 67 static cubeb_async_logger instance; 68 return instance; 69 } 70 void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) 71 { 72 cubeb_log_message msg(str); 73 auto * owned_queue = msg_queue.load(); 74 // Check if the queue is being deallocated. If not, grab ownership. If yes, 75 // return, the message won't be logged. 76 if (!owned_queue || 77 !msg_queue.compare_exchange_strong(owned_queue, nullptr)) { 78 return; 79 } 80 owned_queue->enqueue(msg); 81 // Return ownership. 82 msg_queue.store(owned_queue); 83 } 84 void run() 85 { 86 assert(logging_thread.get_id() == std::thread::id()); 87 logging_thread = std::thread([this]() { 88 CUBEB_REGISTER_THREAD("cubeb_log"); 89 while (!shutdown_thread) { 90 cubeb_log_message msg; 91 while (msg_queue_consumer.load()->dequeue(&msg, 1)) { 92 cubeb_log_internal_no_format(msg.get()); 93 } 94 std::this_thread::sleep_for( 95 std::chrono::milliseconds(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS)); 96 } 97 CUBEB_UNREGISTER_THREAD(); 98 }); 99 } 100 // Tell the underlying queue the producer thread has changed, so it does not 101 // assert in debug. This should be called with the thread stopped. 102 void reset_producer_thread() 103 { 104 if (msg_queue) { 105 msg_queue.load()->reset_thread_ids(); 106 } 107 } 108 void start() 109 { 110 auto * queue = 111 new lock_free_queue<cubeb_log_message>(CUBEB_LOG_MESSAGE_QUEUE_DEPTH); 112 msg_queue.store(queue); 113 msg_queue_consumer.store(queue); 114 shutdown_thread = false; 115 run(); 116 } 117 void stop() 118 { 119 assert(((g_cubeb_log_callback == cubeb_noop_log_callback) || 120 !g_cubeb_log_callback) && 121 "Only call stop after logging has been disabled."); 122 shutdown_thread = true; 123 if (logging_thread.get_id() != std::thread::id()) { 124 logging_thread.join(); 125 logging_thread = std::thread(); 126 auto * owned_queue = msg_queue.load(); 127 // Check if the queue is being used. If not, grab ownership. If yes, 128 // try again shortly. At this point, the logging thread has been joined, 129 // so nothing is going to dequeue. 130 // If there is a valid pointer here, then the real-time audio thread that 131 // logs won't attempt to write into the queue, and instead drop the 132 // message. 133 while (!msg_queue.compare_exchange_weak(owned_queue, nullptr)) { 134 } 135 delete owned_queue; 136 msg_queue_consumer.store(nullptr); 137 } 138 } 139 140 private: 141 cubeb_async_logger() {} 142 ~cubeb_async_logger() 143 { 144 assert(logging_thread.get_id() == std::thread::id() && 145 (g_cubeb_log_callback == cubeb_noop_log_callback || 146 !g_cubeb_log_callback)); 147 if (msg_queue.load()) { 148 delete msg_queue.load(); 149 } 150 } 151 /** This is quite a big data structure, but is only instantiated if the 152 * asynchronous logger is used. The two pointers point to the same object, but 153 * the first one can be temporarily null when a message is being enqueued. */ 154 std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue = {nullptr}; 155 156 std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue_consumer = { 157 nullptr}; 158 std::atomic<bool> shutdown_thread = {false}; 159 std::thread logging_thread; 160 }; 161 162 void 163 cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...) 164 { 165 va_list args; 166 va_start(args, fmt); 167 char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; 168 vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); 169 va_end(args); 170 g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg); 171 } 172 173 void 174 cubeb_log_internal_no_format(const char * msg) 175 { 176 g_cubeb_log_callback.load()(msg); 177 } 178 179 void 180 cubeb_async_log(char const * fmt, ...) 181 { 182 // This is going to copy a 256 bytes array around, which is fine. 183 // We don't want to allocate memory here, because this is made to 184 // be called from a real-time callback. 185 va_list args; 186 va_start(args, fmt); 187 char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; 188 vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); 189 cubeb_async_logger::get().push(msg); 190 va_end(args); 191 } 192 193 void 194 cubeb_async_log_reset_threads(void) 195 { 196 if (!g_cubeb_log_callback) { 197 return; 198 } 199 cubeb_async_logger::get().reset_producer_thread(); 200 } 201 202 void 203 cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback) 204 { 205 g_cubeb_log_level = log_level; 206 // Once a callback has a been set, `g_cubeb_log_callback` is never set back to 207 // nullptr, to prevent a TOCTOU race between checking the pointer 208 if (log_callback && log_level != CUBEB_LOG_DISABLED) { 209 g_cubeb_log_callback = log_callback; 210 if (log_level == CUBEB_LOG_VERBOSE) { 211 cubeb_async_logger::get().start(); 212 } 213 } else if (!log_callback || CUBEB_LOG_DISABLED) { 214 g_cubeb_log_callback = cubeb_noop_log_callback; 215 // This returns once the thread has joined. 216 // This is safe even if CUBEB_LOG_VERBOSE was not set; the thread will 217 // simply not be joinable. 218 cubeb_async_logger::get().stop(); 219 } else { 220 assert(false && "Incorrect parameters passed to cubeb_log_set"); 221 } 222 } 223 224 cubeb_log_level 225 cubeb_log_get_level() 226 { 227 return g_cubeb_log_level; 228 } 229 230 cubeb_log_callback 231 cubeb_log_get_callback() 232 { 233 if (g_cubeb_log_callback == cubeb_noop_log_callback) { 234 return nullptr; 235 } 236 return g_cubeb_log_callback; 237 }