Skip to content

Commit

Permalink
Merge branch 'for-5.5' of git://git.kernel.org/pub/scm/linux/kernel/g…
Browse files Browse the repository at this point in the history
…it/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()
  • Loading branch information
torvalds committed Nov 26, 2019
2 parents 0acefef + 49e9d1a commit 9391ede
Showing 1 changed file with 65 additions and 25 deletions.
90 changes: 65 additions & 25 deletions kernel/workqueue.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ struct workqueue_struct {
struct list_head flusher_overflow; /* WQ: flush overflow list */

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 saved_max_active; /* WQ: saved pwq max_active */
Expand Down Expand Up @@ -355,6 +355,7 @@ EXPORT_SYMBOL_GPL(system_freezable_power_efficient_wq);

static int worker_thread(void *__worker);
static void workqueue_sysfs_unregister(struct workqueue_struct *wq);
static void show_pwq(struct pool_workqueue *pwq);

#define CREATE_TRACE_POINTS
#include <trace/events/workqueue.h>
Expand Down Expand Up @@ -425,7 +426,8 @@ static void workqueue_sysfs_unregister(struct workqueue_struct *wq);
* ignored.
*/
#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; })) { } \
else

Expand Down Expand Up @@ -2532,8 +2534,14 @@ static int rescuer_thread(void *__rescuer)
*/
if (need_to_create_worker(pool)) {
spin_lock(&wq_mayday_lock);
get_pwq(pwq);
list_move_tail(&pwq->mayday_node, &wq->maydays);
/*
* 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);
list_add_tail(&pwq->mayday_node, &wq->maydays);
}
spin_unlock(&wq_mayday_lock);
}
}
Expand Down Expand Up @@ -4314,6 +4322,22 @@ struct workqueue_struct *alloc_workqueue(const char *fmt,
}
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
* @wq: target workqueue
Expand All @@ -4325,31 +4349,51 @@ void destroy_workqueue(struct workqueue_struct *wq)
struct pool_workqueue *pwq;
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_workqueue(wq);

/* sanity checks */
mutex_lock(&wq->mutex);
for_each_pwq(pwq, wq) {
int i;
/* kill rescuer, if sanity checks fail, leave it w/o rescuer */
if (wq->rescuer) {
struct worker *rescuer = wq->rescuer;

for (i = 0; i < WORK_NR_COLORS; i++) {
if (WARN_ON(pwq->nr_in_flight[i])) {
mutex_unlock(&wq->mutex);
show_workqueue_state();
return;
}
}
/* this prevents new queueing */
spin_lock_irq(&wq_mayday_lock);
wq->rescuer = NULL;
spin_unlock_irq(&wq_mayday_lock);

/* 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) ||
WARN_ON(!list_empty(&pwq->delayed_works))) {
/*
* Sanity checks - grab all the locks so that we wait for all
* 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_pool_mutex);
show_workqueue_state();
return;
}
spin_unlock_irq(&pwq->pool->lock);
}
mutex_unlock(&wq->mutex);
mutex_unlock(&wq_pool_mutex);

/*
* wq list is used to freeze wq, remove from list after
Expand All @@ -4359,11 +4403,6 @@ void destroy_workqueue(struct workqueue_struct *wq)
list_del_rcu(&wq->list);
mutex_unlock(&wq_pool_mutex);

workqueue_sysfs_unregister(wq);

if (wq->rescuer)
kthread_stop(wq->rescuer->task);

if (!(wq->flags & WQ_UNBOUND)) {
wq_unregister_lockdep(wq);
/*
Expand Down Expand Up @@ -4638,7 +4677,8 @@ static void show_pwq(struct pool_workqueue *pwq)
pr_info(" pwq %d:", pool->id);
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" : "");

hash_for_each(pool->busy_hash, bkt, worker, hentry) {
Expand All @@ -4657,7 +4697,7 @@ static void show_pwq(struct pool_workqueue *pwq)

pr_cont("%s %d%s:%ps", comma ? "," : "",
task_pid_nr(worker->task),
worker == pwq->wq->rescuer ? "(RESCUER)" : "",
worker->rescue_wq ? "(RESCUER)" : "",
worker->current_func);
list_for_each_entry(work, &worker->scheduled, entry)
pr_cont_work(false, work);
Expand Down

0 comments on commit 9391ede

Please sign in to comment.