After working on this, I believe the real Fixes tag should have been
2c2b0a78b3739 ("ring-buffer: Add percentage of ring buffer full to wake up reader")
Which was added in 5.0, so this is the only release I'm backporting this to.
-- Steve
------------------ original commit in Linus's tree ------------------
From 42fb0a1e84ff525ebe560e2baf9451ab69127e2b Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" rostedt@goodmis.org Date: Thu, 20 Oct 2022 23:14:27 -0400 Subject: [PATCH] tracing/ring-buffer: Have polling block on watermark
Currently the way polling works on the ring buffer is broken. It will return immediately if there's any data in the ring buffer whereas a read will block until the watermark (defined by the tracefs buffer_percent file) is hit.
That is, a select() or poll() will return as if there's data available, but then the following read will block. This is broken for the way select()s and poll()s are supposed to work.
Have the polling on the ring buffer also block the same way reads and splice does on the ring buffer.
Link: https://lkml.kernel.org/r/20221020231427.41be3f26@gandalf.local.home
Cc: Linux Trace Kernel linux-trace-kernel@vger.kernel.org Cc: Masami Hiramatsu mhiramat@kernel.org Cc: Mathieu Desnoyers mathieu.desnoyers@efficios.com Cc: Primiano Tucci primiano@google.com Cc: stable@vger.kernel.org Fixes: 1e0d6714aceb7 ("ring-buffer: Do not wake up a splice waiter when page is not full") Signed-off-by: Steven Rostedt (Google) rostedt@goodmis.org
--- include/linux/ring_buffer.h | 2 - kernel/trace/ring_buffer.c | 54 ++++++++++++++++++++++++++++---------------- kernel/trace/trace.c | 2 - 3 files changed, 37 insertions(+), 21 deletions(-)
Index: linux-test.git/include/linux/ring_buffer.h =================================================================== --- linux-test.git.orig/include/linux/ring_buffer.h 2022-11-23 21:52:16.912916427 -0500 +++ linux-test.git/include/linux/ring_buffer.h 2022-11-23 21:52:39.400515404 -0500 @@ -99,7 +99,7 @@ __ring_buffer_alloc(unsigned long size,
int ring_buffer_wait(struct ring_buffer *buffer, int cpu, int full); __poll_t ring_buffer_poll_wait(struct ring_buffer *buffer, int cpu, - struct file *filp, poll_table *poll_table); + struct file *filp, poll_table *poll_table, int full);
#define RING_BUFFER_ALL_CPUS -1 Index: linux-test.git/kernel/trace/ring_buffer.c =================================================================== --- linux-test.git.orig/kernel/trace/ring_buffer.c 2022-11-23 21:52:16.912916427 -0500 +++ linux-test.git/kernel/trace/ring_buffer.c 2022-11-23 22:40:37.013711581 -0500 @@ -557,6 +557,21 @@ size_t ring_buffer_nr_dirty_pages(struct return cnt - read; }
+static __always_inline bool full_hit(struct ring_buffer *buffer, int cpu, int full) +{ + struct ring_buffer_per_cpu *cpu_buffer = buffer->buffers[cpu]; + size_t nr_pages; + size_t dirty; + + nr_pages = cpu_buffer->nr_pages; + if (!nr_pages || !full) + return true; + + dirty = ring_buffer_nr_dirty_pages(buffer, cpu); + + return (dirty * 100) > (full * nr_pages); +} + /* * rb_wake_up_waiters - wake up tasks waiting for ring buffer input * @@ -652,22 +667,20 @@ int ring_buffer_wait(struct ring_buffer !ring_buffer_empty_cpu(buffer, cpu)) { unsigned long flags; bool pagebusy; - size_t nr_pages; - size_t dirty; + bool done;
if (!full) break;
raw_spin_lock_irqsave(&cpu_buffer->reader_lock, flags); pagebusy = cpu_buffer->reader_page == cpu_buffer->commit_page; - nr_pages = cpu_buffer->nr_pages; - dirty = ring_buffer_nr_dirty_pages(buffer, cpu); + done = !pagebusy && full_hit(buffer, cpu, full); + if (!cpu_buffer->shortest_full || cpu_buffer->shortest_full > full) cpu_buffer->shortest_full = full; raw_spin_unlock_irqrestore(&cpu_buffer->reader_lock, flags); - if (!pagebusy && - (!nr_pages || (dirty * 100) > full * nr_pages)) + if (done) break; }
@@ -688,6 +701,7 @@ int ring_buffer_wait(struct ring_buffer * @cpu: the cpu buffer to wait on * @filp: the file descriptor * @poll_table: The poll descriptor + * @full: wait until the percentage of pages are available, if @cpu != RING_BUFFER_ALL_CPUS * * If @cpu == RING_BUFFER_ALL_CPUS then the task will wake up as soon * as data is added to any of the @buffer's cpu buffers. Otherwise @@ -697,14 +711,14 @@ int ring_buffer_wait(struct ring_buffer * zero otherwise. */ __poll_t ring_buffer_poll_wait(struct ring_buffer *buffer, int cpu, - struct file *filp, poll_table *poll_table) + struct file *filp, poll_table *poll_table, int full) { struct ring_buffer_per_cpu *cpu_buffer; struct rb_irq_work *work;
- if (cpu == RING_BUFFER_ALL_CPUS) + if (cpu == RING_BUFFER_ALL_CPUS) { work = &buffer->irq_work; - else { + } else { if (!cpumask_test_cpu(cpu, buffer->cpumask)) return -EINVAL;
@@ -712,8 +726,14 @@ __poll_t ring_buffer_poll_wait(struct ri work = &cpu_buffer->irq_work; }
- poll_wait(filp, &work->waiters, poll_table); - work->waiters_pending = true; + if (full) { + poll_wait(filp, &work->full_waiters, poll_table); + work->full_waiters_pending = true; + } else { + poll_wait(filp, &work->waiters, poll_table); + work->waiters_pending = true; + } + /* * There's a tight race between setting the waiters_pending and * checking if the ring buffer is empty. Once the waiters_pending bit @@ -729,6 +749,9 @@ __poll_t ring_buffer_poll_wait(struct ri */ smp_mb();
+ if (full) + return full_hit(buffer, cpu, full) ? EPOLLIN | EPOLLRDNORM : 0; + if ((cpu == RING_BUFFER_ALL_CPUS && !ring_buffer_empty(buffer)) || (cpu != RING_BUFFER_ALL_CPUS && !ring_buffer_empty_cpu(buffer, cpu))) return EPOLLIN | EPOLLRDNORM; @@ -2629,10 +2652,6 @@ static void rb_commit(struct ring_buffer static __always_inline void rb_wakeups(struct ring_buffer *buffer, struct ring_buffer_per_cpu *cpu_buffer) { - size_t nr_pages; - size_t dirty; - size_t full; - if (buffer->irq_work.waiters_pending) { buffer->irq_work.waiters_pending = false; /* irq_work_queue() supplies it's own memory barriers */ @@ -2656,10 +2675,7 @@ rb_wakeups(struct ring_buffer *buffer, s
cpu_buffer->last_pages_touch = local_read(&cpu_buffer->pages_touched);
- full = cpu_buffer->shortest_full; - nr_pages = cpu_buffer->nr_pages; - dirty = ring_buffer_nr_dirty_pages(buffer, cpu_buffer->cpu); - if (full && nr_pages && (dirty * 100) <= full * nr_pages) + if (!full_hit(buffer, cpu_buffer->cpu, cpu_buffer->shortest_full)) return;
cpu_buffer->irq_work.wakeup_full = true; Index: linux-test.git/kernel/trace/trace.c =================================================================== --- linux-test.git.orig/kernel/trace/trace.c 2022-11-23 21:52:16.912916427 -0500 +++ linux-test.git/kernel/trace/trace.c 2022-11-23 22:53:05.448304881 -0500 @@ -5993,7 +5993,7 @@ trace_poll(struct trace_iterator *iter, return EPOLLIN | EPOLLRDNORM; else return ring_buffer_poll_wait(iter->trace_buffer->buffer, iter->cpu_file, - filp, poll_table); + filp, poll_table, iter->tr->buffer_percent); }
static __poll_t