tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }