Optimise io_uring completion waiting
Subsystems (3)
v2 cover Optimise io_uring completion waiting
From: Pavel Begunkov <[email protected]>
There could be a lot of overhead within generic wait_event_*() used for waiting for large number of completions. The patchset removes much of it by using custom wait event (wait_threshold).
Synthetic test showed ~40% performance boost. (see patch 2)
v2: rebase
Pavel Begunkov (2):
sched/wait: Add wait_threshold
io_uring: Optimise cq waiting with wait_threshold
fs/io_uring.c | 35 ++++++++++++------ include/linux/wait_threshold.h | 67 ++++++++++++++++++++++++++++++++++ kernel/sched/Makefile | 2 +- kernel/sched/wait_threshold.c | 26 +++++++++++++ 4 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 include/linux/wait_threshold.h create mode 100644 kernel/sched/wait_threshold.c
Signature · 1 line
2.23.0
PATCH v2 1/2 sched/wait: Add wait_threshold
From: Pavel Begunkov <[email protected]>
Add wait_threshold -- a custom wait_event derivative, that waits until a value is equal to or greater than the specified threshold.
v2: rebase
1. use full condition instead of event number generator
2. add WQ_THRESHOLD_WAKE_ALWAYS constant
--- include/linux/wait_threshold.h | 67 ++++++++++++++++++++++++++++++++++ kernel/sched/Makefile | 2 +- kernel/sched/wait_threshold.c | 26 +++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 include/linux/wait_threshold.h create mode 100644 kernel/sched/wait_threshold.c
include/linux/wait_threshold.h new file +67 -0
@@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_WAIT_THRESHOLD_H +#define _LINUX_WAIT_THRESHOLD_H + +#include <linux/wait.h> + +#define WQ_THRESHOLD_WAKE_ALWAYS (~0ui) + +struct wait_threshold_queue_entry { +struct wait_queue_entry wq_entry; +unsigned int threshold; +}; + + +void init_wait_threshold_entry (struct wait_threshold_queue_entry *wtq_entry, +unsigned int threshold); + +static inline void wake_up_threshold (struct wait_queue_head *wq_head, +unsigned int val) +{ +void *arg = (void *)(unsigned long )val; + +__wake_up (wq_head,TASK_NORMAL ,1 , arg); +} + +#define ___wait_threshold_event (q, thresh, condition, state, \ + exclusive, ret, cmd) \ +({ \ + __label__ __out; \ + struct wait_queue_head *__wq_head = &q; \ + struct wait_threshold_queue_entry __wtq_entry; \ + struct wait_queue_entry *__wq_entry = &__wtq_entry.wq_entry; \ + long __ret = ret;/* explicit shadow */ \ + \ +init_wait_threshold_entry (&__wtq_entry, thresh); \ +for (;;) { \ +long __int =prepare_to_wait_event (__wq_head, \ + __wq_entry, \ + state); \ +if (condition) \ +break ; \ + \ +if (___wait_is_interruptible (state) && __int) { \ + __ret = __int; \ + goto __out; \ + } \ + \ + cmd; \ + } \ +finish_wait (__wq_head, __wq_entry); \ +__out: __ret; \ +}) + +#define __wait_threshold_interruptible (q, thresh, condition) \ + ___wait_threshold_event(q, thresh, condition, TASK_INTERRUPTIBLE, 0, 0,\ + schedule()) + +#define wait_threshold_interruptible (q, threshold, condition) \ +({ \ + int __ret = 0; \ + might_sleep(); \ + if (!(condition)) \ + __ret = __wait_threshold_interruptible(q, \ + threshold, condition); \ + __ret; \ +}) +#endif /* _LINUX_WAIT_THRESHOLD_H */
kernel/sched/Makefile +1 -1
@@ -18,7 +18,7 @@ endif obj-y += core.o loadavg.o clock.o cputime.o obj-y += idle.o fair.o rt.o deadline.o -obj-y += wait.o wait_bit.o swait.o completion.o +obj-y += wait.o wait_bit.o wait_threshold.o swait.o completion.o obj-$(CONFIG_SMP) += cpupri.o cpudeadline.o topology.o stop_task.o pelt.o obj-$(CONFIG_SCHED_AUTOGROUP) += autogroup.o
kernel/sched/wait_threshold.c new file +26 -0
@@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "sched.h" +#include <linux/wait_threshold.h> + +static int wake_threshold_function (struct wait_queue_entry *wq_entry, +unsigned int mode,int sync,void *arg) +{ +unsigned int val = (unsigned int )(unsigned long )arg; +struct wait_threshold_queue_entry *wtq_entry = + container_of(wq_entry, struct wait_threshold_queue_entry, + wq_entry); + +if (val < wtq_entry->threshold) +return 0 ; + +return default_wake_function (wq_entry, mode, sync, arg); +} + +void init_wait_threshold_entry (struct wait_threshold_queue_entry *wtq_entry, +unsigned int threshold) +{ +init_wait_entry (&wtq_entry->wq_entry,0 ); + wtq_entry->wq_entry.func = wake_threshold_function; + wtq_entry->threshold = threshold; +} +EXPORT_SYMBOL (init_wait_threshold_entry);
Signature · 1 line
2.23.0
Peter Zijlstra
maintainerRe: [PATCH v2 1/2] sched/wait: Add wait_threshold
Quoted 4 lines · Pavel Begunkov (Silence)
From: Pavel Begunkov <[email protected]>
Add
wait_threshold-- a customwait_eventderivative, that waits until a value is equal to or greater than the specified threshold.
This is quite insufficient justification for this monster... what exact semantics do you want?
Why can't you do this exact same with a slightly more complicated @cond ?
Re: [PATCH v2 1/2] sched/wait: Add wait_threshold
Just in case duplicating a mail from the cover-letter thread:
It could be done with @cond indeed, that's how it works for now. However, this addresses performance issues only.
The problem with wait_event_*() is that, if we have a counter and are trying to wake up tasks after each increment, it would schedule each waiting task O(threshold) times just for it to spuriously check @cond and go back to sleep. All that overhead (memory barriers, registers save/load, accounting, etc) turned out to be enough for some workloads to slow down the system.
With this specialisation it still traverses a wait list and makes indirect calls to the checker callback, but the list supposedly is fairly small, so performance there shouldn't be a problem, at least for now.
Regarding semantics; It should wake a task when a value passed to wake_up_threshold() is greater or equal then a task's threshold, that is specified individually for each task in wait_threshold_*().
In pseudo code:
``` def wake_up_threshold(n, wait_queue): for waiter in wait_queue: waiter.wake_up_if(n >= waiter.threshold); ```
Any thoughts how to do it better? Ideas are very welcome.
BTW, this monster is mostly a copy-paste from wait_event_*(), wait_bit_*(). We could try to extract some common parts from these three, but that's another topic.
Quoted 12 lines · Peter Zijlstra
Quoted 4 lines · Pavel Begunkov (Silence)
From: Pavel Begunkov <[email protected]>
Add
wait_threshold-- a customwait_eventderivative, that waits until a value is equal to or greater than the specified threshold.This is quite insufficient justification for this monster... what exact semantics do you want?
Why can't you do this exact same with a slightly more complicated @cond ?
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Peter Zijlstra
maintainerRe: [PATCH v2 1/2] sched/wait: Add wait_threshold
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in e-mail?
Quoted 1 line · Pavel Begunkov
Just in case duplicating a mail from the cover-letter thread:
Just because every patch should have a self contained and coherent Changelog.
Quoted 26 lines
It could be done with @cond indeed, that's how it works for now. However, this addresses performance issues only.
The problem with wait_event_*() is that, if we have a counter and are trying to wake up tasks after each increment, it would schedule each waiting task O(threshold) times just for it to spuriously check @cond and go back to sleep. All that overhead (memory barriers, registers save/load, accounting, etc) turned out to be enough for some workloads to slow down the system.
With this specialisation it still traverses a wait list and makes indirect calls to the checker callback, but the list supposedly is fairly small, so performance there shouldn't be a problem, at least for now.
Regarding semantics; It should wake a task when a value passed to
wake_up_threshold()is greater or equal then a task's threshold, that is specified individually for each task in wait_threshold_*().In pseudo code:
``` def wake_up_threshold(n, wait_queue): for waiter in wait_queue: waiter.wake_up_if(n >= waiter.threshold); ```
Quoted 5 lines
Any thoughts how to do it better? Ideas are very welcome.
BTW, this monster is mostly a copy-paste from wait_event_*(), wait_bit_*(). We could try to extract some common parts from these three, but that's another topic.
I don't think that is another topic at all. It is a quality of implementation issue. We already have too many copies of all that (3).
So basically you want to fudge the wake function to do the/a @cond test, not unlike what wait_bit already does, but differenly.
I'm really rather annoyed with C for not having proper lambda functions; that would make all this so much easier. Anyway, let me have a poke at this in the morning, it's late already.
Also, is anything actually using wait_queue_entry::private ? I'm not finding any in a hurry.
Peter Zijlstra
maintainerRe: [PATCH v2 1/2] sched/wait: Add wait_threshold
Quoted 2 lines · Peter Zijlstra
Also, is anything actually using
wait_queue_entry::private? I'm not finding any in a hurry.
That is; outside of default_wake_function(). I don't see a sensible reason for that field to be 'void *' nor for the name 'private'.
But _that_ is something we can fix later.
Re: [PATCH v2 1/2] sched/wait: Add wait_threshold
In reply to Peter ZijlstraQuoted 11 lines · Peter Zijlstra
A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing in e-mail?Quoted 1 line · Pavel Begunkov
Just in case duplicating a mail from the cover-letter thread:
Just because every patch should have a self contained and coherent Changelog.
Well, I will expand the patch description, if we agree on the implementation (at least conceptually).
Quoted 7 lines
Quoted 4 lines
BTW, this monster is mostly a copy-paste from wait_event_*(), wait_bit_*(). We could try to extract some common parts from these three, but that's another topic.
I don't think that is another topic at all. It is a quality of implementation issue. We already have too many copies of all that (3).
For example, ___wait_event() is copied in ___wait_var_event(). Instead it could accept a wait entry generator or just accept entry from above and be reused in both cases. I've had such a patch, but want to think what else could be done.
e.g.
``` #define generic_wait_event(ENTRY_GEN, ...) ENTRY_GEN(wq_entry_name); do_wait_event(wq_entry_name); #define WBQ_ENTRY_GEN(name) struct wait_bit_queue_entry tmp = WBQ_INITIALIZER; struct wait_queue_entry name = &tmp->wq_entry; ```
Quoted 4 lines
So basically you want to fudge the wake function to do the/a @cond test, not unlike what
wait_bitalready does, but differenly.
Yes
Quoted 8 lines
I'm really rather annoyed with C for not having proper lambda functions; that would make all this so much easier. Anyway, let me have a poke at this in the morning, it's late already.
Also, is anything actually using
wait_queue_entry::private? I'm not finding any in a hurry.
Signature · 2 lines
Yours sincerely, Pavel Begunkov
PATCH v2 2/2 io_uring: Optimise cq waiting with wait_threshold
In reply to Pavel Begunkov (Silence)From: Pavel Begunkov <[email protected]>
While waiting for completion events in io_cqring_wait(), the process will be waken up inside wait_threshold_interruptible() on any request completion, check num of events in completion queue and potentially go to sleep again.
Apparently, there could be a lot of such spurious wakeups with lots of overhead. It especially manifests itself, when min_events is large, and completions are arriving one by one or in small batches (that usually is true).
E.g. if device completes requests one by one and io_uring_enter is waiting for 100 events, then there will be ~99 spurious wakeups.
Use new wait_threshold_*() instead, which won't wake it up until necessary number of events is collected.
Performance test:
The first thread generates requests (QD=512) one by one, so they will be completed in the similar pattern. The second thread waiting for 128 events to complete.
Tested with null_blk with 5us delay
and 3.8GHz Intel CPU.
throughput before: 270 KIOPS throughput after: 370 KIOPS
~40% throughput boost, exaggerated, but makes a point.
v2: wake always in io_timeout_fn() with WQ_THRESHOLD_WAKE_ALWAYS
--- fs/io_uring.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-)
fs/io_uring.c +24 -11
@@ -70,6 +70,7 @@#include <linux/nospec.h> #include <linux/sizes.h> #include <linux/hugetlb.h> +#include <linux/wait_threshold.h> #include <uapi/linux/io_uring.h>
@@ -414,6 +415,13 @@ static struct io_ring_ctx *io_ring_ctx_alloc(struct io_uring_params *p)return ctx; } +static unsigned int io_cqring_events (struct io_rings *rings) +{ +/* See comment at the top of this file */ +smp_rmb (); +return READ_ONCE (rings->cq.tail) -READ_ONCE (rings->cq.head); +} +static inline bool io_sequence_defer (struct io_ring_ctx *ctx,struct io_kiocb *req) {
@@ -559,16 +567,27 @@ static void io_cqring_fill_event(struct io_ring_ctx *ctx, u64 ki_user_data, } } -static void io_cqring_ev_posted (struct io_ring_ctx *ctx) +static void __io_cqring_ev_posted (struct io_ring_ctx *ctx, +unsigned int nr_events) {if (waitqueue_active (&ctx->wait)) -wake_up (&ctx->wait); +wake_up_threshold (&ctx->wait, nr_events);if (waitqueue_active (&ctx->sqo_wait))wake_up (&ctx->sqo_wait);if (ctx->cq_ev_fd)eventfd_signal (ctx->cq_ev_fd,1 ); } +static inline void io_cqring_ev_posted (struct io_ring_ctx *ctx) +{ +__io_cqring_ev_posted (ctx,io_cqring_events (ctx->rings)); +} + +static inline void io_cqring_timeout_posted (struct io_ring_ctx *ctx) +{ +__io_cqring_ev_posted (ctx,WQ_THRESHOLD_WAKE_ALWAYS ); +} +static void io_cqring_add_event (struct io_ring_ctx *ctx,u64 user_data,long res) {
@@ -587,7 +606,7 @@ static void io_ring_drop_ctx_refs(struct io_ring_ctx *ctx, unsigned refs)percpu_ref_put_many (&ctx->refs, refs);if (waitqueue_active (&ctx->wait)) -wake_up (&ctx->wait); +wake_up_threshold (&ctx->wait,io_cqring_events (ctx->rings)); } static struct io_kiocb *io_get_req(struct io_ring_ctx *ctx,
@@ -722,12 +741,6 @@ static void io_put_req(struct io_kiocb *req)io_free_req (req); } -static unsigned io_cqring_events (struct io_rings *rings) -{ -/* See comment at the top of this file */ -smp_rmb (); -return READ_ONCE (rings->cq.tail) -READ_ONCE (rings->cq.head); -}/* * Find and free completed poll iocbs
@@ -1824,7 +1837,7 @@ static enum hrtimer_restart io_timeout_fn(struct hrtimer *timer)io_commit_cqring (ctx);spin_unlock_irqrestore (&ctx->completion_lock, flags); -io_cqring_ev_posted (ctx); +io_cqring_timeout_posted (ctx);io_put_req (req);return HRTIMER_NORESTART ;
@@ -2723,7 +2736,7 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,* we started waiting. For timeouts, we always want to return to * userspace. */ - ret =wait_event_interruptible (ctx->wait, + ret =wait_threshold_interruptible (ctx->wait, min_events,io_cqring_events (rings) >= min_events ||atomic_read (&ctx->cq_timeouts) != nr_timeouts);restore_saved_sigmask_unless (ret == -ERESTARTSYS );
Signature · 1 line
2.23.0
Jens Axboe
maintainerQuoted 7 lines · Pavel Begunkov (Silence)
From: Pavel Begunkov <[email protected]>
There could be a lot of overhead within generic wait_event_*() used for waiting for large number of completions. The patchset removes much of it by using custom wait event (
wait_threshold).Synthetic test showed ~40% performance boost. (see patch 2)
I'm fine with the io_uring side of things, but to queue this up we really need Peter or Ingo to sign off on the core wakeup bits...
Peter?
Signature · 1 line
Jens Axboe
Quoted 7 lines · Jens Axboe
Quoted 6 lines · Pavel Begunkov (Silence)
From: Pavel Begunkov <[email protected]>
There could be a lot of overhead within generic wait_event_*() used for waiting for large number of completions. The patchset removes much of it by using custom wait event (
wait_threshold).
Quoted 6 lines
Quoted 1 line
Synthetic test showed ~40% performance boost. (see patch 2)
I'm fine with the
io_uringside of things, but to queue this up we really need Peter or Ingo to sign off on the core wakeup bits...Peter?
I'm not sure an extension is needed for such a special interface, why not just put a ->threshold value next to the ctx->wait field and use either the regular wait_event() APIs with the proper condition, or wait_event_cmd() style APIs if you absolutely need something more complex to happen inside?
Should result in a much lower linecount and no scheduler changes. :-)
Thanks,
Ingo
Hi, and thanks for the feedback.
It could be done with @cond indeed, that's how it works for now. However, this addresses performance issues only.
The problem with wait_event_*() is that, if we have a counter and are trying to wake up tasks after each increment, it would schedule each waiting task O(threshold) times just for it to spuriously check @cond and go back to sleep. All that overhead (memory barriers, registers save/load, accounting, etc) turned out to be enough for some workloads to slow down the system.
With this specialisation it still traverses a wait list and makes indirect calls to the checker callback, but the list supposedly is fairly small, so performance there shouldn't be a problem, at least for now.
Regarding semantics; It should wake a task when a value passed to wake_up_threshold() is greater or equal then a task's threshold, that is specified individually for each task in wait_threshold_*().
In pseudo code:
``` def wake_up_threshold(n, wait_queue): for waiter in wait_queue: waiter.wake_up_if(n >= waiter.threshold); ```
Any thoughts how to do it better? Ideas are very welcome.
BTW, this monster is mostly a copy-paste from wait_event_*(), wait_bit_*(). We could try to extract some common parts from these three, but that's another topic.
Quoted 29 lines · Ingo Molnar
Quoted 7 lines · Jens Axboe
Quoted 6 lines · Pavel Begunkov (Silence)
From: Pavel Begunkov <[email protected]>
There could be a lot of overhead within generic wait_event_*() used for waiting for large number of completions. The patchset removes much of it by using custom wait event (
wait_threshold).Quoted 6 lines
Quoted 1 line
Synthetic test showed ~40% performance boost. (see patch 2)
I'm fine with the
io_uringside of things, but to queue this up we really need Peter or Ingo to sign off on the core wakeup bits...Peter?
I'm not sure an extension is needed for such a special interface, why not just put a ->threshold value next to the
ctx->waitfield and use either the regularwait_event()APIs with the proper condition, orwait_event_cmd()style APIs if you absolutely need something more complex to happen inside?Should result in a much lower linecount and no scheduler changes. :-)
Thanks,
Ingo
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Sorry, mixed the threads.
Quoted 6 lines
Quoted 6 lines
I'm not sure an extension is needed for such a special interface, why not just put a ->threshold value next to the
ctx->waitfield and use either the regularwait_event()APIs with the proper condition, orwait_event_cmd()style APIs if you absolutely need something more complex to happen inside?
Ingo,io_uring works well without this patch just using wait_event_*() with proper condition, but there are performance issues with spurious wakeups. Detailed description in the previous mail.
Am I missing something?
Thanks
Quoted 8 lines
Quoted 7 lines
Should result in a much lower linecount and no scheduler changes. :-)
Thanks,
Ingo
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 8 lines · Pavel Begunkov
Sorry, mixed the threads.
Quoted 6 lines
Quoted 6 lines
I'm not sure an extension is needed for such a special interface, why not just put a ->threshold value next to the
ctx->waitfield and use either the regularwait_event()APIs with the proper condition, orwait_event_cmd()style APIs if you absolutely need something more complex to happen inside?
Quoted 5 lines
Ingo,
io_uringworks well without this patch just using wait_event_*() with proper condition, but there are performance issues with spurious wakeups. Detailed description in the previous mail.
Am I missing something?
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Signature · 1 line
Jens Axboe
Jens Axboe
maintainerQuoted 15 lines · Jens Axboe
Quoted 8 lines · Pavel Begunkov
Sorry, mixed the threads.
Quoted 6 lines
Quoted 6 lines
I'm not sure an extension is needed for such a special interface, why not just put a ->threshold value next to the
ctx->waitfield and use either the regularwait_event()APIs with the proper condition, orwait_event_cmd()style APIs if you absolutely need something more complex to happen inside?Quoted 5 lines
Ingo,
io_uringworks well without this patch just using wait_event_*() with proper condition, but there are performance issues with spurious wakeups. Detailed description in the previous mail.
Am I missing something?
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Totally untested, and sent out 5 min before departure... But something like this.
fs/io_uring.c +50 -10
@@ -2768,6 +2768,37 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task); +return 1 ; + } + +return -1 ; +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring.
@@ -2775,8 +2806,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .task = current, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)
@@ -2795,15 +2834,16 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 1 line
Jens Axboe
Quoted 6 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 89 lines
fs/io_uring.c +50
-10@@ -2768,6 +2768,37 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task); +return 1 ; + } + +return -1 ; +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring. @@ -2775,8 +2806,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .task = current, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)@@ -2795,15 +2834,16 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
I totally agree it could be useful in other places. Maybe formalized and used with wake_up_nr() instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.
In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
fs/io_uring.c +55 -10
@@ -2768,6 +2768,42 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task); +return 1 ; + } + +return -1 ; +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring.
@@ -2775,8 +2811,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .task = current, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)
@@ -2795,15 +2839,16 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 1 line
Jens Axboe
Jens Axboe
maintainerQuoted 15 lines · Jens Axboe
Quoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 7 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
Notes:
This version gets the ordering right, you need exclusive waits to get fifo ordering on the waitqueue.
Both versions (yours and mine) suffer from the problem of potentially waking too many. I don't think this is a real issue, as generally we don't do threaded access to the io_urings. But if you had the following tasks wait on the cqring:
[min_events = 32], [min_events = 8], [min_events = 8]
and we reach the io_cqring_events() == threshold, we'll wake all three. I don't see a good solution to this, so I suspect we just live with until proven an issue. Both versions are much better than what we have now.
Signature · 1 line
Jens Axboe
Jens Axboe
maintainerQuoted 34 lines · Jens Axboe
Quoted 15 lines · Jens Axboe
Quoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 7 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
Notes:
This version gets the ordering right, you need exclusive waits to get fifo ordering on the waitqueue.
Both versions (yours and mine) suffer from the problem of potentially waking too many. I don't think this is a real issue, as generally we don't do threaded access to the
io_urings. But if you had the following tasks wait on the cqring:
Quoted 6 lines
[
min_events= 32], [min_events= 8], [min_events= 8]and we reach the
io_cqring_events()== threshold, we'll wake all three. I don't see a good solution to this, so I suspect we just live with until proven an issue. Both versions are much better than what we have now.
Forgot an issue around signal handling, version below adds the right check for that too.
Curious what your test case was for this?
fs/io_uring.c +57 -10
@@ -2768,6 +2768,42 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task); +return 1 ; + } + +return -1 ; +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring.
@@ -2775,8 +2811,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .task = current, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)
@@ -2795,15 +2839,18 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) +break ; +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 1 line
Jens Axboe
Quoted 37 lines · Jens Axboe
Quoted 36 lines · Jens Axboe
Quoted 15 lines · Jens Axboe
Quoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 7 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
Notes:
This version gets the ordering right, you need exclusive waits to get fifo ordering on the waitqueue.
Both versions (yours and mine) suffer from the problem of potentially waking too many. I don't think this is a real issue, as generally we don't do threaded access to the
io_urings. But if you had the following tasks wait on the cqring:[
min_events= 32], [min_events= 8], [min_events= 8]
Quoted 7 lines
Quoted 4 lines
and we reach the
io_cqring_events()== threshold, we'll wake all three. I don't see a good solution to this, so I suspect we just live with until proven an issue. Both versions are much better than what we have now.Forgot an issue around signal handling, version below adds the right check for that too.
It seems to be a good reason to not keep reimplementing "prepare_to_wait*() + wait loop" every time, but keep it in sched :)
Quoted 2 lines
Curious what your test case was for this?
You mean a performance test case? It's briefly described in a comment for the second patch. That's just rewritten io_uring-bench, with
1. a thread generating 1 request per call in a loop
2. and the second thread waiting for ~128 events.
Both are pinned to the same core.
Quoted 96 lines
fs/io_uring.c +57
-10@@ -2768,6 +2768,42 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task); +return 1 ; + } + +return -1 ; +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring. @@ -2775,8 +2811,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .task = current, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)@@ -2795,15 +2839,18 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) +break ; +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 45 lines · Pavel Begunkov
Quoted 42 lines · Jens Axboe
Quoted 40 lines · Jens Axboe
Quoted 15 lines · Jens Axboe
Quoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 7 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
Notes:
This version gets the ordering right, you need exclusive waits to get fifo ordering on the waitqueue.
Both versions (yours and mine) suffer from the problem of potentially waking too many. I don't think this is a real issue, as generally we don't do threaded access to the
io_urings. But if you had the following tasks wait on the cqring:[
min_events= 32], [min_events= 8], [min_events= 8]and we reach the
io_cqring_events()== threshold, we'll wake all three. I don't see a good solution to this, so I suspect we just live with until proven an issue. Both versions are much better than what we have now.
Quoted 5 lines
Quoted 2 lines
Forgot an issue around signal handling, version below adds the right check for that too.
It seems to be a good reason to not keep reimplementing "prepare_to_wait*() + wait loop" every time, but keep it in sched :)
I think if we do the ->private cleanup that Peter mentioned, then there's not much left in terms of consolidation. Not convinced the case is interesting enough to warrant a special helper. If others show up, it's easy enough to consolidate the use cases and unify them.
If you look at wake_up_nr(), I would have thought that would be more widespread. But it really isn't.
Quoted 6 lines
Quoted 1 line
Curious what your test case was for this?
You mean a performance test case? It's briefly described in a comment for the second patch. That's just rewritten io_uring-bench, with
1. a thread generating 1 request per call in a loop
2. and the second thread waiting for ~128 events.
Both are pinned to the same core.
Gotcha, thanks.
Signature · 1 line
Jens Axboe
Peter Zijlstra
maintainerfs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2774
Quoted 4 lines · Jens Axboe
fs/io_uring.c +4
-0+struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task;
wq.private is where the normal waitqueue stores the task pointer.
(I'm going to rename that)
Quoted 16 lines
fs/io_uring.c +16
-0+unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +}
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2800
Quoted 10 lines
fs/io_uring.c +10
-0+ +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task);
Then you can use autoremove_wake_function() here.
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2805
Quoted 5 lines
fs/io_uring.c +5
-0+return 1 ; + } + +return -1 ; +}
Ideally we'd get wait_event()'s @cond in a custom wake function. Then we can _always_ do this.
This is one I'd love to have lambda functions for. It would actually work with GCC nested functions, because the wake function will always be in scope, but we can't use those in the kernel for other reasons :/
Jens Axboe
maintainerQuoted 10 lines · Peter Zijlstra
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2774
Quoted 4 lines · Jens Axboe
fs/io_uring.c +4
-0+struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task;wq.private is where the normal waitqueue stores the task pointer.
(I'm going to rename that)
If you do that, then we can just base the io_uring parts on that.
Quoted 38 lines
Quoted 16 lines
fs/io_uring.c +16
-0+unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +}fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2800
Quoted 10 lines
fs/io_uring.c +10
-0+ +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task);Then you can use
autoremove_wake_function()here.fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2805
Quoted 5 lines
fs/io_uring.c +5
-0+return 1 ; + } + +return -1 ; +}Ideally we'd get wait_event()'s @cond in a custom wake function. Then we can _always_ do this.
Quoted 3 lines
This is one I'd love to have lambda functions for. It would actually work with GCC nested functions, because the wake function will always be in scope, but we can't use those in the kernel for other reasons :/
I'll be happy enough if I can just call autoremove_wake_function(), I think that will simplify the case enough for io_uring to not really make me care too much about going further. I'll leave that to you, if you have the desire :-)
Signature · 1 line
Jens Axboe
Jens Axboe
maintainerQuoted 10 lines · Jens Axboe
Quoted 9 lines · Peter Zijlstra
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2774
Quoted 4 lines · Jens Axboe
fs/io_uring.c +4
-0+struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task;wq.private is where the normal waitqueue stores the task pointer.
Quoted 3 lines
Quoted 1 line
(I'm going to rename that)
If you do that, then we can just base the
io_uringparts on that.
Just took a quick look at it, and ran into block/kyber-iosched.c that actually uses the private pointer for something that isn't a task struct...
Signature · 1 line
Jens Axboe
Quoted 11 lines · Jens Axboe
Quoted 10 lines · Jens Axboe
Quoted 9 lines · Peter Zijlstra
Review
Quoted 4 lines · Jens Axboe
(quoted diff) +4
-0+struct io_wait_queue { + struct wait_queue_entry wq; + struct io_ring_ctx *ctx; + struct task_struct *task;wq.private is where the normal waitqueue stores the task pointer.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
(I'm going to rename that)
If you do that, then we can just base the
io_uringparts on that.Just took a quick look at it, and ran into
block/kyber-iosched.cthat actually uses the private pointer for something that isn't a task struct...
Let reuse autoremove_wake_function anyway. Changed a bit your patch:
fs/io_uring.c +53 -10
@@ -2690,6 +2690,38 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (!io_should_wake (iowq)) +return -1 ; + +return autoremove_wake_function (curr, mode, wake_flags, key); +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring.
@@ -2697,8 +2729,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .private = current, + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)
@@ -2717,15 +2757,18 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) +break ; +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 14 lines · Pavel Begunkov
Quoted 13 lines · Jens Axboe
Quoted 12 lines · Jens Axboe
Quoted 10 lines · Peter Zijlstra
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,
Quoted 4 lines · Jens Axboe
fs/io_uring.c +4
-0+struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task;wq.private is where the normal waitqueue stores the task pointer.
(I'm going to rename that)
Quoted 8 lines
Quoted 6 lines
Quoted 1 line
If you do that, then we can just base the
io_uringparts on that.Just took a quick look at it, and ran into
block/kyber-iosched.cthat actually uses the private pointer for something that isn't a task struct...Let reuse
autoremove_wake_functionanyway. Changed a bit your patch:
Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Signature · 1 line
Jens Axboe
Quoted 22 lines · Jens Axboe
Quoted 21 lines · Pavel Begunkov
Quoted 19 lines · Jens Axboe
Quoted 10 lines · Jens Axboe
Quoted 9 lines · Peter Zijlstra
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,
Quoted 4 lines · Jens Axboe
fs/io_uring.c +4
-0+struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task;wq.private is where the normal waitqueue stores the task pointer.
Quoted 3 lines
Quoted 1 line
(I'm going to rename that)
If you do that, then we can just base the
io_uringparts on that.Just took a quick look at it, and ran into
block/kyber-iosched.cthat actually uses the private pointer for something that isn't a task struct...
Quoted 7 lines
Quoted 1 line
Let reuse
autoremove_wake_functionanyway. Changed a bit your patch:Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Not yet, going to do that tonight
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 6 lines · Pavel Begunkov
Quoted 5 lines
Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Not yet, going to do that tonight
Thanks! For reference, the final version is below. There was still a signal mishap in there, now it should all be correct afaict.
fs/io_uring.c +55 -10
@@ -2768,6 +2768,38 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (!io_should_wake (iowq)) +return -1 ; + +return autoremove_wake_function (curr, mode, wake_flags, key); +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring.
@@ -2775,8 +2807,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .private = current, + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)
@@ -2795,15 +2835,20 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +do { +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq, +TASK_INTERRUPTIBLE ); +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) { + ret = -ERESTARTSYS ; +break ; + } + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 1 line
Jens Axboe
Quoted 38 lines · Jens Axboe
Quoted 6 lines · Pavel Begunkov
Quoted 5 lines
Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Not yet, going to do that tonight
Thanks! For reference, the final version is below. There was still a signal mishap in there, now it should all be correct afaict.
fs/io_uring.c +18
-0@@ -2768,6 +2768,38 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts;
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2798
Quoted 10 lines
fs/io_uring.c +10
-0+} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (!io_should_wake (iowq)) +return -1 ;
It would try to schedule only the first task in the wait list. Is that the semantic you want?
E.g. for waiters=[32,8] and nr_events == 8, io_wake_function() returns after @32, and won't wake up the second one.
Quoted 56 lines
fs/io_uring.c +27
-10+ +return autoremove_wake_function (curr, mode, wake_flags, key); +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring. @@ -2775,8 +2807,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .private = current, + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)@@ -2795,15 +2835,20 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +do { +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq, +TASK_INTERRUPTIBLE ); +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) { + ret = -ERESTARTSYS ; +break ; + } + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 46 lines · Pavel Begunkov
Quoted 45 lines · Jens Axboe
Quoted 6 lines · Pavel Begunkov
Quoted 5 lines
Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Not yet, going to do that tonight
Thanks! For reference, the final version is below. There was still a signal mishap in there, now it should all be correct afaict.
fs/io_uring.c +25
-0@@ -2768,6 +2768,38 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq);
Quoted 8 lines
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2798
Quoted 3 lines
fs/io_uring.c +3
-0+ +if (!io_should_wake (iowq)) +return -1 ;It would try to schedule only the first task in the wait list. Is that the semantic you want?
E.g. for waiters=[32,8] andnr_events== 8,io_wake_function()returns after @32, and won't wake up the second one.
Right, those are the semantics I want. We keep the list ordered by using the exclusive wait addition. Which means that for the case you list, waiters=32 came first, and we should not wake others before that task gets the completions it wants. Otherwise we could potentially starve higher count waiters, if we always keep going and new waiters come in.
Signature · 1 line
Jens Axboe
Quoted 56 lines · Jens Axboe
Quoted 46 lines · Pavel Begunkov
Quoted 45 lines · Jens Axboe
Quoted 6 lines · Pavel Begunkov
Quoted 5 lines
Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Not yet, going to do that tonight
Thanks! For reference, the final version is below. There was still a signal mishap in there, now it should all be correct afaict.
fs/io_uring.c +25
-0@@ -2768,6 +2768,38 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq);Quoted 8 lines
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2798
Quoted 3 lines
fs/io_uring.c +3
-0+ +if (!io_should_wake (iowq)) +return -1 ;It would try to schedule only the first task in the wait list. Is that the semantic you want?
E.g. for waiters=[32,8] andnr_events== 8,io_wake_function()returns after @32, and won't wake up the second one.
Quoted 6 lines
Right, those are the semantics I want. We keep the list ordered by using the exclusive wait addition. Which means that for the case you list, waiters=32 came first, and we should not wake others before that task gets the completions it wants. Otherwise we could potentially starve higher count waiters, if we always keep going and new waiters come in.
Yes. I think It would better to be documented in userspace API. I could imagine some crazy case deadlocking userspace. E.g. thread 1: wait_events(8), reap_events
thread 2: wait_events(32), wait(thread 1), reap_events
works well
BTW, I searched for wait_event*(), and it seems there are plenty of similar use cases. So, generic case would be useful, but this is for later.
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 63 lines · Pavel Begunkov
Quoted 56 lines · Jens Axboe
Quoted 46 lines · Pavel Begunkov
Quoted 45 lines · Jens Axboe
Quoted 6 lines · Pavel Begunkov
Quoted 5 lines
Yep that should do it, and saves 8 bytes of stack as well.
BTW, did you test my patch, this one or the previous? Just curious if it worked for you.
Not yet, going to do that tonight
Thanks! For reference, the final version is below. There was still a signal mishap in there, now it should all be correct afaict.
fs/io_uring.c +25
-0@@ -2768,6 +2768,38 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq);Quoted 8 lines
fs/io_uring.c static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit, · line 2798
Quoted 3 lines
fs/io_uring.c +3
-0+ +if (!io_should_wake (iowq)) +return -1 ;It would try to schedule only the first task in the wait list. Is that the semantic you want?
E.g. for waiters=[32,8] andnr_events== 8,io_wake_function()returns after @32, and won't wake up the second one.Quoted 6 lines
Right, those are the semantics I want. We keep the list ordered by using the exclusive wait addition. Which means that for the case you list, waiters=32 came first, and we should not wake others before that task gets the completions it wants. Otherwise we could potentially starve higher count waiters, if we always keep going and new waiters come in.
Quoted 4 lines
Yes. I think It would better to be documented in userspace API. I could imagine some crazy case deadlocking userspace. E.g. thread 1:
wait_events(8),reap_events
thread 2:wait_events(32), wait(thread 1),reap_events
No matter how you handle cases like this, there will always be deadlocks possible... So I don't think that's a huge concern. It's more important to not have intentional livelocks, which we would have if we always allowed the lowest wait count to get woken and steal the budget everytime.
Quoted 3 lines
works well
Reviewed-by Pavel Begunkov <[email protected]>Tested-by Pavel Begunkov <[email protected]>
Thanks, will add!
Quoted 3 lines
BTW, I searched for wait_event*(), and it seems there are plenty of similar use cases. So, generic case would be useful, but this is for later.
Agree, it would undoubtedly be useful.
Signature · 1 line
Jens Axboe
Peter Zijlstra
maintainerQuoted 5 lines · Pavel Begunkov
fs/io_uring.c +1
-0@@ -2717,15 +2757,18 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts);
fs/io_uring.c static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events, · line 2770
Quoted 10 lines
fs/io_uring.c +10
-0+prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) +break ; +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq);
It it likely OK, but for paranoia, I'd prefer this form:
for (;;) {
prepare_to_wait_exclusive(&ctx->wait, &iowq.wq, TASK_INTERRUPTIBLE);
if (io_should_wake(&iowq))
break;
schedule();
if (signal_pending(current))
break;
}
finish_wait(&ctx->wait, &iowq.wq);The thing is, if we ever succeed with io_wake_function() (that CPU observes io_should_wake()), but when waking here, we do not observe is_wake_function() and go sleep again, we might never wake up if we don't put ourselves back on the wait-list again.
Jens Axboe
maintainerQuoted 30 lines · Peter Zijlstra
Quoted 5 lines · Pavel Begunkov
fs/io_uring.c +1
-0@@ -2717,15 +2757,18 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts);fs/io_uring.c static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events, · line 2770
Quoted 10 lines
fs/io_uring.c +10
-0+prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +if (signal_pending (current)) +break ; +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq);It it likely OK, but for paranoia, I'd prefer this form:
for (;;) { prepare_to_wait_exclusive(&ctx->wait, &iowq.wq, TASK_INTERRUPTIBLE); if (io_should_wake(&iowq)) break; schedule(); if (signal_pending(current)) break; } finish_wait(&ctx->wait, &iowq.wq);
Quoted 4 lines
The thing is, if we ever succeed with
io_wake_function()(that CPU observesio_should_wake()), but when waking here, we do not observeis_wake_function()and go sleep again, we might never wake up if we don't put ourselves back on the wait-list again.
Might be paranoia indeed, but especially after this change, we don't expect to make frequent roundtrips there. So better safe than sorry, I'll make the change.
Signature · 1 line
Jens Axboe
Peter Zijlstra
maintainerQuoted 3 lines · Jens Axboe
Just took a quick look at it, and ran into
block/kyber-iosched.cthat actually uses the private pointer for something that isn't a task struct...
Argh... that's some 'creative' abuse of the waitqueue API :/
Quoted 34 lines · Jens Axboe
Quoted 15 lines · Jens Axboe
Quoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 7 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
Notes:
This version gets the ordering right, you need exclusive waits to get fifo ordering on the waitqueue.
Both versions (yours and mine) suffer from the problem of potentially waking too many. I don't think this is a real issue, as generally we don't do threaded access to the
io_urings. But if you had the following tasks wait on the cqring:
Quoted 7 lines
[
min_events= 32], [min_events= 8], [min_events= 8]and we reach the
io_cqring_events()== threshold, we'll wake all three. I don't see a good solution to this, so I suspect we just live with until proven an issue. Both versions are much better than what we have now.
If io_cqring_events() == 8, only the last two would be woken up in both implementations, as to_wait/threshold is specified per waiter. Isn't it?
Agree with waiting, I don't see a good real-life case for that, that couldn't be done efficiently in userspace.
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 39 lines · Pavel Begunkov
Quoted 36 lines · Jens Axboe
Quoted 15 lines · Jens Axboe
Quoted 5 lines · Pavel Begunkov
Quoted 4 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Quoted 8 lines
Quoted 2 lines
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 7 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
Notes:
This version gets the ordering right, you need exclusive waits to get fifo ordering on the waitqueue.
Both versions (yours and mine) suffer from the problem of potentially waking too many. I don't think this is a real issue, as generally we don't do threaded access to the
io_urings. But if you had the following tasks wait on the cqring:[
min_events= 32], [min_events= 8], [min_events= 8]
Quoted 7 lines
Quoted 5 lines
and we reach the
io_cqring_events()== threshold, we'll wake all three. I don't see a good solution to this, so I suspect we just live with until proven an issue. Both versions are much better than what we have now.If
io_cqring_events()== 8, only the last two would be woken up in both implementations, as to_wait/threshold is specified per waiter. Isn't it?
If io_cqring_events() == 8, then none would be woken in my implementation since the first one will break the wakeup loop.
Quoted 2 lines
Agree with waiting, I don't see a good real-life case for that, that couldn't be done efficiently in userspace.
Exactly
Signature · 1 line
Jens Axboe
Quoted 12 lines · Jens Axboe
Quoted 11 lines · Pavel Begunkov
Quoted 6 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
Quoted 6 lines
Quoted 2 lines
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.
@nr there is about number of tasks to wake up. AFAIK doesn't solve the problem.
Quoted 98 lines
In any case, I did get a chance to test it and it works for me. Here's the "finished" version, slightly cleaned up and with a comment added for good measure.
fs/io_uring.c +55
-10@@ -2768,6 +2768,42 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,return submit; } +struct io_wait_queue { +struct wait_queue_entry wq; +struct io_ring_ctx *ctx; +struct task_struct *task; +unsigned to_wait; +unsigned nr_timeouts; +}; + +static inline bool io_should_wake (struct io_wait_queue *iowq) +{ +struct io_ring_ctx *ctx = iowq->ctx; + +/* +* Wake up if we have enough events, or if a timeout occured since we +* started waiting. For timeouts, we always want to return to userspace, +* regardless of event count. +*/ +return io_cqring_events (ctx->rings) >= iowq->to_wait || +atomic_read (&ctx->cq_timeouts) != iowq->nr_timeouts; +} + +static int io_wake_function (struct wait_queue_entry *curr,unsigned int mode, +int wake_flags,void *key) +{ + struct io_wait_queue *iowq = container_of(curr, struct io_wait_queue, + wq); + +if (io_should_wake (iowq)) { +list_del_init (&curr->entry); +wake_up_process (iowq->task); +return 1 ; + } + +return -1 ; +} +/* * Wait until events become available, if we don't already have some. The * application must reap them itself, as they reside on the shared cq ring. @@ -2775,8 +2811,16 @@ static int io_ring_submit(struct io_ring_ctx *ctx, unsigned int to_submit,static int io_cqring_wait (struct io_ring_ctx *ctx,int min_events,const sigset_t __user *sig,size_t sigsz) { +struct io_wait_queue iowq = { + .wq = { + .func = io_wake_function, + .entry =LIST_HEAD_INIT (iowq.wq.entry), + }, + .task = current, + .ctx = ctx, + .to_wait = min_events, + };struct io_rings *rings = ctx->rings; -unsigned nr_timeouts;int ret;if (io_cqring_events (rings) >= min_events)@@ -2795,15 +2839,16 @@ static int io_cqring_wait(struct io_ring_ctx *ctx, int min_events,return ret; } - nr_timeouts =atomic_read (&ctx->cq_timeouts); -/* -* Return if we have enough events, or if a timeout occured since -* we started waiting. For timeouts, we always want to return to -* userspace. -*/ - ret =wait_event_interruptible (ctx->wait, -io_cqring_events (rings) >= min_events || -atomic_read (&ctx->cq_timeouts) != nr_timeouts); + iowq.nr_timeouts =atomic_read (&ctx->cq_timeouts); +prepare_to_wait_exclusive (&ctx->wait, &iowq.wq,TASK_INTERRUPTIBLE ); +do { +if (io_should_wake (&iowq)) +break ; +schedule (); +set_current_state (TASK_INTERRUPTIBLE ); + }while (1 ); +finish_wait (&ctx->wait, &iowq.wq); +restore_saved_sigmask_unless (ret == -ERESTARTSYS );if (ret == -ERESTARTSYS ) ret = -EINTR ;
Signature · 2 lines
Yours sincerely, Pavel Begunkov
Jens Axboe
maintainerQuoted 16 lines · Pavel Begunkov
Quoted 15 lines · Jens Axboe
Quoted 13 lines · Pavel Begunkov
Quoted 6 lines · Jens Axboe
Quoted 3 lines
I think we can do the same thing, just wrapping the waitqueue in a structure with a count in it, on the stack. Got some flight time coming up later today, let me try and cook up a patch.
Totally untested, and sent out 5 min before departure... But something like this.
Hmm, reminds me my first version. Basically that's the same thing but with macroses inlined. I wanted to make it reusable and self-contained, though.
If you don't think it could be useful in other places, sure, we could do something like that. Is that so?
Quoted 6 lines
Quoted 3 lines
I totally agree it could be useful in other places. Maybe formalized and used with
wake_up_nr()instead of adding a new primitive? Haven't looked into that, I may be talking nonsense.@nr there is about number of tasks to wake up. AFAIK doesn't solve the problem.
Ah right, embarassingly I'm actually the one that added that functionality ages ago...
Signature · 1 line
Jens Axboe