Skip to contentSkip to conversation

locking/mutexes: don't spin on owner when wait list is not NULL.

RFC PATCH In review Ding Tianhong 10 participants 34 messages Jan 21, 2016 – Feb 29, 2016 Locking primitives

lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 locking/mutexes: don't spin on owner when wait list is not NULL.

I build a script to create several process for ioctl loop calling, the ioctl will calling the kernel function just like:
xx_ioctl {
...
rtnl_lock();
function();
rtnl_unlock();
...
}
The function may sleep several ms, but will not halt, at the same time another user service may calling ifconfig to change the state of the ethernet, and after several hours, the hung task thread report this problem:

========================================================================
149738.039038] INFO: task ifconfig:11890 blocked for more than 120 seconds.

[149738.040597] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[149738.042280] ifconfig D ffff88061ec13680 0 11890 11573 0x00000080
[149738.042284] ffff88052449bd40 0000000000000082 ffff88053a33f300 ffff88052449bfd8
[149738.042286] ffff88052449bfd8 ffff88052449bfd8 ffff88053a33f300 ffffffff819e6240
[149738.042288] ffffffff819e6244 ffff88053a33f300 00000000ffffffff ffffffff819e6248
[149738.042290] Call Trace:
[149738.042300] [<ffffffff8160d219>] schedule_preempt_disabled+0x29/0x70
[149738.042303] [<ffffffff8160af65>] __mutex_lock_slowpath+0xc5/0x1c0
[149738.042305] [<ffffffff8160a3cf>] mutex_lock+0x1f/0x2f
[149738.042309] [<ffffffff8150d945>] rtnl_lock+0x15/0x20
[149738.042311] [<ffffffff81514e3a>] dev_ioctl+0xda/0x590
[149738.042314] [<ffffffff816121cc>] ? __do_page_fault+0x21c/0x560
[149738.042318] [<ffffffff814e42c5>] sock_do_ioctl+0x45/0x50
[149738.042320] [<ffffffff814e49d0>] sock_ioctl+0x1f0/0x2c0
[149738.042324] [<ffffffff811dc9b5>] do_vfs_ioctl+0x2e5/0x4c0
[149738.042327] [<ffffffff811e6a00>] ? fget_light+0xa0/0xd0

================================ cut here ================================

I got the vmcore and found that the ifconfig is already in the wait_list of the rtnl_lock for 120 second, but my process could get and release the rtnl_lock normally several times in one second, so it means that my process jump the queue and the ifconfig couldn't get the rtnl all the time, I check the mutex lock slow path and found that the mutex may spin on owner ignore whether the wait list is empty, it will cause the task in the wait list always be cut in line, so add test for wait list in the mutex_can_spin_on_owner and avoid this problem.

Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Davidlohr Bueso <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Paul E. McKenney <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc Will Deacon <[email protected]>
Cc Jason Low <[email protected]>
Cc Tim Chen <[email protected]>
Cc Waiman Long <[email protected]>
---
 kernel/locking/mutex.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)
kernel/locking/mutex.c +6 -5
index 0551c21..596b341 100644
@@ -256,7 +256,7 @@ static inline int mutex_can_spin_on_owner(struct mutex *lock)
 	struct task_struct *owner;
 	int retval = 1;
 
-	if (need_resched())
+	if (need_resched() || atomic_read(&lock->count) == -1)
 		return 0;
 
 	rcu_read_lock();
