Source code

Revision control

Copy as Markdown

Other Tools

/*
* 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;
}