diff options
Diffstat (limited to 'common/msg.c')
-rw-r--r-- | common/msg.c | 469 |
1 files changed, 342 insertions, 127 deletions
diff --git a/common/msg.c b/common/msg.c index e35b953f7d..81c7f654f3 100644 --- a/common/msg.c +++ b/common/msg.c @@ -30,12 +30,12 @@ #include "osdep/atomic.h" #include "common/common.h" #include "common/global.h" -#include "misc/ring.h" #include "misc/bstr.h" #include "options/options.h" #include "options/path.h" #include "osdep/terminal.h" #include "osdep/io.h" +#include "osdep/threads.h" #include "osdep/timer.h" #include "libmpv/client.h" @@ -43,14 +43,18 @@ #include "msg.h" #include "msg_control.h" +#define TERM_BUF 100 + struct mp_log_root { struct mpv_global *global; - // --- protected by mp_msg_lock + pthread_mutex_t lock; + pthread_mutex_t log_file_lock; + pthread_cond_t log_file_wakeup; + // --- protected by lock char **msg_levels; bool use_terminal; // make accesses to stderr/stdout bool module; bool show_time; - bool termosd; // use terminal control codes for status line int blank_lines; // number of lines usable by status int status_lines; // number of current status lines bool color; @@ -59,23 +63,30 @@ struct mp_log_root { bool force_stderr; struct mp_log_buffer **buffers; int num_buffers; - FILE *log_file; + struct mp_log_buffer *early_buffer; FILE *stats_file; - char *log_path; - char *stats_path; + bstr buffer; // --- must be accessed atomically /* This is incremented every time the msglevels must be reloaded. * (This is perhaps better than maintaining a globally accessible and * synchronized mp_log tree.) */ atomic_ulong reload_counter; - // --- protected by mp_msg_lock - bstr buffer; + // --- owner thread only (caller of mp_msg_init() etc.) + char *log_path; + char *stats_path; + pthread_t log_file_thread; + // --- owner thread only, but frozen while log_file_thread is running + FILE *log_file; + struct mp_log_buffer *log_file_buffer; + // --- protected by log_file_lock + bool log_file_thread_active; // also termination signal for the thread }; struct mp_log { struct mp_log_root *root; const char *prefix; const char *verbose_prefix; + int max_level; // minimum log level for this instance int level; // minimum log level for any outputs int terminal_level; // minimum log level for terminal output atomic_ulong reload_counter; @@ -84,15 +95,20 @@ struct mp_log { struct mp_log_buffer { struct mp_log_root *root; - struct mp_ring *ring; - int level; + pthread_mutex_t lock; + // --- protected by lock + struct mp_log_buffer_entry **entries; // ringbuffer + int capacity; // total space in entries[] + int entry0; // first (oldest) entry index + int num_entries; // number of valid entries after entry0 + uint64_t dropped; // number of skipped entries + bool silent; + // --- immutable void (*wakeup_cb)(void *ctx); void *wakeup_cb_ctx; + int level; }; -// Protects some (not all) state in mp_log_root -static pthread_mutex_t mp_msg_lock = PTHREAD_MUTEX_INITIALIZER; - static const struct mp_log null_log = {0}; struct mp_log *const mp_null_log = (struct mp_log *)&null_log; @@ -108,23 +124,41 @@ static bool match_mod(const char *name, const char *mod) static void update_loglevel(struct mp_log *log) { struct mp_log_root *root = log->root; - pthread_mutex_lock(&mp_msg_lock); + pthread_mutex_lock(&root->lock); log->level = MSGL_STATUS + root->verbose; // default log level if (root->really_quiet) - log->level -= 10; + log->level = -1; for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) { if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0])) log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]); } log->terminal_level = log->level; - for (int n = 0; n < log->root->num_buffers; n++) - log->level = MPMAX(log->level, log->root->buffers[n]->level); + for (int n = 0; n < log->root->num_buffers; n++) { + int buffer_level = log->root->buffers[n]->level; + if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) + buffer_level = MSGL_DEBUG; + if (buffer_level != MP_LOG_BUFFER_MSGL_TERM) + log->level = MPMAX(log->level, buffer_level); + } if (log->root->log_file) log->level = MPMAX(log->level, MSGL_DEBUG); if (log->root->stats_file) log->level = MPMAX(log->level, MSGL_STATS); + log->level = MPMIN(log->level, log->max_level); atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter)); - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&root->lock); +} + +// Set (numerically) the maximum level that should still be output for this log +// instances. E.g. lev=MSGL_WARN => show only warnings and errors. +void mp_msg_set_max_level(struct mp_log *log, int lev) +{ + if (!log->root) + return; + pthread_mutex_lock(&log->root->lock); + log->max_level = MPCLAMP(lev, -1, MSGL_MAX); + pthread_mutex_unlock(&log->root->lock); + update_loglevel(log); } // Get the current effective msg level. @@ -189,17 +223,29 @@ static void flush_status_line(struct mp_log_root *root) void mp_msg_flush_status_line(struct mp_log *log) { - pthread_mutex_lock(&mp_msg_lock); - if (log->root) + if (log->root) { + pthread_mutex_lock(&log->root->lock); flush_status_line(log->root); - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&log->root->lock); + } +} + +void mp_msg_set_term_title(struct mp_log *log, const char *title) +{ + if (log->root && title) { + // Lock because printf to terminal is not necessarily atomic. + pthread_mutex_lock(&log->root->lock); + fprintf(stderr, "\e]0;%s\007", title); + pthread_mutex_unlock(&log->root->lock); + } } bool mp_msg_has_status_line(struct mpv_global *global) { - pthread_mutex_lock(&mp_msg_lock); - bool r = global->log->root->status_lines > 0; - pthread_mutex_unlock(&mp_msg_lock); + struct mp_log_root *root = global->log->root; + pthread_mutex_lock(&root->lock); + bool r = root->status_lines > 0; + pthread_mutex_unlock(&root->lock); return r; } @@ -260,7 +306,7 @@ static void print_terminal_line(struct mp_log *log, int lev, set_msg_color(stream, lev); if (root->show_time) - fprintf(stream, "[%" PRId64 "] ", mp_time_us()); + fprintf(stream, "[%10.6f] ", (mp_time_us() - MP_START_TIME) / 1e6); const char *prefix = log->prefix; if ((lev >= MSGL_V) || root->verbose || root->module) @@ -281,18 +327,13 @@ static void print_terminal_line(struct mp_log *log, int lev, fflush(stream); } -static void write_log_file(struct mp_log *log, int lev, char *text) +static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer) { - struct mp_log_root *root = log->root; - - if (!root->log_file || lev > MPMAX(MSGL_DEBUG, log->terminal_level)) - return; - - fprintf(root->log_file, "[%8.3f][%c][%s] %s", - (mp_time_us() - MP_START_TIME) / 1e6, - mp_log_levels[lev][0], - log->verbose_prefix, text); - fflush(root->log_file); + assert(buffer->num_entries); + struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0]; + buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity; + buffer->num_entries -= 1; + return res; } static void write_msg_to_buffers(struct mp_log *log, int lev, char *text) @@ -300,33 +341,52 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text) struct mp_log_root *root = log->root; for (int n = 0; n < root->num_buffers; n++) { struct mp_log_buffer *buffer = root->buffers[n]; + bool wakeup = false; + pthread_mutex_lock(&buffer->lock); int buffer_level = buffer->level; if (buffer_level == MP_LOG_BUFFER_MSGL_TERM) buffer_level = log->terminal_level; + if (buffer_level == MP_LOG_BUFFER_MSGL_LOGFILE) + buffer_level = MPMAX(log->terminal_level, MSGL_DEBUG); if (lev <= buffer_level && lev != MSGL_STATUS) { - // Assuming a single writer (serialized by msg lock) - int avail = mp_ring_available(buffer->ring) / sizeof(void *); - if (avail < 1) - continue; - struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry); - if (avail > 1) { - *entry = (struct mp_log_buffer_entry) { - .prefix = talloc_strdup(entry, log->verbose_prefix), - .level = lev, - .text = talloc_strdup(entry, text), - }; - } else { - // write overflow message to signal that messages might be lost - *entry = (struct mp_log_buffer_entry) { - .prefix = "overflow", - .level = MSGL_FATAL, - .text = "log message buffer overflow\n", - }; + if (buffer->level == MP_LOG_BUFFER_MSGL_LOGFILE) { + // If the buffer is full, block until we can write again. + bool dead = false; + while (buffer->num_entries == buffer->capacity && !dead) { + // Temporary unlock is OK; buffer->level is immutable, and + // buffer can't go away because the global log lock is held. + pthread_mutex_unlock(&buffer->lock); + pthread_mutex_lock(&root->log_file_lock); + if (root->log_file_thread_active) { + pthread_cond_wait(&root->log_file_wakeup, + &root->log_file_lock); + } else { + dead = true; + } + pthread_mutex_unlock(&root->log_file_lock); + pthread_mutex_lock(&buffer->lock); + } + } + if (buffer->num_entries == buffer->capacity) { + struct mp_log_buffer_entry *skip = log_buffer_read(buffer); + talloc_free(skip); + buffer->dropped += 1; } - mp_ring_write(buffer->ring, (unsigned char *)&entry, sizeof(entry)); - if (buffer->wakeup_cb) - buffer->wakeup_cb(buffer->wakeup_cb_ctx); + struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry); + *entry = (struct mp_log_buffer_entry) { + .prefix = talloc_strdup(entry, log->verbose_prefix), + .level = lev, + .text = talloc_strdup(entry, text), + }; + int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity; + buffer->entries[pos] = entry; + buffer->num_entries += 1; + if (buffer->wakeup_cb && !buffer->silent) + wakeup = true; } + pthread_mutex_unlock(&buffer->lock); + if (wakeup) + buffer->wakeup_cb(buffer->wakeup_cb_ctx); } } @@ -342,10 +402,10 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) if (!mp_msg_test(log, lev)) return; // do not display - pthread_mutex_lock(&mp_msg_lock); - struct mp_log_root *root = log->root; + pthread_mutex_lock(&root->lock); + root->buffer.len = 0; if (log->partial[0]) @@ -361,7 +421,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) } else if (lev == MSGL_STATUS && !test_terminal_level(log, lev)) { /* discard */ } else { - if (lev == MSGL_STATUS && root->termosd) + if (lev == MSGL_STATUS) prepare_status_line(root, text); // Split away each line. Normally we require full lines; buffer partial @@ -374,7 +434,6 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) char saved = next[0]; next[0] = '\0'; print_terminal_line(log, lev, text, ""); - write_log_file(log, lev, text); write_msg_to_buffers(log, lev, text); next[0] = saved; text = next; @@ -382,7 +441,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) if (lev == MSGL_STATUS) { if (text[0]) - print_terminal_line(log, lev, text, root->termosd ? "\r" : "\n"); + print_terminal_line(log, lev, text, "\r"); } else if (text[0]) { int size = strlen(text) + 1; if (talloc_get_size(log->partial) < size) @@ -391,7 +450,7 @@ void mp_msg_va(struct mp_log *log, int lev, const char *format, va_list va) } } - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&root->lock); } static void destroy_log(void *ptr) @@ -421,6 +480,7 @@ struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, talloc_set_destructor(log, destroy_log); log->root = parent->root; log->partial = talloc_strdup(NULL, ""); + log->max_level = MSGL_MAX; if (name) { if (name[0] == '!') { name = &name[1]; @@ -456,112 +516,229 @@ void mp_msg_init(struct mpv_global *global) .reload_counter = ATOMIC_VAR_INIT(1), }; + pthread_mutex_init(&root->lock, NULL); + pthread_mutex_init(&root->log_file_lock, NULL); + pthread_cond_init(&root->log_file_wakeup, NULL); + struct mp_log dummy = { .root = root }; struct mp_log *log = mp_log_new(root, &dummy, ""); global->log = log; } -// If opt is different from *current_path, reopen *file and update *current_path. -// If there's an error, _append_ it to err_buf. -// *current_path and *file are, rather trickily, only accessible under the -// mp_msg_lock. -static void reopen_file(char *opt, char **current_path, FILE **file, - const char *type, struct mpv_global *global) +static void *log_file_thread(void *p) +{ + struct mp_log_root *root = p; + + mpthread_set_name("log-file"); + + pthread_mutex_lock(&root->log_file_lock); + + while (root->log_file_thread_active) { + struct mp_log_buffer_entry *e = + mp_msg_log_buffer_read(root->log_file_buffer); + if (e) { + pthread_mutex_unlock(&root->log_file_lock); + fprintf(root->log_file, "[%8.3f][%c][%s] %s", + (mp_time_us() - MP_START_TIME) / 1e6, + mp_log_levels[e->level][0], e->prefix, e->text); + fflush(root->log_file); + pthread_mutex_lock(&root->log_file_lock); + talloc_free(e); + // Multiple threads might be blocked if the log buffer was full. + pthread_cond_broadcast(&root->log_file_wakeup); + } else { + pthread_cond_wait(&root->log_file_wakeup, &root->log_file_lock); + } + } + + pthread_mutex_unlock(&root->log_file_lock); + + return NULL; +} + +static void wakeup_log_file(void *p) +{ + struct mp_log_root *root = p; + + pthread_mutex_lock(&root->log_file_lock); + pthread_cond_broadcast(&root->log_file_wakeup); + pthread_mutex_unlock(&root->log_file_lock); +} + +// Only to be called from the main thread. +static void terminate_log_file_thread(struct mp_log_root *root) +{ + bool wait_terminate = false; + + pthread_mutex_lock(&root->log_file_lock); + if (root->log_file_thread_active) { + root->log_file_thread_active = false; + pthread_cond_broadcast(&root->log_file_wakeup); + wait_terminate = true; + } + pthread_mutex_unlock(&root->log_file_lock); + + if (wait_terminate) + pthread_join(root->log_file_thread, NULL); + + mp_msg_log_buffer_destroy(root->log_file_buffer); + root->log_file_buffer = NULL; + + if (root->log_file) + fclose(root->log_file); + root->log_file = NULL; +} + +// If opt is different from *current_path, update *current_path and return true. +// No lock must be held; passed values must be accessible without. +static bool check_new_path(struct mpv_global *global, char *opt, + char **current_path) { void *tmp = talloc_new(NULL); - bool fail = false; + bool res = false; char *new_path = mp_get_user_path(tmp, global, opt); if (!new_path) new_path = ""; - pthread_mutex_lock(&mp_msg_lock); // for *current_path/*file - char *old_path = *current_path ? *current_path : ""; if (strcmp(old_path, new_path) != 0) { - if (*file) - fclose(*file); - *file = NULL; talloc_free(*current_path); - *current_path = talloc_strdup(NULL, new_path); - if (new_path[0]) { - *file = fopen(new_path, "wb"); - fail = !*file; - } + *current_path = NULL; + if (new_path && new_path[0]) + *current_path = talloc_strdup(NULL, new_path); + res = true; } - pthread_mutex_unlock(&mp_msg_lock); - - if (fail) - mp_err(global->log, "Failed to open %s file '%s'\n", type, new_path); - talloc_free(tmp); + + return res; } void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts) { struct mp_log_root *root = global->log->root; - pthread_mutex_lock(&mp_msg_lock); + pthread_mutex_lock(&root->lock); root->verbose = opts->verbose; root->really_quiet = opts->msg_really_quiet; root->module = opts->msg_module; root->use_terminal = opts->use_terminal; root->show_time = opts->msg_time; - if (root->use_terminal) { + if (root->use_terminal) root->color = opts->msg_color && isatty(STDOUT_FILENO); - root->termosd = isatty(STDERR_FILENO); - } m_option_type_msglevels.free(&root->msg_levels); m_option_type_msglevels.copy(NULL, &root->msg_levels, &opts->msg_levels); atomic_fetch_add(&root->reload_counter, 1); - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&root->lock); + + if (check_new_path(global, opts->log_file, &root->log_path)) { + terminate_log_file_thread(root); + if (root->log_path) { + root->log_file = fopen(root->log_path, "wb"); + if (root->log_file) { + root->log_file_buffer = + mp_msg_log_buffer_new(global, 100, MP_LOG_BUFFER_MSGL_LOGFILE, + wakeup_log_file, root); + root->log_file_thread_active = true; + if (pthread_create(&root->log_file_thread, NULL, log_file_thread, + root)) + { + root->log_file_thread_active = false; + terminate_log_file_thread(root); + } + } else { + mp_err(global->log, "Failed to open log file '%s'\n", + root->log_path); + } + } + } - reopen_file(opts->log_file, &root->log_path, &root->log_file, - "log", global); + if (check_new_path(global, opts->dump_stats, &root->stats_path)) { + bool open_error = false; - reopen_file(opts->dump_stats, &root->stats_path, &root->stats_file, - "stats", global); + pthread_mutex_lock(&root->lock); + if (root->stats_file) + fclose(root->stats_file); + root->stats_file = NULL; + if (root->stats_path) { + root->stats_file = fopen(root->stats_path, "wb"); + open_error = !root->stats_file; + } + pthread_mutex_unlock(&root->lock); + + if (open_error) { + mp_err(global->log, "Failed to open stats file '%s'\n", + root->stats_path); + } + } } void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr) { struct mp_log_root *root = global->log->root; - pthread_mutex_lock(&mp_msg_lock); + pthread_mutex_lock(&root->lock); root->force_stderr = force_stderr; - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&root->lock); } +// Only to be called from the main thread. bool mp_msg_has_log_file(struct mpv_global *global) { struct mp_log_root *root = global->log->root; - pthread_mutex_lock(&mp_msg_lock); - bool res = !!root->log_file; - pthread_mutex_unlock(&mp_msg_lock); - - return res; + return !!root->log_file; } void mp_msg_uninit(struct mpv_global *global) { struct mp_log_root *root = global->log->root; + terminate_log_file_thread(root); + mp_msg_log_buffer_destroy(root->early_buffer); + assert(root->num_buffers == 0); if (root->stats_file) fclose(root->stats_file); talloc_free(root->stats_path); - if (root->log_file) - fclose(root->log_file); talloc_free(root->log_path); m_option_type_msglevels.free(&root->msg_levels); + pthread_mutex_destroy(&root->lock); + pthread_mutex_destroy(&root->log_file_lock); + pthread_cond_destroy(&root->log_file_wakeup); talloc_free(root); global->log = NULL; } +void mp_msg_set_early_logging(struct mpv_global *global, bool enable) +{ + struct mp_log_root *root = global->log->root; + pthread_mutex_lock(&root->lock); + + if (enable != !!root->early_buffer) { + if (enable) { + pthread_mutex_unlock(&root->lock); + struct mp_log_buffer *buf = + mp_msg_log_buffer_new(global, TERM_BUF, MP_LOG_BUFFER_MSGL_TERM, + NULL, NULL); + pthread_mutex_lock(&root->lock); + assert(!root->early_buffer); // no concurrent calls to this function + root->early_buffer = buf; + } else { + struct mp_log_buffer *buf = root->early_buffer; + root->early_buffer = NULL; + pthread_mutex_unlock(&root->lock); + mp_msg_log_buffer_destroy(buf); + return; + } + } + + pthread_mutex_unlock(&root->lock); +} + struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, int size, int level, void (*wakeup_cb)(void *ctx), @@ -569,39 +746,63 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global, { struct mp_log_root *root = global->log->root; -#if !HAVE_ATOMICS - return NULL; -#endif + pthread_mutex_lock(&root->lock); + + if (level == MP_LOG_BUFFER_MSGL_TERM) { + size = TERM_BUF; + + // The first thing which creates a terminal-level log buffer gets the + // early log buffer, if it exists. This is supposed to enable a script + // to grab log messages from before it was initialized. It's OK that + // this works only for 1 script and only once. + if (root->early_buffer) { + struct mp_log_buffer *buffer = root->early_buffer; + root->early_buffer = NULL; + buffer->wakeup_cb = wakeup_cb; + buffer->wakeup_cb_ctx = wakeup_cb_ctx; + pthread_mutex_unlock(&root->lock); + return buffer; + } + } - pthread_mutex_lock(&mp_msg_lock); + assert(size > 0); struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer); *buffer = (struct mp_log_buffer) { .root = root, .level = level, - .ring = mp_ring_new(buffer, sizeof(void *) * size), + .entries = talloc_array(buffer, struct mp_log_buffer_entry *, size), + .capacity = size, .wakeup_cb = wakeup_cb, .wakeup_cb_ctx = wakeup_cb_ctx, }; - if (!buffer->ring) - abort(); + + pthread_mutex_init(&buffer->lock, NULL); MP_TARRAY_APPEND(root, root->buffers, root->num_buffers, buffer); atomic_fetch_add(&root->reload_counter, 1); - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&root->lock); return buffer; } +void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent) +{ + pthread_mutex_lock(&buffer->lock); + buffer->silent = silent; + pthread_mutex_unlock(&buffer->lock); +} + void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer) { if (!buffer) return; - pthread_mutex_lock(&mp_msg_lock); - struct mp_log_root *root = buffer->root; + + pthread_mutex_lock(&root->lock); + for (int n = 0; n < root->num_buffers; n++) { if (root->buffers[n] == buffer) { MP_TARRAY_REMOVE_AT(root->buffers, root->num_buffers, n); @@ -613,29 +814,43 @@ void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer) found: - while (1) { - struct mp_log_buffer_entry *e = mp_msg_log_buffer_read(buffer); - if (!e) - break; - talloc_free(e); - } + while (buffer->num_entries) + talloc_free(log_buffer_read(buffer)); + + pthread_mutex_destroy(&buffer->lock); talloc_free(buffer); atomic_fetch_add(&root->reload_counter, 1); - pthread_mutex_unlock(&mp_msg_lock); + pthread_mutex_unlock(&root->lock); } // Return a queued message, or if the buffer is empty, NULL. // Thread-safety: one buffer can be read by a single thread only. struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer) { - void *ptr = NULL; - int read = mp_ring_read(buffer->ring, (unsigned char *)&ptr, sizeof(ptr)); - if (read == 0) - return NULL; - if (read != sizeof(ptr)) - abort(); - return ptr; + struct mp_log_buffer_entry *res = NULL; + + pthread_mutex_lock(&buffer->lock); + + if (!buffer->silent && buffer->num_entries) { + if (buffer->dropped) { + res = talloc_ptrtype(NULL, res); + *res = (struct mp_log_buffer_entry) { + .prefix = "overflow", + .level = MSGL_FATAL, + .text = talloc_asprintf(res, + "log message buffer overflow: %"PRId64" messages skipped\n", + buffer->dropped), + }; + buffer->dropped = 0; + } else { + res = log_buffer_read(buffer); + } + } + + pthread_mutex_unlock(&buffer->lock); + + return res; } // Thread-safety: fully thread-safe, but keep in mind that the lifetime of |