@@ -283,10 +283,11 @@ static inline bool mutex_try_to_acquire(struct mutex *lock)
 /*
  * Optimistic spinning.
  *
- * We try to spin for acquisition when we find that the lock owner
- * is currently running on a (different) CPU and while we don't
- * need to reschedule. The rationale is that if the lock owner is
- * running, it is likely to release the lock soon.
+ * We try to spin for acquisition when we find that there are no
+ * pending waiters and the lock owner is currently running on a
+ * (different) CPU and while we don't need to reschedule. The
+ * rationale is that if the lock owner is running, it is likely
+ * to release the lock soon.
  *
  * Since this needs the lock owner, and this mutex implementation
  * doesn't track the owner atomically in the lock field, we need to
Signature · 1 line
2.5.0
#1 lore raw

kernel/locking/mutex.c static inline int mutex_can_spin_on_owner(struct mutex *lock) · line 261

Quoted 13 lines · Ding Tianhong
kernel/locking/mutex.c +1 -1
index 0551c21..596b341 100644
@@ -256,7 +256,7 @@ static inline int mutex_can_spin_on_owner(struct mutex *lock)
 	struct task_struct *owner;
 	int retval = 1;
 
-	if (need_resched())
+	if (need_resched() || atomic_read(&lock->count) == -1)
 		return 0;
 

One concern I have is this change will eliminate any optimistic spinning as long as there is a waiter. Is there a middle ground that we can allow only one spinner if there are waiters?

In other words, we allow spinning when atomic_read(&lock->count) == -1 but there is no one on the osq lock that queue up the spinners (i.e. no other process doing optimistic spinning).

This could allow a bit of spinning without starving out the waiters.

Tim

#2 lore raw
Quoted 20 lines · Tim Chen

kernel/locking/mutex.c static inline int mutex_can_spin_on_owner(struct mutex *lock) · line 261

Quoted 13 lines · Ding Tianhong
kernel/locking/mutex.c +1 -1
index 0551c21..596b341 100644
@@ -256,7 +256,7 @@ static inline int mutex_can_spin_on_owner(struct mutex *lock)
 	struct task_struct *owner;
 	int retval = 1;
 
-	if (need_resched())
+	if (need_resched() || atomic_read(&lock->count) == -1)
 		return 0;
 

One concern I have is this change will eliminate any optimistic spinning as long as there is a waiter. Is there a middle ground that we can allow only one spinner if there are waiters?

Quoted 6 lines

In other words, we allow spinning when atomic_read(&lock->count) == -1 but there is no one on the osq lock that queue up the spinners (i.e. no other process doing optimistic spinning).

This could allow a bit of spinning without starving out the waiters.

I did some testing, which exposed it to the 0day test robot, which did note some performance differences. I was hoping that it would clear up some instability from other patches, but no such luck. ;-)

Thanx, Paul

#3 lore raw
Quoted 3 lines · Paul E. McKenney

I did some testing, which exposed it to the 0day test robot, which did note some performance differences. I was hoping that it would clear up some instability from other patches, but no such luck. ;-)

Oh, that explains why we got a performance regression report :)

Thanks,
Davidlohr

#4 lore raw
Quoted 7 lines · Davidlohr Bueso
Quoted 3 lines · Paul E. McKenney

I did some testing, which exposed it to the 0day test robot, which did note some performance differences. I was hoping that it would clear up some instability from other patches, but no such luck. ;-)

Oh, that explains why we got a performance regression report :)

Plus I suspected that you wanted some extra email. ;-)

Thanx, Paul

#5 lore raw
In reply to Ding Tianhong
Quoted 76 lines · Ding Tianhong

I build a script to create several process for ioctl loop calling, the ioctl will calling the kernel function just like:
xx_ioctl {
...
rtnl_lock();
function();
rtnl_unlock();
...
}
The function may sleep several ms, but will not halt, at the same time another user service may calling ifconfig to change the state of the ethernet, and after several hours, the hung task thread report this problem:

========================================================================
149738.039038] INFO: task ifconfig:11890 blocked for more than 120 seconds.

[149738.040597] "echo 0>  /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[149738.042280] ifconfig D ffff88061ec13680 0 11890 11573 0x00000080
[149738.042284] ffff88052449bd40 0000000000000082 ffff88053a33f300 ffff88052449bfd8
[149738.042286] ffff88052449bfd8 ffff88052449bfd8 ffff88053a33f300 ffffffff819e6240
[149738.042288] ffffffff819e6244 ffff88053a33f300 00000000ffffffff ffffffff819e6248
[149738.042290] Call Trace:
[149738.042300] [<ffffffff8160d219>] schedule_preempt_disabled+0x29/0x70
[149738.042303] [<ffffffff8160af65>] __mutex_lock_slowpath+0xc5/0x1c0
[149738.042305] [<ffffffff8160a3cf>] mutex_lock+0x1f/0x2f
[149738.042309] [<ffffffff8150d945>] rtnl_lock+0x15/0x20
[149738.042311] [<ffffffff81514e3a>] dev_ioctl+0xda/0x590
[149738.042314] [<ffffffff816121cc>] ? __do_page_fault+0x21c/0x560
[149738.042318] [<ffffffff814e42c5>] sock_do_ioctl+0x45/0x50
[149738.042320] [<ffffffff814e49d0>] sock_ioctl+0x1f0/0x2c0
[149738.042324] [<ffffffff811dc9b5>] do_vfs_ioctl+0x2e5/0x4c0
[149738.042327] [<ffffffff811e6a00>] ? fget_light+0xa0/0xd0

================================ cut here ================================

I got the vmcore and found that the ifconfig is already in the wait_list of the rtnl_lock for 120 second, but my process could get and release the rtnl_lock normally several times in one second, so it means that my process jump the queue and the ifconfig couldn't get the rtnl all the time, I check the mutex lock slow path and found that the mutex may spin on owner ignore whether the wait list is empty, it will cause the task in the wait list always be cut in line, so add test for wait list in the mutex_can_spin_on_owner and avoid this problem.

Cc Ingo Molnar<[email protected]>
Cc Peter Zijlstra<[email protected]>
Cc Davidlohr Bueso<[email protected]>
Cc Linus Torvalds<[email protected]>
Cc Paul E. McKenney<[email protected]>
Cc Thomas Gleixner<[email protected]>
Cc Will Deacon<[email protected]>
Cc Jason Low<[email protected]>
Cc Tim Chen<[email protected]>
Cc Waiman Long<[email protected]>
---
  kernel/locking/mutex.c | 11 ++++++-----
  1 file changed, 6 insertions(+), 5 deletions(-)
kernel/locking/mutex.c +1 -3
index 0551c21..596b341 100644
@@ -256,7 +256,7 @@ static inline int mutex_can_spin_on_owner(struct mutex *lock)
  	struct task_struct *owner;
  	int retval = 1;
 
-	if (need_resched())
+	if (need_resched() || atomic_read(&lock->count) == -1)
  		return 0;
 
  	rcu_read_lock();
@@ -283,10 +283,11 @@ static inline bool mutex_try_to_acquire(struct mutex *lock)
  /*
   * Optimistic spinning.
   *
- * We try to spin for acquisition when we find that the lock owner
- * is currently running on a (different) CPU and while we don't

kernel/locking/mutex.c static inline bool mutex_try_to_acquire(struct mutex *lock) · line 293

Quoted 10 lines
kernel/locking/mutex.c +5 -2
- * need to reschedule. The rationale is that if the lock owner is
- * running, it is likely to release the lock soon.
+ * We try to spin for acquisition when we find that there are no
+ * pending waiters and the lock owner is currently running on a
+ * (different) CPU and while we don't need to reschedule. The
+ * rationale is that if the lock owner is running, it is likely
+ * to release the lock soon.
   *
   * Since this needs the lock owner, and this mutex implementation
   * doesn't track the owner atomically in the lock field, we need to

This patch will largely defeat the performance benefit of optimistic spinning. I have an alternative solution to this live-lock problem. Would you mind trying out the attached patch to see if it can fix your problem?

Cheers,
Longman

From 1bbb5a4434d395f48163abc5435c5c720a15d327 Mon Sep 17 00:00:00 2001
From: Waiman Long <[email protected]>
Date: Thu, 21 Jan 2016 17:53:14 -0500
Subject: [PATCH] locking/mutex: Enable optimistic spinning of woken task in wait list

Ding Tianhong reported a live-lock situation where a constant stream of incoming optimistic spinners blocked a task in the wait list from getting the mutex.

This patch attempts to fix this live-lock condition by enabling the a woken task in the wait list to enter optimistic spinning loop itself with precedence over the ones in the OSQ. This should prevent the live-lock
condition from happening.

---
 include/linux/mutex.h  |    2 +
 kernel/locking/mutex.c |   95 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 95 insertions(+), 2 deletions(-)
include/linux/mutex.h +2 -0
index 2cb7531..2c55ecd 100644
@@ -57,6 +57,8 @@ struct mutex {
 #endif
 #ifdef CONFIG_MUTEX_SPIN_ON_OWNER
 	struct optimistic_spin_queue osq; /* Spinner MCS lock */
+	/* Set if wait list head actively spinning */
+	int			wlh_spinning;
 #endif
 #ifdef CONFIG_DEBUG_MUTEXES
 	void			*magic;
kernel/locking/mutex.c +93 -2

132 lines not on this page · raw message

Signature · 1 line
1.7.1
#6 lore raw
Quoted 9 lines · Waiman Long
Quoted 7 lines · Ding Tianhong

I got the vmcore and found that the ifconfig is already in the wait_list of the rtnl_lock for 120 second, but my process could get and release the rtnl_lock normally several times in one second, so it means that my process jump the queue and the ifconfig couldn't get the rtnl all the time, I check the mutex lock slow path and found that the mutex may spin on owner ignore whether the wait list is empty, it will cause the task in the wait list always be cut in line, so add test for wait list in the mutex_can_spin_on_owner and avoid this problem.

So this has been somewhat always known, at least in theory, until now. It's the cost of spinning without going through the wait-queue, unlike other locks.

Quoted 10 lines
Quoted 1 line

[...]

From: Waiman Long <[email protected]>
Date: Thu, 21 Jan 2016 17:53:14 -0500
Subject: [PATCH] locking/mutex: Enable optimistic spinning of woken task in wait list

Ding Tianhong reported a live-lock situation where a constant stream of incoming optimistic spinners blocked a task in the wait list from getting the mutex.

Quoted 5 lines

This patch attempts to fix this live-lock condition by enabling the a woken task in the wait list to enter optimistic spinning loop itself with precedence over the ones in the OSQ. This should prevent the live-lock
condition from happening.

And one of the reasons why we never bothered 'fixing' things was the additional branching out in the slowpath (and lack of real issue, although this one being so damn pathological). I fear that your approach is one of those scenarios where the code ends up being bloated, albeit most of it is actually duplicated and can be refactored *sigh*. So now we'd spin, then sleep, then try spinning then sleep again... phew. Not to mention the performance implications, ie loosing the benefits of osq over waiter spinning in scenarios that would otherwise have more osq spinners as opposed to waiter spinners, or in setups where it is actually best to block instead of spinning.

