Skip to contentSkip to conversation

workqueue: relax lockdep annotation on flush_work()

PATCH In review Tejun Heo 3 participants 11 messages Dec 29, 2010 – Jan 9, 2011

Subsystems (2)
lore
Share & tools
lore

Tejun Heo

author maintainer
#0 lore raw

PATCH 1/1 workqueue: relax lockdep annotation on flush_work()

Currently, the lockdep annotation in flush_work() requires exclusive access on the workqueue the target work is queued on and triggers warning if a work is trying to flush another work on the same workqueue; however, this is no longer true as workqueues can now execute multiple works concurrently.

This patch adds lock_map_acquire_read() and make process_one_work() hold read access to the workqueue while executing a work and start_flush_work() check for write access if concurrnecy level is one and read access if higher.

This better represents what's going on and removes spurious lockdep warnings which are triggered by fake dependency chain created through flush_work().

---
How should this one be routed? The lockdep part can be split, merged back into workqueue tree and so on but that seems a bit too much. If it's okay, I'll route this through the workqueue tree. Going through the lockdep tree is fine too.

Thanks.

 include/linux/lockdep.h |    3 +++
 kernel/workqueue.c      |    8 ++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)
include/linux/lockdep.h +3 -0
index 71c09b2..9f19430 100644
@@ -522,12 +522,15 @@ static inline void print_irqtrace_events(struct task_struct *curr)
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 # ifdef CONFIG_PROVE_LOCKING
 #  define lock_map_acquire(l)		lock_acquire(l, 0, 0, 0, 2, NULL, _THIS_IP_)
+#  define lock_map_acquire_read(l)	lock_acquire(l, 0, 0, 2, 2, NULL, _THIS_IP_)
 # else
 #  define lock_map_acquire(l)		lock_acquire(l, 0, 0, 0, 1, NULL, _THIS_IP_)
+#  define lock_map_acquire_read(l)	lock_acquire(l, 0, 0, 2, 1, NULL, _THIS_IP_)
 # endif
 # define lock_map_release(l)			lock_release(l, 1, _THIS_IP_)
 #else
 # define lock_map_acquire(l)			do { } while (0)
+# define lock_map_acquire_read(l)		do { } while (0)
 # define lock_map_release(l)			do { } while (0)
 #endif
 
kernel/workqueue.c +6 -2
index 8ee6ec8..85f8f7b 100644
@@ -1840,7 +1840,7 @@ __acquires(&gcwq->lock)
 	spin_unlock_irq(&gcwq->lock);
 
 	work_clear_pending(work);
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_acquire(&lockdep_map);
 	trace_workqueue_execute_start(work);
 	f(work);
@@ -2384,8 +2384,12 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
 	insert_wq_barrier(cwq, barr, work, worker);
 	spin_unlock_irq(&gcwq->lock);
 
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	if (cwq->wq->saved_max_active > 1)
+		lock_map_acquire_read(&cwq->wq->lockdep_map);
+	else
+		lock_map_acquire(&cwq->wq->lockdep_map);
 	lock_map_release(&cwq->wq->lockdep_map);
+
 	return true;
 already_gone:
 	spin_unlock_irq(&gcwq->lock);
#1 lore raw
Quoted 6 lines · Tejun Heo

Currently, the lockdep annotation in flush_work() requires exclusive access on the workqueue the target work is queued on and triggers warning if a work is trying to flush another work on the same workqueue; however, this is no longer true as workqueues can now execute multiple works concurrently.

Quoted 8 lines

This patch adds lock_map_acquire_read() and make process_one_work() hold read access to the workqueue while executing a work and start_flush_work() check for write access if concurrnecy level is one and read access if higher.

This better represents what's going on and removes spurious lockdep warnings which are triggered by fake dependency chain created through flush_work().

The spurious lockdep warning I've been observing is not printed any more with the patch applied.

Thanks,
Rafael

Quoted 61 lines

---
How should this one be routed? The lockdep part can be split, merged back into workqueue tree and so on but that seems a bit too much. If it's okay, I'll route this through the workqueue tree. Going through the lockdep tree is fine too.

Thanks.

 include/linux/lockdep.h |    3 +++
 kernel/workqueue.c      |    8 ++++++--
 2 files changed, 9 insertions(+), 2 deletions(-)
