Linux: Cleanup taskq threads spawn/exit
This changes taskq_thread_should_stop() to limit maximum exit rate for idle threads to one per 5 seconds. I believe the previous one was broken, not allowing any thread exits for tasks arriving more than one at a time and so completing while others are running. Also while there: - Remove taskq_thread_spawn() calls on task allocation errors. - Remove extra taskq_thread_should_stop() call. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Rich Ercolani <rincebrain@gmail.com> Signed-off-by: Alexander Motin <mav@FreeBSD.org> Sponsored by: iXsystems, Inc. Closes #15873
This commit is contained in:
parent
fdd97e0093
commit
793a2cff2a
|
@ -104,7 +104,7 @@ typedef struct taskq {
|
||||||
/* list node for the cpu hotplug callback */
|
/* list node for the cpu hotplug callback */
|
||||||
struct hlist_node tq_hp_cb_node;
|
struct hlist_node tq_hp_cb_node;
|
||||||
boolean_t tq_hp_support;
|
boolean_t tq_hp_support;
|
||||||
unsigned long lastshouldstop; /* when to purge dynamic */
|
unsigned long lastspawnstop; /* when to purge dynamic */
|
||||||
} taskq_t;
|
} taskq_t;
|
||||||
|
|
||||||
typedef struct taskq_ent {
|
typedef struct taskq_ent {
|
||||||
|
|
|
@ -186,18 +186,8 @@ reading it could cause a lock-up if the list grow too large
|
||||||
without limiting the output.
|
without limiting the output.
|
||||||
"(truncated)" will be shown if the list is larger than the limit.
|
"(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
|
.It Sy spl_taskq_thread_timeout_ms Ns = Ns Sy 5000 Pq uint
|
||||||
(Linux-only)
|
Minimum idle threads exit interval for dynamic taskqs.
|
||||||
How long a taskq has to have had no work before we tear it down.
|
Smaller values allow idle threads exit more often and potentially be
|
||||||
Previously, we would tear down a dynamic taskq worker as soon
|
respawned again on demand, causing more churn.
|
||||||
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
|
.El
|
||||||
|
|
|
@ -36,12 +36,12 @@ static int spl_taskq_thread_bind = 0;
|
||||||
module_param(spl_taskq_thread_bind, int, 0644);
|
module_param(spl_taskq_thread_bind, int, 0644);
|
||||||
MODULE_PARM_DESC(spl_taskq_thread_bind, "Bind taskq thread to CPU by default");
|
MODULE_PARM_DESC(spl_taskq_thread_bind, "Bind taskq thread to CPU by default");
|
||||||
|
|
||||||
static uint_t spl_taskq_thread_timeout_ms = 10000;
|
static uint_t spl_taskq_thread_timeout_ms = 5000;
|
||||||
/* BEGIN CSTYLED */
|
/* BEGIN CSTYLED */
|
||||||
module_param(spl_taskq_thread_timeout_ms, uint, 0644);
|
module_param(spl_taskq_thread_timeout_ms, uint, 0644);
|
||||||
/* END CSTYLED */
|
/* END CSTYLED */
|
||||||
MODULE_PARM_DESC(spl_taskq_thread_timeout_ms,
|
MODULE_PARM_DESC(spl_taskq_thread_timeout_ms,
|
||||||
"Time to require a dynamic thread be idle before it gets cleaned up");
|
"Minimum idle threads exit interval for dynamic taskqs");
|
||||||
|
|
||||||
static int spl_taskq_thread_dynamic = 1;
|
static int spl_taskq_thread_dynamic = 1;
|
||||||
module_param(spl_taskq_thread_dynamic, int, 0444);
|
module_param(spl_taskq_thread_dynamic, int, 0444);
|
||||||
|
@ -594,8 +594,7 @@ taskq_dispatch(taskq_t *tq, task_func_t func, void *arg, uint_t flags)
|
||||||
ASSERT(tq->tq_nactive <= tq->tq_nthreads);
|
ASSERT(tq->tq_nactive <= tq->tq_nthreads);
|
||||||
if ((flags & TQ_NOQUEUE) && (tq->tq_nactive == tq->tq_nthreads)) {
|
if ((flags & TQ_NOQUEUE) && (tq->tq_nactive == tq->tq_nthreads)) {
|
||||||
/* Dynamic taskq may be able to spawn another thread */
|
/* Dynamic taskq may be able to spawn another thread */
|
||||||
if (!(tq->tq_flags & TASKQ_DYNAMIC) ||
|
if (taskq_thread_spawn(tq) == 0)
|
||||||
taskq_thread_spawn(tq) == 0)
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,11 +628,11 @@ taskq_dispatch(taskq_t *tq, task_func_t func, void *arg, uint_t flags)
|
||||||
spin_unlock(&t->tqent_lock);
|
spin_unlock(&t->tqent_lock);
|
||||||
|
|
||||||
wake_up(&tq->tq_work_waitq);
|
wake_up(&tq->tq_work_waitq);
|
||||||
out:
|
|
||||||
/* Spawn additional taskq threads if required. */
|
/* Spawn additional taskq threads if required. */
|
||||||
if (!(flags & TQ_NOQUEUE) && tq->tq_nactive == tq->tq_nthreads)
|
if (!(flags & TQ_NOQUEUE) && tq->tq_nactive == tq->tq_nthreads)
|
||||||
(void) taskq_thread_spawn(tq);
|
(void) taskq_thread_spawn(tq);
|
||||||
|
out:
|
||||||
spin_unlock_irqrestore(&tq->tq_lock, irqflags);
|
spin_unlock_irqrestore(&tq->tq_lock, irqflags);
|
||||||
return (rc);
|
return (rc);
|
||||||
}
|
}
|
||||||
|
@ -676,10 +675,11 @@ taskq_dispatch_delay(taskq_t *tq, task_func_t func, void *arg,
|
||||||
ASSERT(!(t->tqent_flags & TQENT_FLAG_PREALLOC));
|
ASSERT(!(t->tqent_flags & TQENT_FLAG_PREALLOC));
|
||||||
|
|
||||||
spin_unlock(&t->tqent_lock);
|
spin_unlock(&t->tqent_lock);
|
||||||
out:
|
|
||||||
/* Spawn additional taskq threads if required. */
|
/* Spawn additional taskq threads if required. */
|
||||||
if (tq->tq_nactive == tq->tq_nthreads)
|
if (tq->tq_nactive == tq->tq_nthreads)
|
||||||
(void) taskq_thread_spawn(tq);
|
(void) taskq_thread_spawn(tq);
|
||||||
|
out:
|
||||||
spin_unlock_irqrestore(&tq->tq_lock, irqflags);
|
spin_unlock_irqrestore(&tq->tq_lock, irqflags);
|
||||||
return (rc);
|
return (rc);
|
||||||
}
|
}
|
||||||
|
@ -704,9 +704,8 @@ taskq_dispatch_ent(taskq_t *tq, task_func_t func, void *arg, uint_t flags,
|
||||||
|
|
||||||
if ((flags & TQ_NOQUEUE) && (tq->tq_nactive == tq->tq_nthreads)) {
|
if ((flags & TQ_NOQUEUE) && (tq->tq_nactive == tq->tq_nthreads)) {
|
||||||
/* Dynamic taskq may be able to spawn another thread */
|
/* Dynamic taskq may be able to spawn another thread */
|
||||||
if (!(tq->tq_flags & TASKQ_DYNAMIC) ||
|
if (taskq_thread_spawn(tq) == 0)
|
||||||
taskq_thread_spawn(tq) == 0)
|
goto out;
|
||||||
goto out2;
|
|
||||||
flags |= TQ_FRONT;
|
flags |= TQ_FRONT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -742,11 +741,11 @@ taskq_dispatch_ent(taskq_t *tq, task_func_t func, void *arg, uint_t flags,
|
||||||
spin_unlock(&t->tqent_lock);
|
spin_unlock(&t->tqent_lock);
|
||||||
|
|
||||||
wake_up(&tq->tq_work_waitq);
|
wake_up(&tq->tq_work_waitq);
|
||||||
out:
|
|
||||||
/* Spawn additional taskq threads if required. */
|
/* Spawn additional taskq threads if required. */
|
||||||
if (tq->tq_nactive == tq->tq_nthreads)
|
if (tq->tq_nactive == tq->tq_nthreads)
|
||||||
(void) taskq_thread_spawn(tq);
|
(void) taskq_thread_spawn(tq);
|
||||||
out2:
|
out:
|
||||||
spin_unlock_irqrestore(&tq->tq_lock, irqflags);
|
spin_unlock_irqrestore(&tq->tq_lock, irqflags);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(taskq_dispatch_ent);
|
EXPORT_SYMBOL(taskq_dispatch_ent);
|
||||||
|
@ -825,6 +824,7 @@ taskq_thread_spawn(taskq_t *tq)
|
||||||
if (!(tq->tq_flags & TASKQ_DYNAMIC))
|
if (!(tq->tq_flags & TASKQ_DYNAMIC))
|
||||||
return (0);
|
return (0);
|
||||||
|
|
||||||
|
tq->lastspawnstop = jiffies;
|
||||||
if ((tq->tq_nthreads + tq->tq_nspawn < tq->tq_maxthreads) &&
|
if ((tq->tq_nthreads + tq->tq_nspawn < tq->tq_maxthreads) &&
|
||||||
(tq->tq_flags & TASKQ_ACTIVE)) {
|
(tq->tq_flags & TASKQ_ACTIVE)) {
|
||||||
spawning = (++tq->tq_nspawn);
|
spawning = (++tq->tq_nspawn);
|
||||||
|
@ -836,9 +836,9 @@ taskq_thread_spawn(taskq_t *tq)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Threads in a dynamic taskq should only exit once it has been completely
|
* Threads in a dynamic taskq may exit once there is no more work to do.
|
||||||
* drained and no other threads are actively servicing tasks. This prevents
|
* To prevent threads from being created and destroyed too often limit
|
||||||
* threads from being created and destroyed more than is required.
|
* the exit rate to one per spl_taskq_thread_timeout_ms.
|
||||||
*
|
*
|
||||||
* The first thread is the thread list is treated as the primary thread.
|
* The first thread is the thread list is treated as the primary thread.
|
||||||
* There is nothing special about the primary thread but in order to avoid
|
* There is nothing special about the primary thread but in order to avoid
|
||||||
|
@ -847,44 +847,22 @@ taskq_thread_spawn(taskq_t *tq)
|
||||||
static int
|
static int
|
||||||
taskq_thread_should_stop(taskq_t *tq, taskq_thread_t *tqt)
|
taskq_thread_should_stop(taskq_t *tq, taskq_thread_t *tqt)
|
||||||
{
|
{
|
||||||
if (!(tq->tq_flags & TASKQ_DYNAMIC))
|
ASSERT(!taskq_next_ent(tq));
|
||||||
|
if (!(tq->tq_flags & TASKQ_DYNAMIC) || !spl_taskq_thread_dynamic)
|
||||||
return (0);
|
return (0);
|
||||||
|
if (!(tq->tq_flags & TASKQ_ACTIVE))
|
||||||
|
return (1);
|
||||||
if (list_first_entry(&(tq->tq_thread_list), taskq_thread_t,
|
if (list_first_entry(&(tq->tq_thread_list), taskq_thread_t,
|
||||||
tqt_thread_list) == tqt)
|
tqt_thread_list) == tqt)
|
||||||
return (0);
|
return (0);
|
||||||
|
ASSERT3U(tq->tq_nthreads, >, 1);
|
||||||
int no_work =
|
if (tq->tq_nspawn != 0)
|
||||||
((tq->tq_nspawn == 0) && /* No threads are being spawned */
|
return (0);
|
||||||
(tq->tq_nactive == 0) && /* No threads are handling tasks */
|
if (time_before(jiffies, tq->lastspawnstop +
|
||||||
(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)))
|
msecs_to_jiffies(spl_taskq_thread_timeout_ms)))
|
||||||
|
return (0);
|
||||||
|
tq->lastspawnstop = jiffies;
|
||||||
return (1);
|
return (1);
|
||||||
else
|
|
||||||
return (0);
|
|
||||||
} else {
|
|
||||||
tq->lastshouldstop = jiffies;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tq->lastshouldstop = 0;
|
|
||||||
}
|
|
||||||
return (0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -935,10 +913,8 @@ taskq_thread(void *args)
|
||||||
if (list_empty(&tq->tq_pend_list) &&
|
if (list_empty(&tq->tq_pend_list) &&
|
||||||
list_empty(&tq->tq_prio_list)) {
|
list_empty(&tq->tq_prio_list)) {
|
||||||
|
|
||||||
if (taskq_thread_should_stop(tq, tqt)) {
|
if (taskq_thread_should_stop(tq, tqt))
|
||||||
wake_up_all(&tq->tq_wait_waitq);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
add_wait_queue_exclusive(&tq->tq_work_waitq, &wait);
|
add_wait_queue_exclusive(&tq->tq_work_waitq, &wait);
|
||||||
spin_unlock_irqrestore(&tq->tq_lock, flags);
|
spin_unlock_irqrestore(&tq->tq_lock, flags);
|
||||||
|
@ -1013,9 +989,6 @@ taskq_thread(void *args)
|
||||||
tqt->tqt_id = TASKQID_INVALID;
|
tqt->tqt_id = TASKQID_INVALID;
|
||||||
tqt->tqt_flags = 0;
|
tqt->tqt_flags = 0;
|
||||||
wake_up_all(&tq->tq_wait_waitq);
|
wake_up_all(&tq->tq_wait_waitq);
|
||||||
} else {
|
|
||||||
if (taskq_thread_should_stop(tq, tqt))
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set_current_state(TASK_INTERRUPTIBLE);
|
set_current_state(TASK_INTERRUPTIBLE);
|
||||||
|
@ -1122,7 +1095,7 @@ taskq_create(const char *name, int threads_arg, pri_t pri,
|
||||||
tq->tq_flags = (flags | TASKQ_ACTIVE);
|
tq->tq_flags = (flags | TASKQ_ACTIVE);
|
||||||
tq->tq_next_id = TASKQID_INITIAL;
|
tq->tq_next_id = TASKQID_INITIAL;
|
||||||
tq->tq_lowest_id = TASKQID_INITIAL;
|
tq->tq_lowest_id = TASKQID_INITIAL;
|
||||||
tq->lastshouldstop = 0;
|
tq->lastspawnstop = jiffies;
|
||||||
INIT_LIST_HEAD(&tq->tq_free_list);
|
INIT_LIST_HEAD(&tq->tq_free_list);
|
||||||
INIT_LIST_HEAD(&tq->tq_pend_list);
|
INIT_LIST_HEAD(&tq->tq_pend_list);
|
||||||
INIT_LIST_HEAD(&tq->tq_prio_list);
|
INIT_LIST_HEAD(&tq->tq_prio_list);
|
||||||
|
|
Loading…
Reference in New Issue