Thanks,
Davidlohr

#7 lore raw
Quoted 31 lines · Davidlohr Bueso
Quoted 9 lines · Waiman Long
Quoted 7 lines · Ding Tianhong

I got the vmcore and found that the ifconfig is already in the wait_list of the rtnl_lock for 120 second, but my process could get and release the rtnl_lock normally several times in one second, so it means that my process jump the queue and the ifconfig couldn't get the rtnl all the time, I check the mutex lock slow path and found that the mutex may spin on owner ignore whether the wait list is empty, it will cause the task in the wait list always be cut in line, so add test for wait list in the mutex_can_spin_on_owner and avoid this problem.

So this has been somewhat always known, at least in theory, until now. It's the cost of spinning without going through the wait-queue, unlike other locks.

Quoted 10 lines
Quoted 1 line

[...]

From: Waiman Long <[email protected]>
Date: Thu, 21 Jan 2016 17:53:14 -0500
Subject: [PATCH] locking/mutex: Enable optimistic spinning of woken task in wait list

Ding Tianhong reported a live-lock situation where a constant stream of incoming optimistic spinners blocked a task in the wait list from getting the mutex.

Quoted 5 lines

This patch attempts to fix this live-lock condition by enabling the a woken task in the wait list to enter optimistic spinning loop itself with precedence over the ones in the OSQ. This should prevent the live-lock
condition from happening.

Quoted 9 lines

And one of the reasons why we never bothered 'fixing' things was the additional branching out in the slowpath (and lack of real issue, although this one being so damn pathological). I fear that your approach is one of those scenarios where the code ends up being bloated, albeit most of it is actually duplicated and can be refactored *sigh*. So now we'd spin, then sleep, then try spinning then sleep again... phew. Not to mention the performance implications, ie loosing the benefits of osq over waiter spinning in scenarios that would otherwise have more osq spinners as opposed to waiter spinners, or in setups where it is actually best to block instead of spinning.

The patch that I sent out is just a proof of concept to make sure that it can fix that particular case. I do plan to refactor it if I decide to go ahead with an official one. Unlike the OSQ, there can be no more than one waiter spinner as the wakeup function is directed to only the first task in the wait list and the spinning won't happen until the task is first woken up. In the worst case scenario, there are only 2 spinners spinning on the lock and the owner field, one from OSQ and one from the wait list. That shouldn't put too much cacheline contention traffic to the system.

Cheers,
Longman

#8 lore raw
Quoted 9 lines · Waiman Long

The patch that I sent out is just a proof of concept to make sure that it can fix that particular case. I do plan to refactor it if I decide to go ahead with an official one. Unlike the OSQ, there can be no more than one waiter spinner as the wakeup function is directed to only the first task in the wait list and the spinning won't happen until the task is first woken up. In the worst case scenario, there are only 2 spinners spinning on the lock and the owner field, one from OSQ and one from the wait list. That shouldn't put too much cacheline contention traffic to the system.

Similarly, I guess we should also wakeup the next waiter in line after releasing the wait_lock via wake_q. This would allow the woken waiter a slightly better chance of finding the wait_lock free when continuing to take the mutex.

Thanks,
Davidlohr

#9 lore raw

PATCH 1/1 locking/mutex: Allow next waiter lockless wakeup

Make use of wake_q and enable the wakeup to occur after releasing the wait_lock. This is similar to what we do with rtmutex top waiter, slightly shortening the critical region and allow other waiters to acquire the wait_lock sooner. In low contention cases it can also help the recently woken waiter to find the wait_lock available (fastpath) when it continues execution.

---
 kernel/locking/mutex.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/locking/mutex.c +3 -2
index 0551c21..e364b42 100644
@@ -716,6 +716,7 @@ static inline void
 __mutex_unlock_common_slowpath(struct mutex *lock, int nested)
 {
 	unsigned long flags;
+	WAKE_Q(wake_q);
 
 	/*
 	 * As a performance measurement, release the lock before doing other
@@ -743,11 +744,11 @@ __mutex_unlock_common_slowpath(struct mutex *lock, int nested)
 					   struct mutex_waiter, list);
 
 		debug_mutex_wake_waiter(lock, waiter);
-
-		wake_up_process(waiter->task);
+		wake_q_add(&wake_q, waiter->task);
 	}
 
 	spin_unlock_mutex(&lock->wait_lock, flags);
+	wake_up_q(&wake_q);
 }
 
 /*
Signature · 1 line
2.1.4
Reviewed-by on v1 1/1
#10 lore raw

Re: [PATCH] locking/mutex: Allow next waiter lockless wakeup

Quoted 39 lines · Davidlohr Bueso

Make use of wake_q and enable the wakeup to occur after releasing the wait_lock. This is similar to what we do with rtmutex top waiter, slightly shortening the critical region and allow other waiters to acquire the wait_lock sooner. In low contention cases it can also help the recently woken waiter to find the wait_lock available (fastpath) when it continues execution.

---
 kernel/locking/mutex.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/locking/mutex.c +3 -2
index 0551c21..e364b42 100644
@@ -716,6 +716,7 @@ static inline void
 __mutex_unlock_common_slowpath(struct mutex *lock, int nested)
 {
     unsigned long flags;
+    WAKE_Q(wake_q);
 
     /*
      * As a performance measurement, release the lock before doing other
@@ -743,11 +744,11 @@ __mutex_unlock_common_slowpath(struct mutex 
 *lock, int nested)
                        struct mutex_waiter, list);
 
         debug_mutex_wake_waiter(lock, waiter);
-
-        wake_up_process(waiter->task);
+        wake_q_add(&wake_q, waiter->task);
     }
 
     spin_unlock_mutex(&lock->wait_lock, flags);
+    wake_up_q(&wake_q);
 }
 
Quoted 1 line

/*

This patch looks good to me.

#11 lore raw

[tip:locking/core] locking/mutex: Allow next waiter lockless wakeup

In reply to Davidlohr Bueso
locking/mutex: Allow next waiter lockless wakeup
Commit-ID:  1329ce6fbbe4536592dfcfc8d64d61bfeb598fe6
Gitweb:     http://git.kernel.org/tip/1329ce6fbbe4536592dfcfc8d64d61bfeb598fe6
Author:     Davidlohr Bueso <[email protected]>
AuthorDate: Sun, 24 Jan 2016 18:23:43 -0800
Committer:  Ingo Molnar <[email protected]>
CommitDate: Mon, 29 Feb 2016 10:02:42 +0100

locking/mutex: Allow next waiter lockless wakeup

Make use of wake-queues and enable the wakeup to occur after releasing the wait_lock. This is similar to what we do with rtmutex top waiter, slightly shortening the critical region and allow other waiters to acquire the wait_lock sooner. In low contention cases it can also help the recently woken waiter to find the wait_lock available (fastpath) when it continues execution.

Cc Andrew Morton <[email protected]>
Cc Ding Tianhong <[email protected]>
Cc Jason Low <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Paul E. McKenney <[email protected]>
Cc Paul E. McKenney <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc Tim Chen <[email protected]>
Cc Waiman Long <[email protected]>
Cc Will Deacon <[email protected]>
---
 kernel/locking/mutex.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/locking/mutex.c +3 -2
index 0551c21..e364b42 100644
@@ -716,6 +716,7 @@ static inline void
 __mutex_unlock_common_slowpath(struct mutex *lock, int nested)
 {
 	unsigned long flags;
+	WAKE_Q(wake_q);
 
 	/*
 	 * As a performance measurement, release the lock before doing other
@@ -743,11 +744,11 @@ __mutex_unlock_common_slowpath(struct mutex *lock, int nested)
 					   struct mutex_waiter, list);
 
 		debug_mutex_wake_waiter(lock, waiter);
-
-		wake_up_process(waiter->task);
+		wake_q_add(&wake_q, waiter->task);
 	}
 
 	spin_unlock_mutex(&lock->wait_lock, flags);
+	wake_up_q(&wake_q);
 }
 
 /*
maintainer
#12 lore raw
In reply to Waiman Long
Quoted 5 lines · Waiman Long

This patch attempts to fix this live-lock condition by enabling the a woken task in the wait list to enter optimistic spinning loop itself with precedence over the ones in the OSQ. This should prevent the live-lock
condition from happening.

So I think having the top waiter going back in to contend on the OSQ is an excellent idea, but I'm not sure the wlh_spinning thing is important.

The OSQ itself is FIFO fair, and the waiters retain the wait_list position. So having the top wait_list entry contending on the OSQ ensures we cannot starve (I think).

Also, as Davidlohr said, we cannot copy/paste this much code.

#13 lore raw
Quoted 8 lines · Peter Zijlstra
Quoted 5 lines · Waiman Long

This patch attempts to fix this live-lock condition by enabling the a woken task in the wait list to enter optimistic spinning loop itself with precedence over the ones in the OSQ. This should prevent the live-lock
condition from happening.

Quoted 6 lines

So I think having the top waiter going back in to contend on the OSQ is an excellent idea, but I'm not sure the wlh_spinning thing is important.

The OSQ itself is FIFO fair, and the waiters retain the wait_list position. So having the top wait_list entry contending on the OSQ ensures we cannot starve (I think).

Right, and we can also avoid needing to add that extra field to the mutex structure. Before calling optimistic spinning, we do want to check if the lock is available to avoid unnecessary OSQ overhead though.

So maybe the following would be sufficient:

---
 kernel/locking/mutex.c |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)
kernel/locking/mutex.c +7 -0
index 0551c21..ead0bd1 100644
@@ -543,6 +543,8 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 	lock_contended(&lock->dep_map, ip);
 
 	for (;;) {
+		bool acquired = false;
+
 		/*
 		 * Lets try to take the lock again - this is needed even if
 		 * we get here for the first time (shortly after failing to
@@ -577,7 +579,12 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 		/* didn't get the lock, go to sleep: */
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
+
+		if (mutex_is_locked(lock))
+			acquired = mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx);
 		spin_lock_mutex(&lock->wait_lock, flags);
