io-wq: provide a way to limit max number of workers
authorJens Axboe <axboe@kernel.dk>
Fri, 27 Aug 2021 17:33:19 +0000 (11:33 -0600)
committerJens Axboe <axboe@kernel.dk>
Sun, 29 Aug 2021 13:55:55 +0000 (07:55 -0600)
io-wq divides work into two categories:

1) Work that completes in a bounded time, like reading from a regular file
   or a block device. This type of work is limited based on the size of
   the SQ ring.

2) Work that may never complete, we call this unbounded work. The amount
   of workers here is just limited by RLIMIT_NPROC.

For various uses cases, it's handy to have the kernel limit the maximum
amount of pending workers for both categories. Provide a way to do with
with a new IORING_REGISTER_IOWQ_MAX_WORKERS operation.

IORING_REGISTER_IOWQ_MAX_WORKERS takes an array of two integers and sets
the max worker count to what is being passed in for each category. The
old values are returned into that same array. If 0 is being passed in for
either category, it simply returns the current value.

The value is capped at RLIMIT_NPROC. This actually isn't that important
as it's more of a hint, if we're exceeding the value then our attempt
to fork a new worker will fail. This happens naturally already if more
than one node is in the system, as these values are per-node internally
for io-wq.

Reported-by: Johannes Lundberg <johalun0@gmail.com>
Link: https://github.com/axboe/liburing/issues/420
Signed-off-by: Jens Axboe <axboe@kernel.dk>
fs/io-wq.c
fs/io-wq.h
fs/io_uring.c
include/uapi/linux/io_uring.h

index 8da9bb1039168ac3b15434492f926d31047b99bf..4b5fc621ab397a0db64b2e30d0b3896bf33abbb8 100644 (file)
@@ -1152,6 +1152,35 @@ int io_wq_cpu_affinity(struct io_wq *wq, cpumask_var_t mask)
        return 0;
 }
 
+/*
+ * Set max number of unbounded workers, returns old value. If new_count is 0,
+ * then just return the old value.
+ */
+int io_wq_max_workers(struct io_wq *wq, int *new_count)
+{
+       int i, node, prev = 0;
+
+       for (i = 0; i < 2; i++) {
+               if (new_count[i] > task_rlimit(current, RLIMIT_NPROC))
+                       new_count[i] = task_rlimit(current, RLIMIT_NPROC);
+       }
+
+       rcu_read_lock();
+       for_each_node(node) {
+               struct io_wqe_acct *acct;
+
+               for (i = 0; i < 2; i++) {
+                       acct = &wq->wqes[node]->acct[i];
+                       prev = max_t(int, acct->max_workers, prev);
+                       if (new_count[i])
+                               acct->max_workers = new_count[i];
+                       new_count[i] = prev;
+               }
+       }
+       rcu_read_unlock();
+       return 0;
+}
+
 static __init int io_wq_init(void)
 {
        int ret;
index 308af392842441ea7a064c62765b028fcbf37dcc..bf5c4c533760574f87d0cab165be2f4c60ecbff3 100644 (file)
@@ -128,6 +128,7 @@ void io_wq_enqueue(struct io_wq *wq, struct io_wq_work *work);
 void io_wq_hash_work(struct io_wq_work *work, void *val);
 
 int io_wq_cpu_affinity(struct io_wq *wq, cpumask_var_t mask);
+int io_wq_max_workers(struct io_wq *wq, int *new_count);
 
 static inline bool io_wq_is_hashed(struct io_wq_work *work)
 {
index 53326449d68563a36a78451b4e9659d0e93fb8c4..edbda88142f98174f062df22492814ae5ff750ca 100644 (file)
@@ -10233,6 +10233,31 @@ static int io_unregister_iowq_aff(struct io_ring_ctx *ctx)
        return io_wq_cpu_affinity(tctx->io_wq, NULL);
 }
 
+static int io_register_iowq_max_workers(struct io_ring_ctx *ctx,
+                                       void __user *arg)
+{
+       struct io_uring_task *tctx = current->io_uring;
+       __u32 new_count[2];
+       int i, ret;
+
+       if (!tctx || !tctx->io_wq)
+               return -EINVAL;
+       if (copy_from_user(new_count, arg, sizeof(new_count)))
+               return -EFAULT;
+       for (i = 0; i < ARRAY_SIZE(new_count); i++)
+               if (new_count[i] > INT_MAX)
+                       return -EINVAL;
+
+       ret = io_wq_max_workers(tctx->io_wq, new_count);
+       if (ret)
+               return ret;
+
+       if (copy_to_user(arg, new_count, sizeof(new_count)))
+               return -EFAULT;
+
+       return 0;
+}
+
 static bool io_register_op_must_quiesce(int op)
 {
        switch (op) {
@@ -10250,6 +10275,7 @@ static bool io_register_op_must_quiesce(int op)
        case IORING_REGISTER_BUFFERS_UPDATE:
        case IORING_REGISTER_IOWQ_AFF:
        case IORING_UNREGISTER_IOWQ_AFF:
+       case IORING_REGISTER_IOWQ_MAX_WORKERS:
                return false;
        default:
                return true;
@@ -10406,6 +10432,12 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode,
                        break;
                ret = io_unregister_iowq_aff(ctx);
                break;
+       case IORING_REGISTER_IOWQ_MAX_WORKERS:
+               ret = -EINVAL;
+               if (!arg || nr_args != 2)
+                       break;
+               ret = io_register_iowq_max_workers(ctx, arg);
+               break;
        default:
                ret = -EINVAL;
                break;
index 45a4f2373694a13a14641b159335e3cafaa0d5ff..64fe809c4e36b37a9395833ff04c77d6b438ad85 100644 (file)
@@ -309,6 +309,9 @@ enum {
        IORING_REGISTER_IOWQ_AFF                = 17,
        IORING_UNREGISTER_IOWQ_AFF              = 18,
 
+       /* set/get max number of workers */
+       IORING_REGISTER_IOWQ_MAX_WORKERS        = 19,
+
        /* this goes last */
        IORING_REGISTER_LAST
 };