include/linux/lockdep.h +3 -0
index 71c09b2..9f19430 100644
@@ -522,12 +522,15 @@ static inline void print_irqtrace_events(struct task_struct *curr)
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 # ifdef CONFIG_PROVE_LOCKING
 #  define lock_map_acquire(l)		lock_acquire(l, 0, 0, 0, 2, NULL, _THIS_IP_)
+#  define lock_map_acquire_read(l)	lock_acquire(l, 0, 0, 2, 2, NULL, _THIS_IP_)
 # else
 #  define lock_map_acquire(l)		lock_acquire(l, 0, 0, 0, 1, NULL, _THIS_IP_)
+#  define lock_map_acquire_read(l)	lock_acquire(l, 0, 0, 2, 1, NULL, _THIS_IP_)
 # endif
 # define lock_map_release(l)			lock_release(l, 1, _THIS_IP_)
 #else
 # define lock_map_acquire(l)			do { } while (0)
+# define lock_map_acquire_read(l)		do { } while (0)
 # define lock_map_release(l)			do { } while (0)
 #endif
 
kernel/workqueue.c +6 -2
index 8ee6ec8..85f8f7b 100644
@@ -1840,7 +1840,7 @@ __acquires(&gcwq->lock)
 	spin_unlock_irq(&gcwq->lock);
 
 	work_clear_pending(work);
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_acquire(&lockdep_map);
 	trace_workqueue_execute_start(work);
 	f(work);
@@ -2384,8 +2384,12 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
 	insert_wq_barrier(cwq, barr, work, worker);
 	spin_unlock_irq(&gcwq->lock);
 
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	if (cwq->wq->saved_max_active > 1)
+		lock_map_acquire_read(&cwq->wq->lockdep_map);
+	else
+		lock_map_acquire(&cwq->wq->lockdep_map);
 	lock_map_release(&cwq->wq->lockdep_map);
+
 	return true;
 already_gone:
 	spin_unlock_irq(&gcwq->lock);
maintainer
#2 lore raw
In reply to Tejun Heo
Quoted 6 lines · Tejun Heo

Currently, the lockdep annotation in flush_work() requires exclusive access on the workqueue the target work is queued on and triggers warning if a work is trying to flush another work on the same workqueue; however, this is no longer true as workqueues can now execute multiple works concurrently.

Quoted 8 lines

This patch adds lock_map_acquire_read() and make process_one_work() hold read access to the workqueue while executing a work and start_flush_work() check for write access if concurrnecy level is one and read access if higher.

This better represents what's going on and removes spurious lockdep warnings which are triggered by fake dependency chain created through flush_work().

Is this still true in the low memory situation where we're running the emergency thread? I can imagine the emergency thread trying to flush itself isn't really a good thing.

author maintainer
#3 lore raw

PATCH 1/1 workqueue: relax lockdep annotation on flush_work()

Currently, the lockdep annotation in flush_work() requires exclusive access on the workqueue the target work is queued on and triggers warning if a work is trying to flush another work on the same workqueue; however, this is no longer true as workqueues can now execute multiple works concurrently.

This patch adds lock_map_acquire_read() and make process_one_work() hold read access to the workqueue while executing a work and start_flush_work() check for write access if concurrnecy level is one or the workqueue has a rescuer (as only one execution resource - the rescuer - is guaranteed to be available under memory pressure), and read access if higher.

This better represents what's going on and removes spurious lockdep warnings which are triggered by fake dependency chain created through flush_work().

* Peter pointed out that flushing another work from a WQ_MEM_RECLAIM wq breaks forward progress guarantee under memory pressure. Condition check accordingly updated.

---
Ah... good point. How does this one look then?

Thanks.

 include/linux/lockdep.h |    3 +++
 kernel/workqueue.c      |   14 ++++++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)
include/linux/lockdep.h +3 -0
index 71c09b2..9f19430 100644
@@ -522,12 +522,15 @@ static inline void print_irqtrace_events(struct task_struct *curr)
 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 # ifdef CONFIG_PROVE_LOCKING
 #  define lock_map_acquire(l)		lock_acquire(l, 0, 0, 0, 2, NULL, _THIS_IP_)