+		if (acquired)
+			break;
 	}
 	__set_task_state(task, TASK_RUNNING);
 
Signature · 1 line
1.7.2.5
maintainer
#14 lore raw
Quoted 13 lines · Jason Low
kernel/locking/mutex.c +2 -0
@@ -543,6 +543,8 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 	lock_contended(&lock->dep_map, ip);
 
 	for (;;) {
+		bool acquired = false;
+
 		/*
 		 * Lets try to take the lock again - this is needed even if
 		 * we get here for the first time (shortly after failing to
@@ -577,7 +579,12 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 		/* didn't get the lock, go to sleep: */

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, · line 589

Quoted 10 lines
kernel/locking/mutex.c +5 -0
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
+
+		if (mutex_is_locked(lock))
+			acquired = mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx);
 		spin_lock_mutex(&lock->wait_lock, flags);
+		if (acquired)
+			break;
 	}
 	__set_task_state(task, TASK_RUNNING);

I think the problem here is that mutex_optimistic_spin() leaves the mutex->count == 0, even though we have waiters (us at the very least).

But this should be easily fixed, since if we acquired, we should be the one releasing, so there's no race.

So something like so:

		if (acquired) {
			atomic_set(&mutex->count, -1);
			break;
		}

Should deal with that -- we'll set it to 0 again a little further down if the list ends up empty.

There might be other details, but this is the one that stood out.

maintainer
#15 lore raw
Quoted 1 line · Peter Zijlstra

There might be other details, but this is the one that stood out.

I think this also does the wrong thing for use_ww_ctx.

maintainer
#16 lore raw
Quoted 5 lines · Peter Zijlstra
Quoted 1 line · Peter Zijlstra

There might be other details, but this is the one that stood out.

I think this also does the wrong thing for use_ww_ctx.

Something like so?

---
 kernel/locking/mutex.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
kernel/locking/mutex.c +15 -0
index 0551c219c40e..070a0ac34aa7 100644
@@ -512,6 +512,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 	struct task_struct *task = current;
 	struct mutex_waiter waiter;
 	unsigned long flags;
+	bool acquired;
 	int ret;
 
 	preempt_disable();
@@ -543,6 +544,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 	lock_contended(&lock->dep_map, ip);
 
 	for (;;) {
+		acquired = false;
 		/*
 		 * Lets try to take the lock again - this is needed even if
 		 * we get here for the first time (shortly after failing to
@@ -577,7 +579,16 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 		/* didn't get the lock, go to sleep: */
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
+
+		if (mutex_is_locked(lock))
+			acquired = mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx);
+
 		spin_lock_mutex(&lock->wait_lock, flags);
+
+		if (acquired) {
+			atomic_set(&lock->count, -1);
+			break;
+		}
 	}
 	__set_task_state(task, TASK_RUNNING);
 
@@ -587,6 +598,9 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 		atomic_set(&lock->count, 0);
 	debug_mutex_free_waiter(&waiter);
 
+	if (acquired)
+		goto unlock;
+
 skip_wait:
 	/* got the lock - cleanup and rejoice! */
 	lock_acquired(&lock->dep_map, ip);
@@ -597,6 +611,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
 		ww_mutex_set_context_slowpath(ww, ww_ctx);
 	}
 
+unlock:
 	spin_unlock_mutex(&lock->wait_lock, flags);
 	preempt_enable();
 	return 0;
#17 lore raw
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · Peter Zijlstra
Quoted 1 line · Peter Zijlstra

There might be other details, but this is the one that stood out.

I think this also does the wrong thing for use_ww_ctx.

Something like so?

I think that should work. My only minor concern is that putting the waiter spinner at the end of the OSQ will take it longer to get the lock, but that shouldn't be a big issue.

Quoted 52 lines
---
  kernel/locking/mutex.c | 15 +++++++++++++++
  1 file changed, 15 insertions(+)
kernel/locking/mutex.c +14 -0
index 0551c219c40e..070a0ac34aa7 100644
@@ -512,6 +512,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
  	struct task_struct *task = current;
  	struct mutex_waiter waiter;
  	unsigned long flags;
+	bool acquired;
  	int ret;
 
  	preempt_disable();
