Commit 9391edee authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq

Pull workqueue updates from Tejun Heo:
 "There have been sporadic reports of sanity checks in
  destroy_workqueue() failing spuriously over the years. This contains
  the fix and its follow-up changes / fixes.

  There's also a RCU annotation improvement"

* 'for-5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq:
  workqueue: Add RCU annotation for pwq list walk
  workqueue: Fix pwq ref leak in rescuer_thread()
  workqueue: more destroy_workqueue() fixes
  workqueue: Minor follow-ups to the rescuer destruction change
  workqueue: Fix missing kfree(rescuer) in destroy_workqueue()
  workqueue: Fix spurious sanity check failures in destroy_workqueue()
parents 0acefef5 49e9d1a9
...@@ -248,7 +248,7 @@ struct workqueue_struct { ...@@ -248,7 +248,7 @@ struct workqueue_struct {
struct list_head flusher_overflow; /* WQ: flush overflow list */ struct list_head flusher_overflow; /* WQ: flush overflow list */
struct list_head maydays; /* MD: pwqs requesting rescue */ struct list_head maydays; /* MD: pwqs requesting rescue */
struct worker *rescuer; /* I: rescue worker */ struct worker *rescuer; /* MD: rescue worker */
int nr_drainers; /* WQ: drain in progress */ int nr_drainers; /* WQ: drain in progress */
int saved_max_active; /* WQ: saved pwq max_active */ int saved_max_active; /* WQ: saved pwq max_active */
...@@ -355,6 +355,7 @@ EXPORT_SYMBOL_GPL(system_freezable_power_efficient_wq); ...@@ -355,6 +355,7 @@ EXPORT_SYMBOL_GPL(system_freezable_power_efficient_wq);
static int worker_thread(void *__worker); static int worker_thread(void *__worker);
static void workqueue_sysfs_unregister(struct workqueue_struct *wq); static void workqueue_sysfs_unregister(struct workqueue_struct *wq);
static void show_pwq(struct pool_workqueue *pwq);
#define CREATE_TRACE_POINTS #define CREATE_TRACE_POINTS
#include <trace/events/workqueue.h> #include <trace/events/workqueue.h>
...@@ -425,7 +426,8 @@ static void workqueue_sysfs_unregister(struct workqueue_struct *wq); ...@@ -425,7 +426,8 @@ static void workqueue_sysfs_unregister(struct workqueue_struct *wq);
* ignored. * ignored.
*/ */
#define for_each_pwq(pwq, wq) \ #define for_each_pwq(pwq, wq) \
list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node) \ list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node, \
lockdep_is_held(&wq->mutex)) \
if (({ assert_rcu_or_wq_mutex(wq); false; })) { } \ if (({ assert_rcu_or_wq_mutex(wq); false; })) { } \
else else
...@@ -2532,8 +2534,14 @@ static int rescuer_thread(void *__rescuer) ...@@ -2532,8 +2534,14 @@ static int rescuer_thread(void *__rescuer)
*/ */
if (need_to_create_worker(pool)) { if (need_to_create_worker(pool)) {
spin_lock(&wq_mayday_lock); spin_lock(&wq_mayday_lock);
/*
* Queue iff we aren't racing destruction
* and somebody else hasn't queued it already.
*/
if (wq->rescuer && list_empty(&pwq->mayday_node)) {
get_pwq(pwq); get_pwq(pwq);
list_move_tail(&pwq->mayday_node, &wq->maydays); list_add_tail(&pwq->mayday_node, &wq->maydays);
}
spin_unlock(&wq_mayday_lock); spin_unlock(&wq_mayday_lock);
} }
} }
...@@ -4314,6 +4322,22 @@ struct workqueue_struct *alloc_workqueue(const char *fmt, ...@@ -4314,6 +4322,22 @@ struct workqueue_struct *alloc_workqueue(const char *fmt,
} }
EXPORT_SYMBOL_GPL(alloc_workqueue); EXPORT_SYMBOL_GPL(alloc_workqueue);
static bool pwq_busy(struct pool_workqueue *pwq)
{
int i;
for (i = 0; i < WORK_NR_COLORS; i++)
if (pwq->nr_in_flight[i])
return true;
if ((pwq != pwq->wq->dfl_pwq) && (pwq->refcnt > 1))
return true;
if (pwq->nr_active || !list_empty(&pwq->delayed_works))
return true;
return false;
}
/** /**
* destroy_workqueue - safely terminate a workqueue * destroy_workqueue - safely terminate a workqueue
* @wq: target workqueue * @wq: target workqueue
...@@ -4325,31 +4349,51 @@ void destroy_workqueue(struct workqueue_struct *wq) ...@@ -4325,31 +4349,51 @@ void destroy_workqueue(struct workqueue_struct *wq)
struct pool_workqueue *pwq; struct pool_workqueue *pwq;
int node; int node;
/*
* Remove it from sysfs first so that sanity check failure doesn't
* lead to sysfs name conflicts.
*/
workqueue_sysfs_unregister(wq);
/* drain it before proceeding with destruction */ /* drain it before proceeding with destruction */
drain_workqueue(wq); drain_workqueue(wq);
/* sanity checks */ /* kill rescuer, if sanity checks fail, leave it w/o rescuer */
mutex_lock(&wq->mutex); if (wq->rescuer) {
for_each_pwq(pwq, wq) { struct worker *rescuer = wq->rescuer;
int i;
for (i = 0; i < WORK_NR_COLORS; i++) { /* this prevents new queueing */
if (WARN_ON(pwq->nr_in_flight[i])) { spin_lock_irq(&wq_mayday_lock);
mutex_unlock(&wq->mutex); wq->rescuer = NULL;
show_workqueue_state(); spin_unlock_irq(&wq_mayday_lock);
return;
} /* rescuer will empty maydays list before exiting */
kthread_stop(rescuer->task);
kfree(rescuer);
} }
if (WARN_ON((pwq != wq->dfl_pwq) && (pwq->refcnt > 1)) || /*
WARN_ON(pwq->nr_active) || * Sanity checks - grab all the locks so that we wait for all
WARN_ON(!list_empty(&pwq->delayed_works))) { * in-flight operations which may do put_pwq().
*/
mutex_lock(&wq_pool_mutex);
mutex_lock(&wq->mutex);
for_each_pwq(pwq, wq) {
spin_lock_irq(&pwq->pool->lock);
if (WARN_ON(pwq_busy(pwq))) {
pr_warning("%s: %s has the following busy pwq\n",
__func__, wq->name);
show_pwq(pwq);
spin_unlock_irq(&pwq->pool->lock);
mutex_unlock(&wq->mutex); mutex_unlock(&wq->mutex);
mutex_unlock(&wq_pool_mutex);
show_workqueue_state(); show_workqueue_state();
return; return;
} }
spin_unlock_irq(&pwq->pool->lock);
} }
mutex_unlock(&wq->mutex); mutex_unlock(&wq->mutex);
mutex_unlock(&wq_pool_mutex);
/* /*
* wq list is used to freeze wq, remove from list after * wq list is used to freeze wq, remove from list after
...@@ -4359,11 +4403,6 @@ void destroy_workqueue(struct workqueue_struct *wq) ...@@ -4359,11 +4403,6 @@ void destroy_workqueue(struct workqueue_struct *wq)
list_del_rcu(&wq->list); list_del_rcu(&wq->list);
mutex_unlock(&wq_pool_mutex); mutex_unlock(&wq_pool_mutex);
workqueue_sysfs_unregister(wq);
if (wq->rescuer)
kthread_stop(wq->rescuer->task);
if (!(wq->flags & WQ_UNBOUND)) { if (!(wq->flags & WQ_UNBOUND)) {
wq_unregister_lockdep(wq); wq_unregister_lockdep(wq);
/* /*
...@@ -4638,7 +4677,8 @@ static void show_pwq(struct pool_workqueue *pwq) ...@@ -4638,7 +4677,8 @@ static void show_pwq(struct pool_workqueue *pwq)
pr_info(" pwq %d:", pool->id); pr_info(" pwq %d:", pool->id);
pr_cont_pool_info(pool); pr_cont_pool_info(pool);
pr_cont(" active=%d/%d%s\n", pwq->nr_active, pwq->max_active, pr_cont(" active=%d/%d refcnt=%d%s\n",
pwq->nr_active, pwq->max_active, pwq->refcnt,
!list_empty(&pwq->mayday_node) ? " MAYDAY" : ""); !list_empty(&pwq->mayday_node) ? " MAYDAY" : "");
hash_for_each(pool->busy_hash, bkt, worker, hentry) { hash_for_each(pool->busy_hash, bkt, worker, hentry) {
...@@ -4657,7 +4697,7 @@ static void show_pwq(struct pool_workqueue *pwq) ...@@ -4657,7 +4697,7 @@ static void show_pwq(struct pool_workqueue *pwq)
pr_cont("%s %d%s:%ps", comma ? "," : "", pr_cont("%s %d%s:%ps", comma ? "," : "",
task_pid_nr(worker->task), task_pid_nr(worker->task),
worker == pwq->wq->rescuer ? "(RESCUER)" : "", worker->rescue_wq ? "(RESCUER)" : "",
worker->current_func); worker->current_func);
list_for_each_entry(work, &worker->scheduled, entry) list_for_each_entry(work, &worker->scheduled, entry)
pr_cont_work(false, work); pr_cont_work(false, work);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment