All of lore.kernel.org
 help / color / mirror / Atom feed
* workqueue_set_max_active(wq, 0)?
@ 2011-12-09  9:54 Johannes Berg
  2011-12-09 16:57 ` Tejun Heo
  0 siblings, 1 reply; 9+ messages in thread
From: Johannes Berg @ 2011-12-09  9:54 UTC (permalink / raw)
  To: Tejun Heo; +Cc: LKML

Hi,

I know workqueue_set_max_active(wq, 0) isn't allowed, but I was just
playing with making mac80211 completely synchronous in the configuration
path. (The reason is that right now, we have a single-threaded workqueue
and configuration entry points, so it's *almost* single-threaded /
synchronous. This means that the non-synchronous nature rarely gets
tested.)

So I thought I could put a work struct onto the workqueue and then when
it executes it signals the configuration function & waits for that to
finish, etc., a bit like the rendezvous mechanism used to flush works.

But then I thought maybe this should be more generic, like
"workqueue_pause()" / "workqueue_resume()".

And then I found workqueue_set_max_active() but passing 0 isn't allowed.

Before I dive in more deeply I figured I'd ask if you think what a good
way of doing this would be (and whether I'm completely insane) :-)

johannes


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-09  9:54 workqueue_set_max_active(wq, 0)? Johannes Berg
@ 2011-12-09 16:57 ` Tejun Heo
  2011-12-09 16:59   ` Johannes Berg
  2011-12-15 15:38   ` Johannes Berg
  0 siblings, 2 replies; 9+ messages in thread
From: Tejun Heo @ 2011-12-09 16:57 UTC (permalink / raw)
  To: Johannes Berg; +Cc: LKML

Hello, Johannes. :)

