Commit 8864b4e5 authored by Tejun Heo's avatar Tejun Heo

workqueue: implement get/put_pwq()

Add pool_workqueue->refcnt along with get/put_pwq().  Both per-cpu and
unbound pwqs have refcnts and any work item inserted on a pwq
increments the refcnt which is dropped when the work item finishes.

For per-cpu pwqs the base ref is never dropped and destroy_workqueue()
frees the pwqs as before.  For unbound ones, destroy_workqueue()
simply drops the base ref on the first pwq.  When the refcnt reaches
zero, pwq_unbound_release_workfn() is scheduled on system_wq, which
unlinks the pwq, puts the associated pool and frees the pwq and wq as
necessary.  This needs to be done from a work item as put_pwq() needs
to be protected by pool->lock but release can't happen with the lock
held - e.g. put_unbound_pool() involves blocking operations.

Unbound pool->locks are marked with lockdep subclas 1 as put_pwq()
will schedule the release work item on system_wq while holding the
unbound pool's lock and triggers recursive locking warning spuriously.

This will be used to implement dynamic creation and destruction of
unbound pwqs.
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Reviewed-by: default avatarLai Jiangshan <laijs@cn.fujitsu.com>
parent d2c1d404
...@@ -179,6 +179,7 @@ struct pool_workqueue { ...@@ -179,6 +179,7 @@ struct pool_workqueue {
struct workqueue_struct *wq; /* I: the owning workqueue */ struct workqueue_struct *wq; /* I: the owning workqueue */
int work_color; /* L: current color */ int work_color; /* L: current color */
int flush_color; /* L: flushing color */ int flush_color; /* L: flushing color */
int refcnt; /* L: reference count */
int nr_in_flight[WORK_NR_COLORS]; int nr_in_flight[WORK_NR_COLORS];
/* L: nr of in_flight works */ /* L: nr of in_flight works */
int nr_active; /* L: nr of active works */ int nr_active; /* L: nr of active works */
...@@ -186,6 +187,15 @@ struct pool_workqueue { ...@@ -186,6 +187,15 @@ struct pool_workqueue {
struct list_head delayed_works; /* L: delayed works */ struct list_head delayed_works; /* L: delayed works */
struct list_head pwqs_node; /* R: node on wq->pwqs */ struct list_head pwqs_node; /* R: node on wq->pwqs */
struct list_head mayday_node; /* W: node on wq->maydays */ struct list_head mayday_node; /* W: node on wq->maydays */
/*
* Release of unbound pwq is punted to system_wq. See put_pwq()
* and pwq_unbound_release_workfn() for details. pool_workqueue
* itself is also sched-RCU protected so that the first pwq can be
* determined without grabbing workqueue_lock.
*/
struct work_struct unbound_release_work;
struct rcu_head rcu;
} __aligned(1 << WORK_STRUCT_FLAG_BITS); } __aligned(1 << WORK_STRUCT_FLAG_BITS);
/* /*
...@@ -939,6 +949,45 @@ static void move_linked_works(struct work_struct *work, struct list_head *head, ...@@ -939,6 +949,45 @@ static void move_linked_works(struct work_struct *work, struct list_head *head,
*nextp = n; *nextp = n;
} }
/**
* get_pwq - get an extra reference on the specified pool_workqueue
* @pwq: pool_workqueue to get
*
* Obtain an extra reference on @pwq. The caller should guarantee that
* @pwq has positive refcnt and be holding the matching pool->lock.
*/
static void get_pwq(struct pool_workqueue *pwq)
{
lockdep_assert_held(&pwq->pool->lock);
WARN_ON_ONCE(pwq->refcnt <= 0);
pwq->refcnt++;
}
/**
* put_pwq - put a pool_workqueue reference
* @pwq: pool_workqueue to put
*
* Drop a reference of @pwq. If its refcnt reaches zero, schedule its
* destruction. The caller should be holding the matching pool->lock.
*/
static void put_pwq(struct pool_workqueue *pwq)
{
lockdep_assert_held(&pwq->pool->lock);
if (likely(--pwq->refcnt))
return;
if (WARN_ON_ONCE(!(pwq->wq->flags & WQ_UNBOUND)))
return;
/*
* @pwq can't be released under pool->lock, bounce to
* pwq_unbound_release_workfn(). This never recurses on the same
* pool->lock as this path is taken only for unbound workqueues and
* the release work item is scheduled on a per-cpu workqueue. To
* avoid lockdep warning, unbound pool->locks are given lockdep
* subclass of 1 in get_unbound_pool().
*/
schedule_work(&pwq->unbound_release_work);
}
static void pwq_activate_delayed_work(struct work_struct *work) static void pwq_activate_delayed_work(struct work_struct *work)
{ {
struct pool_workqueue *pwq = get_work_pwq(work); struct pool_workqueue *pwq = get_work_pwq(work);
...@@ -970,9 +1019,9 @@ static void pwq_activate_first_delayed(struct pool_workqueue *pwq) ...@@ -970,9 +1019,9 @@ static void pwq_activate_first_delayed(struct pool_workqueue *pwq)
*/ */
static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color) static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color)
{ {
/* ignore uncolored works */ /* uncolored work items don't participate in flushing or nr_active */
if (color == WORK_NO_COLOR) if (color == WORK_NO_COLOR)
return; goto out_put;
pwq->nr_in_flight[color]--; pwq->nr_in_flight[color]--;
...@@ -985,11 +1034,11 @@ static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color) ...@@ -985,11 +1034,11 @@ static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color)
/* is flush in progress and are we at the flushing tip? */ /* is flush in progress and are we at the flushing tip? */
if (likely(pwq->flush_color != color)) if (likely(pwq->flush_color != color))
return; goto out_put;
/* are there still in-flight works? */ /* are there still in-flight works? */
if (pwq->nr_in_flight[color]) if (pwq->nr_in_flight[color])
return; goto out_put;
/* this pwq is done, clear flush_color */ /* this pwq is done, clear flush_color */
pwq->flush_color = -1; pwq->flush_color = -1;
...@@ -1000,6 +1049,8 @@ static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color) ...@@ -1000,6 +1049,8 @@ static void pwq_dec_nr_in_flight(struct pool_workqueue *pwq, int color)
*/ */
if (atomic_dec_and_test(&pwq->wq->nr_pwqs_to_flush)) if (atomic_dec_and_test(&pwq->wq->nr_pwqs_to_flush))
complete(&pwq->wq->first_flusher->done); complete(&pwq->wq->first_flusher->done);
out_put:
put_pwq(pwq);
} }
/** /**
...@@ -1122,6 +1173,7 @@ static void insert_work(struct pool_workqueue *pwq, struct work_struct *work, ...@@ -1122,6 +1173,7 @@ static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
/* we own @work, set data and link */ /* we own @work, set data and link */
set_work_pwq(work, pwq, extra_flags); set_work_pwq(work, pwq, extra_flags);
list_add_tail(&work->entry, head); list_add_tail(&work->entry, head);
get_pwq(pwq);
/* /*
* Ensure either worker_sched_deactivated() sees the above * Ensure either worker_sched_deactivated() sees the above
...@@ -3301,6 +3353,7 @@ static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs) ...@@ -3301,6 +3353,7 @@ static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
if (!pool || init_worker_pool(pool) < 0) if (!pool || init_worker_pool(pool) < 0)
goto fail; goto fail;
lockdep_set_subclass(&pool->lock, 1); /* see put_pwq() */
copy_workqueue_attrs(pool->attrs, attrs); copy_workqueue_attrs(pool->attrs, attrs);
if (worker_pool_assign_id(pool) < 0) if (worker_pool_assign_id(pool) < 0)
...@@ -3329,7 +3382,41 @@ static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs) ...@@ -3329,7 +3382,41 @@ static struct worker_pool *get_unbound_pool(const struct workqueue_attrs *attrs)
return NULL; return NULL;
} }
/* initialize @pwq which interfaces with @pool for @wq and link it in */ static void rcu_free_pwq(struct rcu_head *rcu)
{
kmem_cache_free(pwq_cache,
container_of(rcu, struct pool_workqueue, rcu));
}
/*
* Scheduled on system_wq by put_pwq() when an unbound pwq hits zero refcnt
* and needs to be destroyed.
*/
static void pwq_unbound_release_workfn(struct work_struct *work)
{
struct pool_workqueue *pwq = container_of(work, struct pool_workqueue,
unbound_release_work);
struct workqueue_struct *wq = pwq->wq;
struct worker_pool *pool = pwq->pool;
if (WARN_ON_ONCE(!(wq->flags & WQ_UNBOUND)))
return;
spin_lock_irq(&workqueue_lock);
list_del_rcu(&pwq->pwqs_node);
spin_unlock_irq(&workqueue_lock);
put_unbound_pool(pool);
call_rcu_sched(&pwq->rcu, rcu_free_pwq);
/*
* If we're the last pwq going away, @wq is already dead and no one
* is gonna access it anymore. Free it.
*/
if (list_empty(&wq->pwqs))
kfree(wq);
}
static void init_and_link_pwq(struct pool_workqueue *pwq, static void init_and_link_pwq(struct pool_workqueue *pwq,
struct workqueue_struct *wq, struct workqueue_struct *wq,
struct worker_pool *pool) struct worker_pool *pool)
...@@ -3339,9 +3426,11 @@ static void init_and_link_pwq(struct pool_workqueue *pwq, ...@@ -3339,9 +3426,11 @@ static void init_and_link_pwq(struct pool_workqueue *pwq,
pwq->pool = pool; pwq->pool = pool;
pwq->wq = wq; pwq->wq = wq;
pwq->flush_color = -1; pwq->flush_color = -1;
pwq->refcnt = 1;
pwq->max_active = wq->saved_max_active; pwq->max_active = wq->saved_max_active;
INIT_LIST_HEAD(&pwq->delayed_works); INIT_LIST_HEAD(&pwq->delayed_works);
INIT_LIST_HEAD(&pwq->mayday_node); INIT_LIST_HEAD(&pwq->mayday_node);
INIT_WORK(&pwq->unbound_release_work, pwq_unbound_release_workfn);
list_add_tail_rcu(&pwq->pwqs_node, &wq->pwqs); list_add_tail_rcu(&pwq->pwqs_node, &wq->pwqs);
} }
...@@ -3384,15 +3473,6 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq) ...@@ -3384,15 +3473,6 @@ static int alloc_and_link_pwqs(struct workqueue_struct *wq)
return 0; return 0;
} }
static void free_pwqs(struct workqueue_struct *wq)
{
if (!(wq->flags & WQ_UNBOUND))
free_percpu(wq->cpu_pwqs);
else if (!list_empty(&wq->pwqs))
kmem_cache_free(pwq_cache, list_first_entry(&wq->pwqs,
struct pool_workqueue, pwqs_node));
}
static int wq_clamp_max_active(int max_active, unsigned int flags, static int wq_clamp_max_active(int max_active, unsigned int flags,
const char *name) const char *name)
{ {
...@@ -3524,7 +3604,8 @@ void destroy_workqueue(struct workqueue_struct *wq) ...@@ -3524,7 +3604,8 @@ void destroy_workqueue(struct workqueue_struct *wq)
} }
} }
if (WARN_ON(pwq->nr_active) || if (WARN_ON(pwq->refcnt > 1) ||
WARN_ON(pwq->nr_active) ||
WARN_ON(!list_empty(&pwq->delayed_works))) { WARN_ON(!list_empty(&pwq->delayed_works))) {
spin_unlock_irq(&workqueue_lock); spin_unlock_irq(&workqueue_lock);
return; return;
...@@ -3545,17 +3626,27 @@ void destroy_workqueue(struct workqueue_struct *wq) ...@@ -3545,17 +3626,27 @@ void destroy_workqueue(struct workqueue_struct *wq)
wq->rescuer = NULL; wq->rescuer = NULL;
} }
/* if (!(wq->flags & WQ_UNBOUND)) {
* We're the sole accessor of @wq at this point. Directly access /*
* the first pwq and put its pool. * The base ref is never dropped on per-cpu pwqs. Directly
*/ * free the pwqs and wq.
if (wq->flags & WQ_UNBOUND) { */
free_percpu(wq->cpu_pwqs);
kfree(wq);
} else {
/*
* We're the sole accessor of @wq at this point. Directly
* access the first pwq and put the base ref. As both pwqs
* and pools are sched-RCU protected, the lock operations
* are safe. @wq will be freed when the last pwq is
* released.
*/
pwq = list_first_entry(&wq->pwqs, struct pool_workqueue, pwq = list_first_entry(&wq->pwqs, struct pool_workqueue,
pwqs_node); pwqs_node);
put_unbound_pool(pwq->pool); spin_lock_irq(&pwq->pool->lock);
put_pwq(pwq);
spin_unlock_irq(&pwq->pool->lock);
} }
free_pwqs(wq);
kfree(wq);
} }
EXPORT_SYMBOL_GPL(destroy_workqueue); EXPORT_SYMBOL_GPL(destroy_workqueue);
......
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