+#  define lock_map_acquire_read(l)	lock_acquire(l, 0, 0, 2, 2, NULL, _THIS_IP_)
 # else
 #  define lock_map_acquire(l)		lock_acquire(l, 0, 0, 0, 1, NULL, _THIS_IP_)
+#  define lock_map_acquire_read(l)	lock_acquire(l, 0, 0, 2, 1, NULL, _THIS_IP_)
 # endif
 # define lock_map_release(l)			lock_release(l, 1, _THIS_IP_)
 #else
 # define lock_map_acquire(l)			do { } while (0)
+# define lock_map_acquire_read(l)		do { } while (0)
 # define lock_map_release(l)			do { } while (0)
 #endif
 
kernel/workqueue.c +12 -2
index 8ee6ec8..930c239 100644
@@ -1840,7 +1840,7 @@ __acquires(&gcwq->lock)
 	spin_unlock_irq(&gcwq->lock);
 
 	work_clear_pending(work);
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_acquire(&lockdep_map);
 	trace_workqueue_execute_start(work);
 	f(work);
@@ -2384,8 +2384,18 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
 	insert_wq_barrier(cwq, barr, work, worker);
 	spin_unlock_irq(&gcwq->lock);
 
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	/*
+	 * If @max_active is 1 or rescuer is in use, flushing another work
+	 * item on the same workqueue may lead to deadlock.  Make sure the
+	 * flusher is not running on the same workqueue by verifying write
+	 * access.
+	 */
+	if (cwq->wq->saved_max_active == 1 || cwq->wq->flags & WQ_RESCUER)
+		lock_map_acquire(&cwq->wq->lockdep_map);
+	else
+		lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_release(&cwq->wq->lockdep_map);
+
 	return true;
 already_gone:
 	spin_unlock_irq(&gcwq->lock);
maintainer
#4 lore raw
Quoted 10 lines · Tejun Heo
kernel/workqueue.c +5 -1
@@ -2384,8 +2384,18 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
 	insert_wq_barrier(cwq, barr, work, worker);
 	spin_unlock_irq(&gcwq->lock);
 
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	/*
+	 * If @max_active is 1 or rescuer is in use, flushing another work
+	 * item on the same workqueue may lead to deadlock.  Make sure the
+	 * flusher is not running on the same workqueue by verifying write
+	 * access.

kernel/workqueue.c static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr, · line 2401

Quoted 10 lines
kernel/workqueue.c +6 -0
+	 */
+	if (cwq->wq->saved_max_active == 1 || cwq->wq->flags & WQ_RESCUER)
+		lock_map_acquire(&cwq->wq->lockdep_map);
+	else
+		lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_release(&cwq->wq->lockdep_map);
+
 	return true;
 already_gone:
 	spin_unlock_irq(&gcwq->lock);

Ah, but this violates the rule that you must always use the most strict constraints. Code doesn't know if it will run in a rescue thread or not, hence it must assume it does.

author maintainer
#5 lore raw

Hello,

Quoted 18 lines · Peter Zijlstra
Quoted 16 lines · Tejun Heo
kernel/workqueue.c +10 -1
@@ -2384,8 +2384,18 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
 	insert_wq_barrier(cwq, barr, work, worker);
 	spin_unlock_irq(&gcwq->lock);
 
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	/*
+	 * If @max_active is 1 or rescuer is in use, flushing another work
+	 * item on the same workqueue may lead to deadlock.  Make sure the
+	 * flusher is not running on the same workqueue by verifying write
+	 * access.
+	 */
+	if (cwq->wq->saved_max_active == 1 || cwq->wq->flags & WQ_RESCUER)
+		lock_map_acquire(&cwq->wq->lockdep_map);
+	else
+		lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_release(&cwq->wq->lockdep_map);
Quoted 8 lines

kernel/workqueue.c static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr, · line 2401

Quoted 4 lines
kernel/workqueue.c +1 -0
+
 	return true;
 already_gone:
 	spin_unlock_irq(&gcwq->lock);

Ah, but this violates the rule that you must always use the most strict constraints. Code doesn't know if it will run in a rescue thread or not, hence it must assume it does.

Hmmm? The code applies the most strict contraints. If the workqueue has a rescuer, flushing another work from the workqueue will always trigger lockdep warning. The rule is relaxed only for workqueues which aren't used for memory reclaiming && support parallel execution.

