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