@@ -543,6 +544,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
  	lock_contended(&lock->dep_map, ip);
 
  	for (;;) {
+		acquired = false;
  		/*
  		 * Lets try to take the lock again - this is needed even if
  		 * we get here for the first time (shortly after failing to
@@ -577,7 +579,16 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
  		/* didn't get the lock, go to sleep: */
  		spin_unlock_mutex(&lock->wait_lock, flags);
  		schedule_preempt_disabled();
+
+		if (mutex_is_locked(lock))
+			acquired = mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx);
+
  		spin_lock_mutex(&lock->wait_lock, flags);
+
+		if (acquired) {
+			atomic_set(&lock->count, -1);
+			break;
+		}
  	}
  	__set_task_state(task, TASK_RUNNING);
 
@@ -587,6 +598,9 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
  		atomic_set(&lock->count, 0);
  	debug_mutex_free_waiter(&waiter);
 
+	if (acquired)
+		goto unlock;
+
  skip_wait:
  	/* got the lock - cleanup and rejoice! */
  	lock_acquired(&lock->dep_map, ip);

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, · line 617

Quoted 7 lines
kernel/locking/mutex.c +1 -0
@@ -597,6 +611,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
  		ww_mutex_set_context_slowpath(ww, ww_ctx);
  	}
 
+unlock:
  	spin_unlock_mutex(&lock->wait_lock, flags);
  	preempt_enable();
  	return 0;

Cheers,
Longman

#18 lore raw
Quoted 65 lines · Waiman Long
Quoted 6 lines · Peter Zijlstra
Quoted 4 lines · Peter Zijlstra
Quoted 1 line · Peter Zijlstra

There might be other details, but this is the one that stood out.

I think this also does the wrong thing for use_ww_ctx.

Something like so?

I think that should work. My only minor concern is that putting the waiter spinner at the end of the OSQ will take it longer to get the lock, but that shouldn't be a big issue.

Quoted 55 lines
---
  kernel/locking/mutex.c | 15 +++++++++++++++
  1 file changed, 15 insertions(+)
kernel/locking/mutex.c +14 -0
index 0551c219c40e..070a0ac34aa7 100644
@@ -512,6 +512,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
      struct task_struct *task = current;
      struct mutex_waiter waiter;
      unsigned long flags;
+    bool acquired;
      int ret;
 
      preempt_disable();
@@ -543,6 +544,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
      lock_contended(&lock->dep_map, ip);
 
      for (;;) {
+        acquired = false;
          /*
           * Lets try to take the lock again - this is needed even if
           * we get here for the first time (shortly after failing to
@@ -577,7 +579,16 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
          /* didn't get the lock, go to sleep: */
          spin_unlock_mutex(&lock->wait_lock, flags);
          schedule_preempt_disabled();
+
+        if (mutex_is_locked(lock))
+            acquired = mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx);
+
          spin_lock_mutex(&lock->wait_lock, flags);
+
+        if (acquired) {
+            atomic_set(&lock->count, -1);
+            break;
+        }
      }
      __set_task_state(task, TASK_RUNNING);
 
@@ -587,6 +598,9 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
          atomic_set(&lock->count, 0);
      debug_mutex_free_waiter(&waiter);
 
+    if (acquired)
+        goto unlock;
+
  skip_wait:
      /* got the lock - cleanup and rejoice! */
      lock_acquired(&lock->dep_map, ip);
@@ -597,6 +611,7 @@ __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
          ww_mutex_set_context_slowpath(ww, ww_ctx);
      }
 
Quoted 8 lines

kernel/locking/mutex.c __mutex_lock_common(struct mutex *lock, long state, unsigned int subclass, · line 617

Quoted 4 lines
kernel/locking/mutex.c +1 -0
+unlock:
      spin_unlock_mutex(&lock->wait_lock, flags);
      preempt_enable();
      return 0;

Cheers,
Longman

looks good to me, I will try this solution and report the result, thanks everyone.

Ding

Quoted 2 lines

.

maintainer
#19 lore raw
Quoted 1 line · Ding Tianhong

looks good to me, I will try this solution and report the result, thanks everyone.

Did you get a change to run with this?

#20 lore raw
Quoted 8 lines · Peter Zijlstra
Quoted 1 line · Ding Tianhong

looks good to me, I will try this solution and report the result, thanks everyone.

Did you get a change to run with this?

.

I backport this patch to 3.10 lts kernel, and didn't change any logic, Till now, the patch works fine to me, and no need to change anything, So I think this patch is no problem, could you formal release this patch to the latest kernel? :)

Thanks.
Ding

#21 lore raw
Quoted 7 lines · Ding Tianhong
Quoted 6 lines · Peter Zijlstra
Quoted 1 line · Ding Tianhong

looks good to me, I will try this solution and report the result, thanks everyone.

Did you get a change to run with this?

Quoted 10 lines
Quoted 2 lines

.

I backport this patch to 3.10 lts kernel, and didn't change any logic, Till now, the patch works fine to me, and no need to change anything, So I think this patch is no problem, could you formal release this patch to the latest kernel? :)

Thanks.
Ding

The original patch from Tianhong triggered a performance regression because the optimistic spinning is turned off in effect. I tested Peter's patch with same configuration and there show no regression. So I think the patch keep the optimistic spinning. Test result details will be in the next email.

Best Regards,
Huang, YIng

#22 lore raw
Quoted 21 lines · huang ying
Quoted 10 lines · Ding Tianhong
Quoted 8 lines · Peter Zijlstra
Quoted 1 line · Ding Tianhong

looks good to me, I will try this solution and report the result, thanks everyone.

Did you get a change to run with this?

.

Quoted 9 lines

I backport this patch to 3.10 lts kernel, and didn't change any
logic, Till now, the patch works fine to me, and no need to change
anything,
So I think this patch is no problem, could you formal release this patch to the latest kernel? :)

Thanks.
Ding

Quoted 5 lines

The original patch from Tianhong triggered a performance regression because the optimistic spinning is turned off in effect. I tested Peter's patch with same configuration and there show no regression. So I think the patch keep the optimistic spinning. Test result details will be in the next email.

Here is the detailed test result:

=========================================================================================
compiler/cpufreq_governor/kconfig/nr_task/rootfs/tbox_group/test/testcase:
gcc-4.9/performance/x86_64-rhel/100%/debian-x86_64-2015-02-07.cgz/lkp-ivb-d01/fstime/unixbench

commit:
v4.4
1db66c17114d5437c0757d6792c0d8923192ecd6

            v4.4 1db66c17114d5437c0757d6792 
