Skip to contentSkip to conversation

Optimise io_uring completion waiting

PATCH In review Pavel Begunkov (Silence) 4 participants 38 messages Sep 22, 2019 – Sep 24, 2019

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

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
#1 lore raw

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
new file mode 100644
index 000000000000..d8b054504c26
@@ -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
index 21fb5a5662b5..bb895a3184f9 100644
@@ -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
new file mode 100644
index 000000000000..80a027c02ff3
@@ -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
maintainer
#2 lore raw

Re: [PATCH v2 1/2] sched/wait: Add wait_threshold

Quoted 4 lines · Pavel Begunkov (Silence)

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.

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 ?

#3 lore raw

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 custom wait_event derivative, 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
maintainer
#4 lore raw

Re: [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.

maintainer
#5 lore raw

Re: [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.

#6 lore raw

Re: [PATCH v2 1/2] sched/wait: Add wait_threshold

In reply to Peter Zijlstra
Quoted 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_bit already 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
#7 lore raw

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
index 5c3f2bb81637..05f4391c7bbe 100644
@@ -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
maintainer
#8 lore raw
In reply to Pavel Begunkov (Silence)
Quoted 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
#9 lore raw
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_uring side 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

#10 lore raw

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_uring side 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

Signature · 2 lines
Yours sincerely,
Pavel Begunkov
#11 lore raw

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->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?

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
maintainer
#12 lore raw
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->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?

Quoted 5 lines

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?

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
maintainer
#13 lore raw
Quoted 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->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?

Quoted 5 lines

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?

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
index ca7570aca430..c2f9e1da26dd 100644
@@ -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
#14 lore raw
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
index ca7570aca430..c2f9e1da26dd 100644
@@ -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
maintainer
#15 lore raw
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?

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
index ca7570aca430..14fae454cf75 100644
@@ -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
maintainer
#16 lore raw
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.

Signature · 1 line
Jens Axboe
maintainer
#17 lore raw
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 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
index ca7570aca430..3fbab5692f14 100644
@@ -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
#18 lore raw
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
index ca7570aca430..3fbab5692f14 100644
@@ -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
maintainer
#19 lore raw
Quoted 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
maintainer
#20 lore raw
In reply to Jens Axboe

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)

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 :/

maintainer
#21 lore raw
Quoted 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
maintainer
#22 lore raw
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, · 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_uring parts 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
#23 lore raw
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_uring parts 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...

Let reuse autoremove_wake_function anyway. Changed a bit your patch:

fs/io_uring.c +53 -10
index 5c3f2bb81637..a77971290fdd 100644
@@ -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
maintainer
#24 lore raw
Quoted 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_uring parts 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...

Let reuse autoremove_wake_function anyway. 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
#25 lore raw
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_uring parts 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...

Quoted 7 lines
Quoted 1 line

Let reuse autoremove_wake_function anyway. 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
maintainer
#26 lore raw
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 +55 -10
index 9b84232e5cc4..d2a86164d520 100644
@@ -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
#27 lore raw
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
index 9b84232e5cc4..d2a86164d520 100644
@@ -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
maintainer
#28 lore raw
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
index 9b84232e5cc4..d2a86164d520 100644
@@ -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] and nr_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
author Reviewed-by Tested-by
#29 lore raw
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
index 9b84232e5cc4..d2a86164d520 100644
@@ -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] and nr_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
maintainer
#30 lore raw
Quoted 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
index 9b84232e5cc4..d2a86164d520 100644
@@ -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] and nr_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

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
maintainer
#31 lore raw
In reply to Pavel Begunkov
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);

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.

maintainer
#32 lore raw
Quoted 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 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.

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
maintainer
#33 lore raw
In reply to Jens Axboe
Quoted 3 lines · Jens Axboe

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...

Argh... that's some 'creative' abuse of the waitqueue API :/

#34 lore raw
In reply to Jens Axboe
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
maintainer
#35 lore raw
Quoted 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
#36 lore raw
In reply to 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
index ca7570aca430..14fae454cf75 100644
@@ -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
maintainer
#37 lore raw
Quoted 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