]> git.proxmox.com Git - mirror_zfs.git/commitdiff
Add a delay to tearing down threads.
authorRich Ercolani <214141+rincebrain@users.noreply.github.com>
Mon, 26 Jun 2023 20:57:12 +0000 (16:57 -0400)
committerGitHub <noreply@github.com>
Mon, 26 Jun 2023 20:57:12 +0000 (13:57 -0700)
It's been observed that in certain workloads (zvol-related being a
big one), ZFS will end up spending a large amount of time spinning
up taskqs only to tear them down again almost immediately, then
spin them up again...

I noticed this when I looked at what my mostly-idle system was doing
and wondered how on earth taskq creation/destroy was a bunch of time...

So I added a configurable delay to avoid it tearing down tasks the
first time it notices them idle, and the total number of threads at
steady state went up, but the amount of time being burned just
tearing down/turning up new ones almost vanished.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Rich Ercolani <rincebrain@gmail.com>
Closes #14938

include/os/linux/spl/sys/taskq.h
man/man4/spl.4
module/os/linux/spl/spl-taskq.c

index 2a6cd8283d16439eb30e79c973fa217466874b8f..6c1b4377a98a71a939fa52286e2c1436ca16a371 100644 (file)
@@ -104,6 +104,7 @@ typedef struct taskq {
        /* list node for the cpu hotplug callback */
        struct hlist_node       tq_hp_cb_node;
        boolean_t               tq_hp_support;
+       unsigned long           lastshouldstop; /* when to purge dynamic */
 } taskq_t;
 
 typedef struct taskq_ent {
index 02efaf16dc3a2b55f0b3b1260e76ce32987e92fa..82455fb532547fe9db55d9373ac5a2b2650a94d8 100644 (file)
@@ -193,4 +193,19 @@ The proc file will walk the lists with lock held,
 reading it could cause a lock-up if the list grow too large
 without limiting the output.
 "(truncated)" will be shown if the list is larger than the limit.
+.
+.It Sy spl_taskq_thread_timeout_ms Ns = Ns Sy 10000 Pq uint
+(Linux-only)
+How long a taskq has to have had no work before we tear it down.
+Previously, we would tear down a dynamic taskq worker as soon
+as we noticed it had no work, but it was observed that this led
+to a lot of churn in tearing down things we then immediately
+spawned anew.
+In practice, it seems any nonzero value will remove the vast
+majority of this churn, while the nontrivially larger value
+was chosen to help filter out the little remaining churn on
+a mostly idle system.
+Setting this value to
+.Sy 0
+will revert to the previous behavior.
 .El
index 84497359ce2e09ba894d9e31858399ba96187f17..d18f935b167caa51b55cfe2abc62e9c99c42496f 100644 (file)
@@ -36,6 +36,12 @@ static int spl_taskq_thread_bind = 0;
 module_param(spl_taskq_thread_bind, int, 0644);
 MODULE_PARM_DESC(spl_taskq_thread_bind, "Bind taskq thread to CPU by default");
 
+static uint_t spl_taskq_thread_timeout_ms = 10000;
+/* BEGIN CSTYLED */
+module_param(spl_taskq_thread_timeout_ms, uint, 0644);
+/* END CSTYLED */
+MODULE_PARM_DESC(spl_taskq_thread_timeout_ms,
+       "Time to require a dynamic thread be idle before it gets cleaned up");
 
 static int spl_taskq_thread_dynamic = 1;
 module_param(spl_taskq_thread_dynamic, int, 0444);
@@ -848,12 +854,37 @@ taskq_thread_should_stop(taskq_t *tq, taskq_thread_t *tqt)
            tqt_thread_list) == tqt)
                return (0);
 
-       return
+       int no_work =
            ((tq->tq_nspawn == 0) &&    /* No threads are being spawned */
            (tq->tq_nactive == 0) &&    /* No threads are handling tasks */
            (tq->tq_nthreads > 1) &&    /* More than 1 thread is running */
            (!taskq_next_ent(tq)) &&    /* There are no pending tasks */
            (spl_taskq_thread_dynamic)); /* Dynamic taskqs are allowed */
+
+       /*
+        * If we would have said stop before, let's instead wait a bit, maybe
+        * we'll see more work come our way soon...
+        */
+       if (no_work) {
+               /* if it's 0, we want the old behavior. */
+               /* if the taskq is being torn down, we also want to go away. */
+               if (spl_taskq_thread_timeout_ms == 0 ||
+                   !(tq->tq_flags & TASKQ_ACTIVE))
+                       return (1);
+               unsigned long lasttime = tq->lastshouldstop;
+               if (lasttime > 0) {
+                       if (time_after(jiffies, lasttime +
+                           msecs_to_jiffies(spl_taskq_thread_timeout_ms)))
+                               return (1);
+                       else
+                               return (0);
+               } else {
+                       tq->lastshouldstop = jiffies;
+               }
+       } else {
+               tq->lastshouldstop = 0;
+       }
+       return (0);
 }
 
 static int
@@ -1091,6 +1122,7 @@ taskq_create(const char *name, int threads_arg, pri_t pri,
        tq->tq_flags = (flags | TASKQ_ACTIVE);
        tq->tq_next_id = TASKQID_INITIAL;
        tq->tq_lowest_id = TASKQID_INITIAL;
+       tq->lastshouldstop = 0;
        INIT_LIST_HEAD(&tq->tq_free_list);
        INIT_LIST_HEAD(&tq->tq_pend_list);
        INIT_LIST_HEAD(&tq->tq_prio_list);