/** 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;
/** * 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(charconst 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';
} charconst * get() { return storage; }
/** 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(charconst 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};
void
cubeb_async_log(charconst * 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();
} elseif (!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");
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.24Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.