On Fri, Dec 09, 2011 at 10:54:42AM +0100, Johannes Berg wrote:
> And then I found workqueue_set_max_active() but passing 0 isn't allowed.
> 
> Before I dive in more deeply I figured I'd ask if you think what a good
> way of doing this would be (and whether I'm completely insane) :-)

Hmmm... yeah, actually, that's what wq uses internally to implement
freezable workqueues.  It sets max_active to 0 temporarily and waits
for all in-flight works to finish.  On thaw, the original value is
restored.

Updating workqueue_set_max_active() to return the old value would be a
nice API update which goes together, I think.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-09 16:57 ` Tejun Heo
@ 2011-12-09 16:59   ` Johannes Berg
  2011-12-15 15:38   ` Johannes Berg
  1 sibling, 0 replies; 9+ messages in thread
From: Johannes Berg @ 2011-12-09 16:59 UTC (permalink / raw)
  To: Tejun Heo; +Cc: LKML

Hi Tejun,

> > Before I dive in more deeply I figured I'd ask if you think what a good
> > way of doing this would be (and whether I'm completely insane) :-)
> 
> Hmmm... yeah, actually, that's what wq uses internally to implement
> freezable workqueues.  It sets max_active to 0 temporarily and waits
> for all in-flight works to finish.  On thaw, the original value is
> restored.

Right, it's a little more complicated though because it has to wait etc.

> Updating workqueue_set_max_active() to return the old value would be a
> nice API update which goes together, I think.

Yeah, that would probably be useful, in particular if one tries to
freeze a paused workqueue...

johannes


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-09 16:57 ` Tejun Heo
  2011-12-09 16:59   ` Johannes Berg
@ 2011-12-15 15:38   ` Johannes Berg
  2011-12-15 18:35     ` Tejun Heo
  1 sibling, 1 reply; 9+ messages in thread
From: Johannes Berg @ 2011-12-15 15:38 UTC (permalink / raw)
  To: Tejun Heo; +Cc: LKML

Hi Tejun,


> > Before I dive in more deeply I figured I'd ask if you think what a good
> > way of doing this would be (and whether I'm completely insane) :-)
> 
> Hmmm... yeah, actually, that's what wq uses internally to implement
> freezable workqueues.  It sets max_active to 0 temporarily and waits
> for all in-flight works to finish.  On thaw, the original value is
> restored.
> 
> Updating workqueue_set_max_active() to return the old value would be a
> nice API update which goes together, I think.

So I looked at set_max_active() but I think it can also be called in
atomic contexts but I would want this to wait I think ... Does the below
look like a reasonable first cut (never mind that it's not really
exported in header files yet, I haven't even tested it yet but the
interaction with other code is interesting)

johannes


---
 kernel/workqueue.c |   68 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 66 insertions(+), 2 deletions(-)

--- a/kernel/workqueue.c	2011-12-10 17:32:26.000000000 +0100
+++ b/kernel/workqueue.c	2011-12-15 16:36:06.000000000 +0100
@@ -51,6 +51,7 @@ enum {
 	GCWQ_DISASSOCIATED	= 1 << 2,	/* cpu can't serve workers */
 	GCWQ_FREEZING		= 1 << 3,	/* freeze in progress */
 	GCWQ_HIGHPRI_PENDING	= 1 << 4,	/* highpri works on queue */
+	GCWQ_PAUSING		= 1 << 5,
 
 	/* worker flags */
 	WORKER_STARTED		= 1 << 0,	/* started */
@@ -246,6 +247,7 @@ struct workqueue_struct {
 #ifdef CONFIG_LOCKDEP
 	struct lockdep_map	lockdep_map;
 #endif
+	wait_queue_head_t	waitq;
 };
 
 struct workqueue_struct *system_wq __read_mostly;
@@ -1759,6 +1761,8 @@ static void cwq_dec_nr_in_flight(struct
 			if (cwq->nr_active < cwq->max_active)
 				cwq_activate_first_delayed(cwq);
 		}
+		if (cwq->nr_active == 0 && cwq->max_active == 0)
+			wake_up(&cwq->wq->waitq);
 	}
 
 	/* is flush in progress and are we at the flushing tip? */
@@ -2990,6 +2994,7 @@ struct workqueue_struct *__alloc_workque
 	atomic_set(&wq->nr_cwqs_to_flush, 0);
 	INIT_LIST_HEAD(&wq->flusher_queue);
 	INIT_LIST_HEAD(&wq->flusher_overflow);
+	init_waitqueue_head(&wq->waitq);
 
 	wq->name = name;
 	lockdep_init_map(&wq->lockdep_map, lock_name, key, 0);
@@ -3124,7 +3129,7 @@ void workqueue_set_max_active(struct wor
 		spin_lock_irq(&gcwq->lock);
 
 		if (!(wq->flags & WQ_FREEZABLE) ||
-		    !(gcwq->flags & GCWQ_FREEZING))
+		    !(gcwq->flags & (GCWQ_FREEZING | GCWQ_PAUSING)))
 			get_cwq(gcwq->cpu, wq)->max_active = max_active;
 
 		spin_unlock_irq(&gcwq->lock);
@@ -3377,7 +3382,7 @@ static int __cpuinit trustee_thread(void
 	 * completion while frozen.
 	 */
 	while (gcwq->nr_workers != gcwq->nr_idle ||
-	       gcwq->flags & GCWQ_FREEZING ||
+	       gcwq->flags & (GCWQ_FREEZING | GCWQ_PAUSING) ||
 	       gcwq->trustee_state == TRUSTEE_IN_CHARGE) {
 		int nr_works = 0;
 
@@ -3767,6 +3772,65 @@ out_unlock:
 }
 #endif /* CONFIG_FREEZER */
 
+static unsigned int count_active(struct workqueue_struct *wq)
+{
+	unsigned int cpu, active = 0;
+
+	for_each_cwq_cpu(cpu, wq) {
+		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
+		struct global_cwq *gcwq = cwq->gcwq;
+
+		spin_lock_irq(&gcwq->lock);
+		active += cwq->nr_active;
+		spin_unlock_irq(&gcwq->lock);
+	}
+
+	return active;
+}
+
+void pause_workqueue(struct workqueue_struct *wq)
+{
+	unsigned int cpu;
+
+	for_each_cwq_cpu(cpu, wq) {
+		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
+		struct global_cwq *gcwq = cwq->gcwq;
+
+		spin_lock_irq(&gcwq->lock);
+
+		WARN_ON(gcwq->flags & GCWQ_PAUSING);
+		gcwq->flags |= GCWQ_PAUSING;
+
+		cwq->max_active = 0;
+
+		spin_unlock_irq(&gcwq->lock);
+	}
+
+	wait_event(wq->waitq, count_active(wq) == 0);
+}
+EXPORT_SYMBOL(pause_workqueue);
+
+void resume_workqueue(struct workqueue_struct *wq)
+{
+	unsigned int cpu;
+
+	for_each_cwq_cpu(cpu, wq) {
+		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
+		struct global_cwq *gcwq = cwq->gcwq;
+
+		spin_lock_irq(&gcwq->lock);
+
+		WARN_ON(!(gcwq->flags & GCWQ_PAUSING));
+		gcwq->flags &= ~GCWQ_PAUSING;
+
+		cwq->max_active = wq->saved_max_active;
+
+		wake_up_worker(gcwq);
+		spin_unlock_irq(&gcwq->lock);
+	}
+}
+EXPORT_SYMBOL(resume_workqueue);
+
 static int __init init_workqueues(void)
 {
 	unsigned int cpu;



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-15 15:38   ` Johannes Berg
@ 2011-12-15 18:35     ` Tejun Heo
  2011-12-15 18:43       ` Johannes Berg
  0 siblings, 1 reply; 9+ messages in thread
From: Tejun Heo @ 2011-12-15 18:35 UTC (permalink / raw)
  To: Johannes Berg; +Cc: LKML

Hello, Johannes.

On Thu, Dec 15, 2011 at 04:38:12PM +0100, Johannes Berg wrote:
> --- a/kernel/workqueue.c	2011-12-10 17:32:26.000000000 +0100
> +++ b/kernel/workqueue.c	2011-12-15 16:36:06.000000000 +0100
> @@ -51,6 +51,7 @@ enum {
>  	GCWQ_DISASSOCIATED	= 1 << 2,	/* cpu can't serve workers */
>  	GCWQ_FREEZING		= 1 << 3,	/* freeze in progress */
>  	GCWQ_HIGHPRI_PENDING	= 1 << 4,	/* highpri works on queue */
> +	GCWQ_PAUSING		= 1 << 5,

Hmmm... confused.  Pausing is per-wq, why is this flag on gcwq?
Shouldn't it be on workqueue_struct?

> +void pause_workqueue(struct workqueue_struct *wq)
> +{
> +	unsigned int cpu;
> +
> +	for_each_cwq_cpu(cpu, wq) {
> +		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
> +		struct global_cwq *gcwq = cwq->gcwq;
> +
> +		spin_lock_irq(&gcwq->lock);
> +
> +		WARN_ON(gcwq->flags & GCWQ_PAUSING);
> +		gcwq->flags |= GCWQ_PAUSING;
> +
> +		cwq->max_active = 0;
> +
> +		spin_unlock_irq(&gcwq->lock);
> +	}
> +
> +	wait_event(wq->waitq, count_active(wq) == 0);
> +}
> +EXPORT_SYMBOL(pause_workqueue);

What if there are multiple callers of this function on the same wq?
Maybe something like wq->pause_depth and also use it from freeze path?

> +void resume_workqueue(struct workqueue_struct *wq)
> +{
> +	unsigned int cpu;
> +
> +	for_each_cwq_cpu(cpu, wq) {
> +		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
> +		struct global_cwq *gcwq = cwq->gcwq;
> +
> +		spin_lock_irq(&gcwq->lock);
> +
> +		WARN_ON(!(gcwq->flags & GCWQ_PAUSING));
> +		gcwq->flags &= ~GCWQ_PAUSING;
> +
> +		cwq->max_active = wq->saved_max_active;
> +
> +		wake_up_worker(gcwq);
> +		spin_unlock_irq(&gcwq->lock);
> +	}
> +}
> +EXPORT_SYMBOL(resume_workqueue);

I don't think wake_up_worker() would be sufficient.
cwq_activate_first_delayed() needs to be called to kick the delayed
work items.

I think it would be great if this can be abstracted out so that both
the freezer and explicit pausing use the same facility.  They aren't
that different after all.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-15 18:35     ` Tejun Heo
@ 2011-12-15 18:43       ` Johannes Berg
  2011-12-15 19:12         ` Tejun Heo
  0 siblings, 1 reply; 9+ messages in thread
From: Johannes Berg @ 2011-12-15 18:43 UTC (permalink / raw)
  To: Tejun Heo; +Cc: LKML

Hi,

> > @@ -51,6 +51,7 @@ enum {
> >  	GCWQ_DISASSOCIATED	= 1 << 2,	/* cpu can't serve workers */
> >  	GCWQ_FREEZING		= 1 << 3,	/* freeze in progress */
> >  	GCWQ_HIGHPRI_PENDING	= 1 << 4,	/* highpri works on queue */
> > +	GCWQ_PAUSING		= 1 << 5,
> 
> Hmmm... confused.  Pausing is per-wq, why is this flag on gcwq?
> Shouldn't it be on workqueue_struct?

Hm, no idea :-)
I just copied FREEZING really without knowing what I was doing. I'm not
very familiar with this code (yet).

> > +void pause_workqueue(struct workqueue_struct *wq)
> > +{
> > +	unsigned int cpu;
> > +
> > +	for_each_cwq_cpu(cpu, wq) {
> > +		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
> > +		struct global_cwq *gcwq = cwq->gcwq;
> > +
> > +		spin_lock_irq(&gcwq->lock);
> > +
> > +		WARN_ON(gcwq->flags & GCWQ_PAUSING);
> > +		gcwq->flags |= GCWQ_PAUSING;
> > +
> > +		cwq->max_active = 0;
> > +
> > +		spin_unlock_irq(&gcwq->lock);
> > +	}
> > +
> > +	wait_event(wq->waitq, count_active(wq) == 0);
> > +}
> > +EXPORT_SYMBOL(pause_workqueue);
> 
> What if there are multiple callers of this function on the same wq?
> Maybe something like wq->pause_depth and also use it from freeze path?

Hm, good point. We can't abstract out all of it -- the freezer API
doesn't want to wait for it to finish -- but probably a bit of it.

How do you iterate workqueues? We'd have to do that for the freezer
part, unless we want to work on CWQs again.

Actually I'm not really sure I understand the differences between WQ,
CWQ and GCWQ...

> > +void resume_workqueue(struct workqueue_struct *wq)
> > +{
> > +	unsigned int cpu;
> > +
> > +	for_each_cwq_cpu(cpu, wq) {
> > +		struct cpu_workqueue_struct *cwq = get_cwq(cpu, wq);
> > +		struct global_cwq *gcwq = cwq->gcwq;
> > +
> > +		spin_lock_irq(&gcwq->lock);
> > +
> > +		WARN_ON(!(gcwq->flags & GCWQ_PAUSING));
> > +		gcwq->flags &= ~GCWQ_PAUSING;
> > +
> > +		cwq->max_active = wq->saved_max_active;
> > +
> > +		wake_up_worker(gcwq);
> > +		spin_unlock_irq(&gcwq->lock);
> > +	}
> > +}
> > +EXPORT_SYMBOL(resume_workqueue);
> 
> I don't think wake_up_worker() would be sufficient.
> cwq_activate_first_delayed() needs to be called to kick the delayed
> work items.

Hm, ok.

> I think it would be great if this can be abstracted out so that both
> the freezer and explicit pausing use the same facility.  They aren't
> that different after all.

I'll take a look tomorrow. If you want to beat me to it ... ;-)

johannes


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-15 18:43       ` Johannes Berg
@ 2011-12-15 19:12         ` Tejun Heo
  2011-12-15 19:26           ` Johannes Berg
  0 siblings, 1 reply; 9+ messages in thread
From: Tejun Heo @ 2011-12-15 19:12 UTC (permalink / raw)
  To: Johannes Berg; +Cc: LKML

Hello,

On Thu, Dec 15, 2011 at 07:43:40PM +0100, Johannes Berg wrote:
> Hm, good point. We can't abstract out all of it -- the freezer API
> doesn't want to wait for it to finish -- but probably a bit of it.
> 
> How do you iterate workqueues? We'd have to do that for the freezer
> part, unless we want to work on CWQs again.

By Locking workqueue_lock and walking workqueues list.  Hmmm...

> Actually I'm not really sure I understand the differences between WQ,
> CWQ and GCWQ...

WQ is workqueue - the part visible to users.

CWQ is cpu workqueue.  Each wq has its own set of cpu workqueues for
all CPUs (there are exceptions but this should be a good enough
explanation).  A WQ is always a set of cwq's.  WQ chooses which CWQ to
use on queue but most of actual processing happens on CWQs.

GCWQ stands for global cpu workqueue - there's one for each CPU.  This
is per-cpu global worker pool used by all workqueues.  Every CWQ on a
CPU shares the GCWQ on that CPU.

The reason why FREEZING currently is on GCWQ is because freezing is a
system wide operation.  If we're gonna implement pause, I think it
should probably be in cwq.

> > I think it would be great if this can be abstracted out so that both
> > the freezer and explicit pausing use the same facility.  They aren't
> > that different after all.
> 
> I'll take a look tomorrow. If you want to beat me to it ... ;-)

Heh heh, [un]fortunately, I'm pretty occupied at the moment.  :P

Thank you.

-- 
tejun

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-15 19:12         ` Tejun Heo
@ 2011-12-15 19:26           ` Johannes Berg
  2011-12-15 19:31             ` Tejun Heo
  0 siblings, 1 reply; 9+ messages in thread
From: Johannes Berg @ 2011-12-15 19:26 UTC (permalink / raw)
  To: Tejun Heo; +Cc: LKML

On Thu, 2011-12-15 at 11:12 -0800, Tejun Heo wrote:

> > Hm, good point. We can't abstract out all of it -- the freezer API
> > doesn't want to wait for it to finish -- but probably a bit of it.
> > 
> > How do you iterate workqueues? We'd have to do that for the freezer
> > part, unless we want to work on CWQs again.
> 
> By Locking workqueue_lock and walking workqueues list.  Hmmm...

Ah. So fundamentally, the freeze code does:

 * set each gcwq frozen
 * set max_active=0 for each CWQ in each WQ

but it interleaves the two loops. I guess this would have to be
untangled if we want to share it so it sets all gcwq frozen and then
iterates the workqueues and their CWQs. Locking seems a bit hairy
though, why does the current code keep the GCWQ lock over CWQ changes? I
guess that's so nothing can work on the CWQ?

> > Actually I'm not really sure I understand the differences between WQ,
> > CWQ and GCWQ...
> 
[snip explanation]

thanks.

> The reason why FREEZING currently is on GCWQ is because freezing is a
> system wide operation.  If we're gonna implement pause, I think it
> should probably be in cwq.

Ok, makes sense too.

I think I'm going to do something simpler first though, the locking
scares me a bit. I'll do something for my single-threaded max-active=1
workqueue first directly in mac80211 to try out the idea ...

johannes


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: workqueue_set_max_active(wq, 0)?
  2011-12-15 19:26           ` Johannes Berg
@ 2011-12-15 19:31             ` Tejun Heo
  0 siblings, 0 replies; 9+ messages in thread
From: Tejun Heo @ 2011-12-15 19:31 UTC (permalink / raw)
  To: Johannes Berg; +Cc: LKML

Hello,

On Thu, Dec 15, 2011 at 08:26:01PM +0100, Johannes Berg wrote:
> Ah. So fundamentally, the freeze code does:
> 
>  * set each gcwq frozen
>  * set max_active=0 for each CWQ in each WQ

Yeap and then iterate over them waiting for all nr_actives to drop to
zero.

> but it interleaves the two loops. I guess this would have to be
> untangled if we want to share it so it sets all gcwq frozen and then
> iterates the workqueues and their CWQs. Locking seems a bit hairy
> though, why does the current code keep the GCWQ lock over CWQ changes? I
> guess that's so nothing can work on the CWQ?

All CWQ's are protected by the corresponding GCWQ lock, so all CWQs on
the same CPU are protected by single gcwq->lock on that CPU.  It's
actually rather simple.  The reason the loop there is interleaved is
to avoid releasing and grabbing different gcwq->lock's for each
iteration.  I don't think that would really matter either way.

Thanks.

-- 
tejun

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2011-12-15 19:31 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-12-09  9:54 workqueue_set_max_active(wq, 0)? Johannes Berg
2011-12-09 16:57 ` Tejun Heo
2011-12-09 16:59   ` Johannes Berg
2011-12-15 15:38   ` Johannes Berg
2011-12-15 18:35     ` Tejun Heo
2011-12-15 18:43       ` Johannes Berg
2011-12-15 19:12         ` Tejun Heo
2011-12-15 19:26           ` Johannes Berg
2011-12-15 19:31             ` Tejun Heo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.