---------------- -------------------------- 
         %stddev     %change         %stddev
             \          |                \  
    371269 ± 10%     -93.2%      25080 ±  4%  unixbench.time.voluntary_context_switches
    371269 ± 10%     -93.2%      25080 ±  4%  time.voluntary_context_switches
      6189 ±  8%     -76.4%       1463 ±  6%  vmstat.system.cs
      5706 ±  0%      -1.7%       5608 ±  0%  vmstat.system.in
    113680 ± 12%     -73.5%      30086 ±  1%  cpuidle.C1-IVB.usage
   1515925 ± 20%     +68.3%    2552001 ± 11%  cpuidle.C1E-IVB.time
   1227221 ± 20%     -51.7%     592695 ± 17%  cpuidle.C3-IVB.time
      2697 ± 10%     -77.8%     598.33 ± 10%  cpuidle.C3-IVB.usage
     15173 ±  6%     -23.1%      11663 ±  1%  cpuidle.C6-IVB.usage
     34.38 ± 27%     -35.0%      22.33 ±  2%  cpuidle.POLL.usage
     61.92 ±  9%     +14.3%      70.78 ±  7%  sched_debug.cfs_rq:/.load_avg.min
     40.85 ± 29%     +27.3%      52.00 ± 10%  sched_debug.cfs_rq:/.runnable_load_avg.2
     -1949 ±-37%     -64.6%    -690.19 ±-134%  sched_debug.cfs_rq:/.spread0.4
     -1773 ±-29%     -85.6%    -256.00 ±-388%  sched_debug.cfs_rq:/.spread0.7
     -2478 ±-26%     -49.5%      -1251 ±-66%  sched_debug.cfs_rq:/.spread0.min
     61.95 ±  9%     +14.8%      71.11 ±  7%  sched_debug.cfs_rq:/.tg_load_avg_contrib.min
    396962 ± 12%     +27.9%     507573 ± 10%  sched_debug.cpu.avg_idle.0
    432973 ± 18%     +45.3%     629147 ± 12%  sched_debug.cpu.avg_idle.6
    448566 ±  3%     +11.5%     499990 ±  0%  sched_debug.cpu.avg_idle.avg
     45.31 ±  5%      -9.5%      41.00 ±  3%  sched_debug.cpu.cpu_load[3].7
     52204 ± 10%     -49.9%      26173 ± 34%  sched_debug.cpu.nr_switches.0
     50383 ± 12%     -57.6%      21353 ± 15%  sched_debug.cpu.nr_switches.1
     45425 ± 16%     -68.5%      14325 ± 28%  sched_debug.cpu.nr_switches.2
     43069 ± 20%     -65.5%      14852 ± 41%  sched_debug.cpu.nr_switches.3
     40285 ± 16%     -70.4%      11905 ± 47%  sched_debug.cpu.nr_switches.4
     40732 ± 13%     -75.8%       9872 ± 39%  sched_debug.cpu.nr_switches.5
     43011 ± 19%     -80.0%       8607 ± 42%  sched_debug.cpu.nr_switches.6
     38076 ± 12%     -75.9%       9167 ± 40%  sched_debug.cpu.nr_switches.7
     44148 ±  7%     -67.1%      14532 ±  6%  sched_debug.cpu.nr_switches.avg
     59877 ±  8%     -51.3%      29146 ± 21%  sched_debug.cpu.nr_switches.max
     33672 ±  9%     -83.0%       5718 ±  4%  sched_debug.cpu.nr_switches.min
     -0.62 ±-1411%   -2212.5%      13.00 ± 16%  sched_debug.cpu.nr_uninterruptible.0
     -1.23 ±-582%   -1318.8%      15.00 ± 35%  sched_debug.cpu.nr_uninterruptible.1
      2.54 ±263%    +267.7%       9.33 ± 91%  sched_debug.cpu.nr_uninterruptible.2
      0.31 ±2966%   -5841.7%     -17.67 ±-19%  sched_debug.cpu.nr_uninterruptible.5
      9.84 ± 19%     +70.4%      16.76 ±  5%  sched_debug.cpu.nr_uninterruptible.stddev
    116287 ±  4%     -20.9%      91972 ±  9%  sched_debug.cpu.sched_count.0
     50411 ± 12%     -57.6%      21382 ± 15%  sched_debug.cpu.sched_count.1
     45453 ± 16%     -68.4%      14356 ± 28%  sched_debug.cpu.sched_count.2
     43098 ± 20%     -65.5%      14888 ± 41%  sched_debug.cpu.sched_count.3
     40314 ± 16%     -70.4%      11934 ± 47%  sched_debug.cpu.sched_count.4
     40761 ± 13%     -75.7%       9896 ± 39%  sched_debug.cpu.sched_count.5
     43041 ± 19%     -79.9%       8636 ± 42%  sched_debug.cpu.sched_count.6
     38105 ± 12%     -75.9%       9193 ± 40%  sched_debug.cpu.sched_count.7
     52184 ±  6%     -56.3%      22782 ±  4%  sched_debug.cpu.sched_count.avg
    116288 ±  4%     -20.9%      91972 ±  9%  sched_debug.cpu.sched_count.max
     33701 ±  9%     -82.9%       5746 ±  4%  sched_debug.cpu.sched_count.min
     22760 ± 10%     -63.0%       8418 ± 40%  sched_debug.cpu.sched_goidle.0
     23319 ± 13%     -60.9%       9114 ± 22%  sched_debug.cpu.sched_goidle.1
     21273 ± 17%     -80.4%       4169 ± 13%  sched_debug.cpu.sched_goidle.2
     19993 ± 19%     -67.8%       6429 ± 45%  sched_debug.cpu.sched_goidle.3
     18614 ± 17%     -85.0%       2788 ± 29%  sched_debug.cpu.sched_goidle.4
     18921 ± 12%     -86.7%       2520 ± 15%  sched_debug.cpu.sched_goidle.5
     20131 ± 17%     -82.1%       3596 ± 52%  sched_debug.cpu.sched_goidle.6
     17861 ± 12%     -86.9%       2334 ± 14%  sched_debug.cpu.sched_goidle.7
     20359 ±  8%     -75.8%       4921 ±  5%  sched_debug.cpu.sched_goidle.avg
     26477 ± 10%     -60.2%      10539 ± 21%  sched_debug.cpu.sched_goidle.max
     15845 ± 10%     -87.2%       2033 ±  4%  sched_debug.cpu.sched_goidle.min
     29043 ± 15%     -58.8%      11958 ± 26%  sched_debug.cpu.ttwu_count.0
     24191 ± 10%     -68.8%       7547 ± 27%  sched_debug.cpu.ttwu_count.1
     21313 ± 11%     -72.7%       5819 ± 24%  sched_debug.cpu.ttwu_count.2
     21487 ± 13%     -61.4%       8296 ± 43%  sched_debug.cpu.ttwu_count.3
     19644 ± 15%     -54.4%       8967 ± 79%  sched_debug.cpu.ttwu_count.4
     20786 ± 15%     -69.2%       6409 ± 58%  sched_debug.cpu.ttwu_count.5
     20435 ± 17%     -79.3%       4231 ± 58%  sched_debug.cpu.ttwu_count.6
     19293 ± 17%     -77.0%       4432 ± 55%  sched_debug.cpu.ttwu_count.7
     22024 ±  7%     -67.3%       7207 ±  6%  sched_debug.cpu.ttwu_count.avg
     31009 ±  9%     -45.2%      17008 ± 17%  sched_debug.cpu.ttwu_count.max
     16791 ± 10%     -85.1%       2494 ±  5%  sched_debug.cpu.ttwu_count.min
      3084 ±  8%     +25.5%       3870 ±  9%  sched_debug.cpu.ttwu_local.avg

The URL of the original regression report email:

https://lists.01.org/pipermail/lkp/2016-January/003442.html

Best Regards,
Huang, Ying

maintainer
#23 lore raw

PATCH 1/1 locking/mutex: Avoid spinner vs waiter starvation

