summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/src/cubeb_log.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libcubeb/src/cubeb_log.cpp')
-rw-r--r--media/libcubeb/src/cubeb_log.cpp233
1 files changed, 233 insertions, 0 deletions
diff --git a/media/libcubeb/src/cubeb_log.cpp b/media/libcubeb/src/cubeb_log.cpp
new file mode 100644
index 0000000000..73b568f5cd
--- /dev/null
+++ b/media/libcubeb/src/cubeb_log.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright © 2016 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+#define NOMINMAX
+
+#include "cubeb_log.h"
+#include "cubeb_ringbuffer.h"
+#include "cubeb_tracing.h"
+#include <cstdarg>
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <time.h>
+#endif
+
+std::atomic<cubeb_log_level> g_cubeb_log_level;
+std::atomic<cubeb_log_callback> g_cubeb_log_callback;
+
+/** The maximum size of a log message, after having been formatted. */
+const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
+/** The maximum number of log messages that can be queued before dropping
+ * messages. */
+const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
+/** Number of milliseconds to wait before dequeuing log messages. */
+const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10;
+
+void
+cubeb_noop_log_callback(char const * /* fmt */, ...)
+{
+}
+
+/**
+ * This wraps an inline buffer, that represents a log message, that must be
+ * null-terminated.
+ * This class should not use system calls or other potentially blocking code.
+ */
+class cubeb_log_message {
+public:
+ cubeb_log_message() { *storage = '\0'; }
+ cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ size_t length = strlen(str);
+ /* paranoia against malformed message */
+ assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
+ if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
+ return;
+ }
+ PodCopy(storage, str, length);
+ storage[length] = '\0';
+ }
+ char const * get() { return storage; }
+
+private:
+ char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]{};
+};
+
+/** Lock-free asynchronous logger, made so that logging from a
+ * real-time audio callback does not block the audio thread. */
+class cubeb_async_logger {
+public:
+ /* This is thread-safe since C++11 */
+ static cubeb_async_logger & get()
+ {
+ static cubeb_async_logger instance;
+ return instance;
+ }
+ void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
+ {
+ cubeb_log_message msg(str);
+ auto * owned_queue = msg_queue.load();
+ // Check if the queue is being deallocated. If not, grab ownership. If yes,
+ // return, the message won't be logged.
+ if (!owned_queue ||
+ !msg_queue.compare_exchange_strong(owned_queue, nullptr)) {
+ return;
+ }
+ owned_queue->enqueue(msg);
+ // Return ownership.
+ msg_queue.store(owned_queue);
+ }
+ void run()
+ {
+ assert(logging_thread.get_id() == std::thread::id());
+ logging_thread = std::thread([this]() {
+ CUBEB_REGISTER_THREAD("cubeb_log");
+ while (!shutdown_thread) {
+ cubeb_log_message msg;
+ while (msg_queue_consumer.load()->dequeue(&msg, 1)) {
+ cubeb_log_internal_no_format(msg.get());
+ }
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS));
+ }
+ CUBEB_UNREGISTER_THREAD();
+ });
+ }
+ // Tell the underlying queue the producer thread has changed, so it does not
+ // assert in debug. This should be called with the thread stopped.
+ void reset_producer_thread()
+ {
+ if (msg_queue) {
+ msg_queue.load()->reset_thread_ids();
+ }
+ }
+ void start()
+ {
+ auto * queue =
+ new lock_free_queue<cubeb_log_message>(CUBEB_LOG_MESSAGE_QUEUE_DEPTH);
+ msg_queue.store(queue);
+ msg_queue_consumer.store(queue);
+ shutdown_thread = false;
+ run();
+ }
+ void stop()
+ {
+ assert(((g_cubeb_log_callback == cubeb_noop_log_callback) ||
+ !g_cubeb_log_callback) &&
+ "Only call stop after logging has been disabled.");
+ shutdown_thread = true;
+ if (logging_thread.get_id() != std::thread::id()) {
+ logging_thread.join();
+ logging_thread = std::thread();
+ auto * owned_queue = msg_queue.load();
+ // Check if the queue is being used. If not, grab ownership. If yes,
+ // try again shortly. At this point, the logging thread has been joined,
+ // so nothing is going to dequeue.
+ // If there is a valid pointer here, then the real-time audio thread that
+ // logs won't attempt to write into the queue, and instead drop the
+ // message.
+ while (!msg_queue.compare_exchange_weak(owned_queue, nullptr)) {
+ }
+ delete owned_queue;
+ msg_queue_consumer.store(nullptr);
+ }
+ }
+
+private:
+ cubeb_async_logger() {}
+ ~cubeb_async_logger()
+ {
+ assert(logging_thread.get_id() == std::thread::id() &&
+ (g_cubeb_log_callback == cubeb_noop_log_callback ||
+ !g_cubeb_log_callback));
+ if (msg_queue.load()) {
+ delete msg_queue.load();
+ }
+ }
+ /** This is quite a big data structure, but is only instantiated if the
+ * asynchronous logger is used. The two pointers point to the same object, but
+ * the first one can be temporarily null when a message is being enqueued. */
+ std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue = {nullptr};
+
+ std::atomic<lock_free_queue<cubeb_log_message> *> msg_queue_consumer = {
+ nullptr};
+ std::atomic<bool> shutdown_thread = {false};
+ std::thread logging_thread;
+};
+
+void
+cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ va_end(args);
+ g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg);
+}
+
+void
+cubeb_log_internal_no_format(const char * msg)
+{
+ g_cubeb_log_callback.load()(msg);
+}
+
+void
+cubeb_async_log(char const * fmt, ...)
+{
+ // This is going to copy a 256 bytes array around, which is fine.
+ // We don't want to allocate memory here, because this is made to
+ // be called from a real-time callback.
+ va_list args;
+ va_start(args, fmt);
+ char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
+ vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
+ cubeb_async_logger::get().push(msg);
+ va_end(args);
+}
+
+void
+cubeb_async_log_reset_threads(void)
+{
+ if (!g_cubeb_log_callback) {
+ return;
+ }
+ cubeb_async_logger::get().reset_producer_thread();
+}
+
+void
+cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback)
+{
+ g_cubeb_log_level = log_level;
+ // Once a callback has a been set, `g_cubeb_log_callback` is never set back to
+ // nullptr, to prevent a TOCTOU race between checking the pointer
+ if (log_callback && log_level != CUBEB_LOG_DISABLED) {
+ g_cubeb_log_callback = log_callback;
+ cubeb_async_logger::get().start();
+ } else if (!log_callback || CUBEB_LOG_DISABLED) {
+ g_cubeb_log_callback = cubeb_noop_log_callback;
+ // This returns once the thread has joined.
+ cubeb_async_logger::get().stop();
+ } else {
+ assert(false && "Incorrect parameters passed to cubeb_log_set");
+ }
+}
+
+cubeb_log_level
+cubeb_log_get_level()
+{
+ return g_cubeb_log_level;
+}
+
+cubeb_log_callback
+cubeb_log_get_callback()
+{
+ if (g_cubeb_log_callback == cubeb_noop_log_callback) {
+ return nullptr;
+ }
+ return g_cubeb_log_callback;
+}