Thanks.

Signature · 1 line
tejun
maintainer
#6 lore raw
Quoted 30 lines · Tejun Heo

Hello,

Quoted 18 lines · Peter Zijlstra
Quoted 16 lines · Tejun Heo
kernel/workqueue.c +10 -1
@@ -2384,8 +2384,18 @@ static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr,
 	insert_wq_barrier(cwq, barr, work, worker);
 	spin_unlock_irq(&gcwq->lock);
 
-	lock_map_acquire(&cwq->wq->lockdep_map);
+	/*
+	 * If @max_active is 1 or rescuer is in use, flushing another work
+	 * item on the same workqueue may lead to deadlock.  Make sure the
+	 * flusher is not running on the same workqueue by verifying write
+	 * access.
+	 */
+	if (cwq->wq->saved_max_active == 1 || cwq->wq->flags & WQ_RESCUER)
+		lock_map_acquire(&cwq->wq->lockdep_map);
+	else
+		lock_map_acquire_read(&cwq->wq->lockdep_map);
 	lock_map_release(&cwq->wq->lockdep_map);
Quoted 8 lines

kernel/workqueue.c static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr, · line 2401

Quoted 4 lines
kernel/workqueue.c +1 -0
+
 	return true;
 already_gone:
 	spin_unlock_irq(&gcwq->lock);

Ah, but this violates the rule that you must always use the most strict constraints. Code doesn't know if it will run in a rescue thread or not, hence it must assume it does.

Quoted 4 lines

Hmmm? The code applies the most strict contraints. If the workqueue has a rescuer, flushing another work from the workqueue will always trigger lockdep warning. The rule is relaxed only for workqueues which aren't used for memory reclaiming && support parallel execution.

Ah, ok. I read it like: if the current thread is a rescue thread.

author maintainer
#7 lore raw
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines

Hmmm? The code applies the most strict contraints. If the workqueue has a rescuer, flushing another work from the workqueue will always trigger lockdep warning. The rule is relaxed only for workqueues which aren't used for memory reclaiming && support parallel execution.

Ah, ok. I read it like: if the current thread is a rescue thread.

Ah, I see. Rafael, can you please verify the updated version is okay too (it should be but just in case)? Peter, shall I route this through workqueue tree with your ack?

Thank you.

Signature · 1 line
tejun
maintainer
#8 lore raw
Quoted 11 lines · Tejun Heo
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines

Hmmm? The code applies the most strict contraints. If the workqueue has a rescuer, flushing another work from the workqueue will always trigger lockdep warning. The rule is relaxed only for workqueues which aren't used for memory reclaiming && support parallel execution.

Ah, ok. I read it like: if the current thread is a rescue thread.

Ah, I see. Rafael, can you please verify the updated version is okay too (it should be but just in case)? Peter, shall I route this through workqueue tree with your ack?

That's fine with me, thanks!

#9 lore raw
In reply to Tejun Heo
Quoted 10 lines · Tejun Heo
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines

Hmmm? The code applies the most strict contraints. If the workqueue has a rescuer, flushing another work from the workqueue will always trigger lockdep warning. The rule is relaxed only for workqueues which aren't used for memory reclaiming && support parallel execution.

Ah, ok. I read it like: if the current thread is a rescue thread.

Ah, I see. Rafael, can you please verify the updated version is okay too (it should be but just in case)?

It still appears to be fine.

Thanks,
Rafael

author maintainer
#10 lore raw
Quoted 7 lines · Rafael J. Wysocki
Quoted 6 lines · Tejun Heo
Quoted 5 lines · Peter Zijlstra
Quoted 4 lines

Hmmm? The code applies the most strict contraints. If the workqueue has a rescuer, flushing another work from the workqueue will always trigger lockdep warning. The rule is relaxed only for workqueues which aren't used for memory reclaiming && support parallel execution.

Quoted 6 lines
Quoted 4 lines
Quoted 1 line

Ah, ok. I read it like: if the current thread is a rescue thread.

Ah, I see. Rafael, can you please verify the updated version is okay too (it should be but just in case)?

It still appears to be fine.

Applied to wq#fixes-2.6.38 with Tested-by and stable cc added.

Thank you.

Signature · 1 line
tejun