In reply to Ding Tianhong
Quoted 7 lines · Ding Tianhong
Quoted 6 lines · Peter Zijlstra
Quoted 1 line · Ding Tianhong

looks good to me, I will try this solution and report the result, thanks everyone.

Did you get a change to run with this?

Quoted 7 lines
Quoted 2 lines

.

I backport this patch to 3.10 lts kernel, and didn't change any logic, Till now, the patch works fine to me, and no need to change anything, So I think this patch is no problem, could you formal release this patch to the latest kernel? :)

Thanks for testing, I've queued the below patch.

---
Subject: locking/mutex: Avoid spinner vs waiter starvation
From: Peter Zijlstra <[email protected]>
Date: Fri, 22 Jan 2016 12:06:53 +0100

Ding Tianhong reported that under his load the optimistic spinners would totally starve a task that ended up on the wait list.

Fix this by ensuring the top waiter also partakes in the optimistic spin queue.

There are a few subtle differences between the assumed state of regular optimistic spinners and those already on the wait list, which result in the @acquired complication of the acquire path.

Most notable are:

- waiters are on the wait list and need to be taken off
- mutex_optimistic_spin() sets the lock->count to 0 on acquire even though there might be more tasks on the wait list.

---
 kernel/locking/mutex.c |   15 +++++++++++++++
 1 file changed, 15 insertions(+)
kernel/locking/mutex.c +15 -0
@@ -512,6 +512,7 @@ __mutex_lock_common(struct mutex *lock,
 	struct task_struct *task = current;
 	struct mutex_waiter waiter;
 	unsigned long flags;
+	bool acquired;
 	int ret;
 
 	preempt_disable();
@@ -543,6 +544,7 @@ __mutex_lock_common(struct mutex *lock,
 	lock_contended(&lock->dep_map, ip);
 
 	for (;;) {
+		acquired = false;
 		/*
 		 * Lets try to take the lock again - this is needed even if
 		 * we get here for the first time (shortly after failing to
@@ -577,7 +579,16 @@ __mutex_lock_common(struct mutex *lock,
 		/* didn't get the lock, go to sleep: */
 		spin_unlock_mutex(&lock->wait_lock, flags);
 		schedule_preempt_disabled();
+
+		if (mutex_is_locked(lock))
+			acquired = mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx);
+
 		spin_lock_mutex(&lock->wait_lock, flags);
+
+		if (acquired) {
+			atomic_set(&lock->count, -1);
+			break;
+		}
 	}
 	__set_task_state(task, TASK_RUNNING);
 
@@ -587,6 +598,9 @@ __mutex_lock_common(struct mutex *lock,
 		atomic_set(&lock->count, 0);
 	debug_mutex_free_waiter(&waiter);
 
+	if (acquired)
+		goto unlock;
+
 skip_wait:
 	/* got the lock - cleanup and rejoice! */
 	lock_acquired(&lock->dep_map, ip);
@@ -597,6 +611,7 @@ __mutex_lock_common(struct mutex *lock,
 		ww_mutex_set_context_slowpath(ww, ww_ctx);
 	}
 
+unlock:
 	spin_unlock_mutex(&lock->wait_lock, flags);
 	preempt_enable();
 	return 0;
#24 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Quoted 14 lines · Peter Zijlstra

Subject: locking/mutex: Avoid spinner vs waiter starvation
From: Peter Zijlstra <[email protected]>
Date: Fri, 22 Jan 2016 12:06:53 +0100

Ding Tianhong reported that under his load the optimistic spinners would totally starve a task that ended up on the wait list.

Fix this by ensuring the top waiter also partakes in the optimistic spin queue.

There are a few subtle differences between the assumed state of regular optimistic spinners and those already on the wait list, which result in the @acquired complication of the acquire path.

Quoted 5 lines

Most notable are:

- waiters are on the wait list and need to be taken off
- mutex_optimistic_spin() sets the lock->count to 0 on acquire even though there might be more tasks on the wait list.

Right, the main impact I see with these complications are that the window of when a waiter takes the lock via spinning and then acquires the wait_lock to remove itself from the list, will allow an unlock thread to set the lock as available in the fastpath which could in turn allow a third thread the steal the lock. With high contention, this window will be come obviously larger as we contend for the wait_lock.

CPU-0 CPU-1 CPU-3
__mutex_lock_common
mutex_optimistic_spin
(->count now 0)

			__mutex_fastpath_unlock
			(->count now 1)				 __mutex_fastpath_lock
				     				 (stolen)

spin_lock_mutex(&lock->wait_lock, flags);

But we've always been bad when it comes to counter and waiters.

Thanks,
Davidlohr

#25 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Quoted 33 lines · Davidlohr Bueso
Quoted 14 lines · Peter Zijlstra

Subject: locking/mutex: Avoid spinner vs waiter starvation
From: Peter Zijlstra <[email protected]>
Date: Fri, 22 Jan 2016 12:06:53 +0100

Ding Tianhong reported that under his load the optimistic spinners would totally starve a task that ended up on the wait list.

Fix this by ensuring the top waiter also partakes in the optimistic spin queue.

There are a few subtle differences between the assumed state of regular optimistic spinners and those already on the wait list, which result in the @acquired complication of the acquire path.

Quoted 5 lines

Most notable are:

- waiters are on the wait list and need to be taken off
- mutex_optimistic_spin() sets the lock->count to 0 on acquire even though there might be more tasks on the wait list.

Right, the main impact I see with these complications are that the window of when a waiter takes the lock via spinning and then acquires the wait_lock to remove itself from the list, will allow an unlock thread to set the lock as available in the fastpath which could in turn allow a third thread the steal the lock. With high contention, this window will be come obviously larger as we contend for the wait_lock.

CPU-0 CPU-1 CPU-3
__mutex_lock_common mutex_optimistic_spin
(->count now 0)

Quoted 8 lines
            __mutex_fastpath_unlock
            (->count now 1)                 __mutex_fastpath_lock
                                      (stolen)

spin_lock_mutex(&lock->wait_lock, flags);

But we've always been bad when it comes to counter and waiters.

Agree, but this patch is going to help the waiter in the wait list to get the lock, your scene probability looks more too low and I don't think it is a problem.

Thanks
Ding

Quoted 5 lines

Thanks,
Davidlohr

.

#26 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Quoted 2 lines · Ding Tianhong

Agree, but this patch is going to help the waiter in the wait list to get the lock, your scene probability looks more too low and I don't think it is a problem.

Sure, I was in fact implying its not the end of the world, although it will be interesting to see the impact on different (non pathological) workloads, even if it only affects a single waiter. Also, technically this issue can also affect rwsems if only using writers, but that's obviously pretty idiotic, so I wouldn't worry about it.

Thanks,
Davidlohr

#27 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Quoted 12 lines · Davidlohr Bueso
Quoted 2 lines · Ding Tianhong

Agree, but this patch is going to help the waiter in the wait list to get the lock, your scene probability looks more too low and I don't think it is a problem.

Sure, I was in fact implying its not the end of the world, although it will be interesting to see the impact on different (non pathological) workloads, even if it only affects a single waiter. Also, technically this issue can also affect rwsems if only using writers, but that's obviously pretty idiotic, so I wouldn't worry about it.

Quoted 3 lines

Thanks,
Davidlohr

Hi Davidlohr, Peter:

According Davidlohr's suggestion, I use several VM to test Peter's patch, and sadly I found one VM still happen Hung Task for this problem, so I think we still need to think more about this solution.

Thanks.
Ding

Quoted 2 lines

.

#28 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Quoted 15 lines · Ding Tianhong
Quoted 11 lines · Davidlohr Bueso
Quoted 2 lines · Ding Tianhong

Agree, but this patch is going to help the waiter in the wait list to get the lock, your scene probability looks more too low and I don't think it is a problem.

Sure, I was in fact implying its not the end of the world, although it will be interesting to see the impact on different (non pathological) workloads, even if it only affects a single waiter. Also, technically this issue can also affect rwsems if only using writers, but that's obviously pretty idiotic, so I wouldn't worry about it.

Quoted 3 lines

Thanks,
Davidlohr

Quoted 7 lines

Hi Davidlohr, Peter:

According Davidlohr's suggestion, I use several VM to test Peter's patch, and sadly I found one VM still happen Hung Task for this problem, so I think we still need to think more about this solution.

Thanks.
Ding

After thinking about it, it may be the case the hung task missed the wakeup. If no new task enters the wait queue again, the hung task will never get woken up. Would you mind trying out my patch to see if you are still seeing hung task or not?

Thanks,
Longman

#29 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

In reply to Davidlohr Bueso
Quoted 37 lines · Davidlohr Bueso
Quoted 14 lines · Peter Zijlstra

Subject: locking/mutex: Avoid spinner vs waiter starvation
From: Peter Zijlstra <[email protected]>
Date: Fri, 22 Jan 2016 12:06:53 +0100

Ding Tianhong reported that under his load the optimistic spinners would totally starve a task that ended up on the wait list.

Fix this by ensuring the top waiter also partakes in the optimistic spin queue.

There are a few subtle differences between the assumed state of regular optimistic spinners and those already on the wait list, which result in the @acquired complication of the acquire path.

Quoted 5 lines

Most notable are:

- waiters are on the wait list and need to be taken off
- mutex_optimistic_spin() sets the lock->count to 0 on acquire even though there might be more tasks on the wait list.

Right, the main impact I see with these complications are that the window of when a waiter takes the lock via spinning and then acquires the wait_lock to remove itself from the list, will allow an unlock thread to set the lock as available in the fastpath which could in turn allow a third thread the steal the lock. With high contention, this window will be come obviously larger as we contend for the wait_lock.

CPU-0 CPU-1 CPU-3
__mutex_lock_common mutex_optimistic_spin
(->count now 0)

            __mutex_fastpath_unlock
            (->count now 1)                 __mutex_fastpath_lock
                                      (stolen)
Quoted 6 lines

spin_lock_mutex(&lock->wait_lock, flags);

But we've always been bad when it comes to counter and waiters.

Thanks,
Davidlohr

I don't quite get how that can happen. CPU0 cannot change the count to 0 unless CPU1, the lock holder, does the unlock first. Once CPU0 sees a count of 1 and change it to 0, it is the lock holder and there can be no other CPU that can do the unlock.

Cheers,
Longman

#30 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

In reply to Peter Zijlstra
Quoted 39 lines · Peter Zijlstra
Quoted 7 lines · Ding Tianhong
Quoted 6 lines · Peter Zijlstra
Quoted 1 line · Ding Tianhong

looks good to me, I will try this solution and report the result, thanks everyone.

Did you get a change to run with this?

Quoted 7 lines
Quoted 2 lines

.

I backport this patch to 3.10 lts kernel, and didn't change any logic, Till now, the patch works fine to me, and no need to change anything, So I think this patch is no problem, could you formal release this patch to the latest kernel? :)

Thanks for testing, I've queued the below patch.

---
Subject: locking/mutex: Avoid spinner vs waiter starvation
From: Peter Zijlstra <[email protected]>
Date: Fri, 22 Jan 2016 12:06:53 +0100

Ding Tianhong reported that under his load the optimistic spinners would totally starve a task that ended up on the wait list.

Fix this by ensuring the top waiter also partakes in the optimistic spin queue.

There are a few subtle differences between the assumed state of regular optimistic spinners and those already on the wait list, which result in the @acquired complication of the acquire path.

Most notable are:

- waiters are on the wait list and need to be taken off
- mutex_optimistic_spin() sets the lock->count to 0 on acquire even though there might be more tasks on the wait list.

Quoted 15 lines

I've done some testing with this patch with some of the AIM7 workloads and found that this reduced throughput by about 10%. The reduction in throughput is expected since spinning as a waiter is less efficient.

Another observation I made is that the top waiter spinners would often times require needing to reschedule before being able to acquire the lock from spinning when there was high contention. A waiter can go into the cycle of spin -> reschedule -> spin -> reschedule. So although the chance of starvation is reduced, this patch doesn't fully address the issue of waiter starvation.

Jason

#31 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Hi, Low,

Quoted 10 lines · Jason Low

I've done some testing with this patch with some of the AIM7 workloads and found that this reduced throughput by about 10%. The reduction in throughput is expected since spinning as a waiter is less efficient.

Another observation I made is that the top waiter spinners would often times require needing to reschedule before being able to acquire the lock from spinning when there was high contention. A waiter can go into the cycle of spin -> reschedule -> spin -> reschedule. So although the chance of starvation is reduced, this patch doesn't fully address the issue of waiter starvation.

Could you share your workload? I want to reproduce it in 0day/LKP+ environment.

Best Regards,
Huang, Ying

#32 lore raw

Re: [PATCH] locking/mutex: Avoid spinner vs waiter starvation

Quoted 7 lines · huang ying

Hi, Low,

Quoted 4 lines · Jason Low

I've done some testing with this patch with some of the AIM7 workloads and found that this reduced throughput by about 10%. The reduction in throughput is expected since spinning as a waiter is less efficient.

Quoted 8 lines
Quoted 6 lines

Another observation I made is that the top waiter spinners would often times require needing to reschedule before being able to acquire the lock from spinning when there was high contention. A waiter can go into the cycle of spin -> reschedule -> spin -> reschedule. So although the chance of starvation is reduced, this patch doesn't fully address the issue of waiter starvation.

Could you share your workload? I want to reproduce it in 0day/LKP+ environment.

CC'ing Scott, who wrote the automation scripts.

Jason

#33 lore raw
In reply to Peter Zijlstra
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines · Waiman Long

This patch attempts to fix this live-lock condition by enabling the a woken task in the wait list to enter optimistic spinning loop itself with precedence over the ones in the OSQ. This should prevent the live-lock
condition from happening.

So I think having the top waiter going back in to contend on the OSQ is an excellent idea, but I'm not sure the wlh_spinning thing is important.

Yes, that is optional. I put it there just to make it is more likely for the waiter spinner to get the lock. Without that, the chance will be 50/50 on average. I can certainly take that out.

Quoted 5 lines

The OSQ itself is FIFO fair, and the waiters retain the wait_list position. So having the top wait_list entry contending on the OSQ ensures we cannot starve (I think).

Also, as Davidlohr said, we cannot copy/paste this much code.

As I said in the previous mail, I do intend to refactor it before sending out the official patch.

Cheers,
Longman