* [PATCH v4 1/5] locking/rwsem: Prevent non-first waiter from spinning in down_write() slowpath
2022-10-24 17:44 [PATCH v4 0/5] lockinig/rwsem: Fix rwsem bugs & enable true lock handoff Waiman Long
@ 2022-10-24 17:44 ` Waiman Long
2022-10-24 17:44 ` [PATCH v4 2/5] locking/rwsem: Limit # of null owner retries for handoff writer Waiman Long
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Waiman Long @ 2022-10-24 17:44 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, john.p.donnelly, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Waiman Long, stable
A non-first waiter can potentially spin in the for loop of
rwsem_down_write_slowpath() without sleeping but fail to acquire the
lock even if the rwsem is free if the following sequence happens:
Non-first RT waiter First waiter Lock holder
------------------- ------------ -----------
Acquire wait_lock
rwsem_try_write_lock():
Set handoff bit if RT or
wait too long
Set waiter->handoff_set
Release wait_lock
Acquire wait_lock
Inherit waiter->handoff_set
Release wait_lock
Clear owner
Release lock
if (waiter.handoff_set) {
rwsem_spin_on_owner(();
if (OWNER_NULL)
goto trylock_again;
}
trylock_again:
Acquire wait_lock
rwsem_try_write_lock():
if (first->handoff_set && (waiter != first))
return false;
Release wait_lock
A non-first waiter cannot really acquire the rwsem even if it mistakenly
believes that it can spin on OWNER_NULL value. If that waiter happens
to be an RT task running on the same CPU as the first waiter, it can
block the first waiter from acquiring the rwsem leading to live lock.
Fix this problem by making sure that a non-first waiter cannot spin in
the slowpath loop without sleeping.
Fixes: d257cc8cb8d5 ("locking/rwsem: Make handoff bit handling more consistent")
Reviewed-and-tested-by: Mukesh Ojha <quic_mojha@quicinc.com>
Signed-off-by: Waiman Long <longman@redhat.com>
Cc: stable@vger.kernel.org
---
kernel/locking/rwsem.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c
index 44873594de03..be2df9ea7c30 100644
--- a/kernel/locking/rwsem.c
+++ b/kernel/locking/rwsem.c
@@ -624,18 +624,16 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
*/
if (first->handoff_set && (waiter != first))
return false;
-
- /*
- * First waiter can inherit a previously set handoff
- * bit and spin on rwsem if lock acquisition fails.
- */
- if (waiter == first)
- waiter->handoff_set = true;
}
new = count;
if (count & RWSEM_LOCK_MASK) {
+ /*
+ * A waiter (first or not) can set the handoff bit
+ * if it is an RT task or wait in the wait queue
+ * for too long.
+ */
if (has_handoff || (!rt_task(waiter->task) &&
!time_after(jiffies, waiter->timeout)))
return false;
@@ -651,11 +649,12 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
} while (!atomic_long_try_cmpxchg_acquire(&sem->count, &count, new));
/*
- * We have either acquired the lock with handoff bit cleared or
- * set the handoff bit.
+ * We have either acquired the lock with handoff bit cleared or set
+ * the handoff bit. Only the first waiter can have its handoff_set
+ * set here to enable optimistic spinning in slowpath loop.
*/
if (new & RWSEM_FLAG_HANDOFF) {
- waiter->handoff_set = true;
+ first->handoff_set = true;
lockevent_inc(rwsem_wlock_handoff);
return false;
}
--
2.31.1
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v4 2/5] locking/rwsem: Limit # of null owner retries for handoff writer
2022-10-24 17:44 [PATCH v4 0/5] lockinig/rwsem: Fix rwsem bugs & enable true lock handoff Waiman Long
2022-10-24 17:44 ` [PATCH v4 1/5] locking/rwsem: Prevent non-first waiter from spinning in down_write() slowpath Waiman Long
@ 2022-10-24 17:44 ` Waiman Long
2022-10-24 17:44 ` [PATCH v4 3/5] locking/rwsem: Change waiter->hanodff_set to a handoff_state enum Waiman Long
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Waiman Long @ 2022-10-24 17:44 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, john.p.donnelly, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Waiman Long, stable
Commit 91d2a812dfb9 ("locking/rwsem: Make handoff writer optimistically
spin on owner") assumes that when the owner field is changed to NULL,
the lock will become free soon. Commit 48dfb5d2560d ("locking/rwsem:
Disable preemption while trying for rwsem lock") disable preemption
when acquiring rwsem for write. However, preemption has not yet been
disabled when acquiring a read lock on a rwsem. So a reader can add a
RWSEM_READER_BIAS to count without setting owner to signal a reader,
got preempted out by a RT task which then spins in the writer slowpath
as owner remains NULL leading to live lock.
One way to fix that is to disable preemption before the read lock attempt
and then immediately remove RWSEM_READER_BIAS when the trylock fails
before reenabling preemption. This will remove some optimizations that
can be done by delaying the RWSEM_READER_BIAS backoff. Alternatively
we could delay the preempt_enable() into the rwsem_down_read_slowpath()
and even after acquiring and releasing the wait_lock. Another possible
alternative is to limit the number of trylock attempts without sleeping.
The last alternative seems to be less messy and is being implemented
in this patch.
The current limit is now set to 8 to allow enough time for the other
task to hopefully complete its action.
By adding new lock events to track the number of NULL owner retries with
handoff flag set before a successful trylock when running a 96 threads
locking microbenchmark with equal number of readers and writers running
on a 2-core 96-thread system for 15 seconds, the following stats are
obtained. Note that none of locking threads are RT tasks.
Retries of successful trylock Count
----------------------------- -----
1 1738
2 19
3 11
4 2
5 1
6 1
7 1
8 0
X 1
The last row is the one failed attempt that needs more than 8 retries.
So a retry count maximum of 8 should capture most of them if no RT task
is in the mix.
Fixes: 91d2a812dfb9 ("locking/rwsem: Make handoff writer optimistically spin on owner")
Reported-by: Mukesh Ojha <quic_mojha@quicinc.com>
Signed-off-by: Waiman Long <longman@redhat.com>
Reviewed-and-tested-by: Mukesh Ojha <quic_mojha@quicinc.com>
Cc: stable@vger.kernel.org
---
kernel/locking/rwsem.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c
index be2df9ea7c30..c68d76fc8c68 100644
--- a/kernel/locking/rwsem.c
+++ b/kernel/locking/rwsem.c
@@ -1115,6 +1115,7 @@ static struct rw_semaphore __sched *
rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
{
struct rwsem_waiter waiter;
+ int null_owner_retries;
DEFINE_WAKE_Q(wake_q);
/* do optimistic spinning and steal lock if possible */
@@ -1156,7 +1157,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
set_current_state(state);
trace_contention_begin(sem, LCB_F_WRITE);
- for (;;) {
+ for (null_owner_retries = 0;;) {
if (rwsem_try_write_lock(sem, &waiter)) {
/* rwsem_try_write_lock() implies ACQUIRE on success */
break;
@@ -1182,8 +1183,21 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
owner_state = rwsem_spin_on_owner(sem);
preempt_enable();
- if (owner_state == OWNER_NULL)
+ /*
+ * owner is NULL doesn't guarantee the lock is free.
+ * An incoming reader will temporarily increment the
+ * reader count without changing owner and the
+ * rwsem_try_write_lock() will fails if the reader
+ * is not able to decrement it in time. Allow 8
+ * trylock attempts when hitting a NULL owner before
+ * going to sleep.
+ */
+ if ((owner_state == OWNER_NULL) &&
+ (null_owner_retries < 8)) {
+ null_owner_retries++;
goto trylock_again;
+ }
+ null_owner_retries = 0;
}
schedule();
--
2.31.1
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v4 3/5] locking/rwsem: Change waiter->hanodff_set to a handoff_state enum
2022-10-24 17:44 [PATCH v4 0/5] lockinig/rwsem: Fix rwsem bugs & enable true lock handoff Waiman Long
2022-10-24 17:44 ` [PATCH v4 1/5] locking/rwsem: Prevent non-first waiter from spinning in down_write() slowpath Waiman Long
2022-10-24 17:44 ` [PATCH v4 2/5] locking/rwsem: Limit # of null owner retries for handoff writer Waiman Long
@ 2022-10-24 17:44 ` Waiman Long
2022-10-24 18:25 ` john.p.donnelly
2022-10-24 17:44 ` [PATCH v4 4/5] locking/rwsem: Enable direct rwsem lock handoff Waiman Long
2022-10-24 17:44 ` [PATCH v4 5/5] locking/rwsem: Update handoff lock events tracking Waiman Long
4 siblings, 1 reply; 8+ messages in thread
From: Waiman Long @ 2022-10-24 17:44 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, john.p.donnelly, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Waiman Long
Change the boolean waiter->handoff_set to an enum type so that we can
have more states in some later patches. Also use READ_ONCE() outside
wait_lock critical sections for read and WRITE_ONCE() inside wait_lock
critical sections for write for proper synchronization. There is no
functional change.
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/locking/rwsem.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c
index c68d76fc8c68..a8bfc905637a 100644
--- a/kernel/locking/rwsem.c
+++ b/kernel/locking/rwsem.c
@@ -338,12 +338,17 @@ enum rwsem_waiter_type {
RWSEM_WAITING_FOR_READ
};
+enum rwsem_handoff_state {
+ HANDOFF_NONE = 0,
+ HANDOFF_REQUESTED,
+};
+
struct rwsem_waiter {
struct list_head list;
struct task_struct *task;
enum rwsem_waiter_type type;
+ enum rwsem_handoff_state handoff_state;
unsigned long timeout;
- bool handoff_set;
};
#define rwsem_first_waiter(sem) \
list_first_entry(&sem->wait_list, struct rwsem_waiter, list)
@@ -470,7 +475,7 @@ static void rwsem_mark_wake(struct rw_semaphore *sem,
adjustment -= RWSEM_FLAG_HANDOFF;
lockevent_inc(rwsem_rlock_handoff);
}
- waiter->handoff_set = true;
+ WRITE_ONCE(waiter->handoff_state, HANDOFF_REQUESTED);
}
atomic_long_add(-adjustment, &sem->count);
@@ -622,7 +627,7 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
* waiter is the one that set it. Otherwisee, we
* still try to acquire the rwsem.
*/
- if (first->handoff_set && (waiter != first))
+ if (first->handoff_state && (waiter != first))
return false;
}
@@ -650,11 +655,11 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
/*
* We have either acquired the lock with handoff bit cleared or set
- * the handoff bit. Only the first waiter can have its handoff_set
+ * the handoff bit. Only the first waiter can have its handoff_state
* set here to enable optimistic spinning in slowpath loop.
*/
if (new & RWSEM_FLAG_HANDOFF) {
- first->handoff_set = true;
+ WRITE_ONCE(first->handoff_state, HANDOFF_REQUESTED);
lockevent_inc(rwsem_wlock_handoff);
return false;
}
@@ -1043,7 +1048,7 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int stat
waiter.task = current;
waiter.type = RWSEM_WAITING_FOR_READ;
waiter.timeout = jiffies + RWSEM_WAIT_TIMEOUT;
- waiter.handoff_set = false;
+ waiter.handoff_state = HANDOFF_NONE;
raw_spin_lock_irq(&sem->wait_lock);
if (list_empty(&sem->wait_list)) {
@@ -1131,7 +1136,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
waiter.task = current;
waiter.type = RWSEM_WAITING_FOR_WRITE;
waiter.timeout = jiffies + RWSEM_WAIT_TIMEOUT;
- waiter.handoff_set = false;
+ waiter.handoff_state = HANDOFF_NONE;
raw_spin_lock_irq(&sem->wait_lock);
rwsem_add_waiter(sem, &waiter);
@@ -1176,7 +1181,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
* In this case, we attempt to acquire the lock again
* without sleeping.
*/
- if (waiter.handoff_set) {
+ if (READ_ONCE(waiter.handoff_state)) {
enum owner_state owner_state;
preempt_disable();
--
2.31.1
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 3/5] locking/rwsem: Change waiter->hanodff_set to a handoff_state enum
2022-10-24 17:44 ` [PATCH v4 3/5] locking/rwsem: Change waiter->hanodff_set to a handoff_state enum Waiman Long
@ 2022-10-24 18:25 ` john.p.donnelly
2022-10-24 18:46 ` Waiman Long
0 siblings, 1 reply; 8+ messages in thread
From: john.p.donnelly @ 2022-10-24 18:25 UTC (permalink / raw)
To: Waiman Long, Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Jack Vogel, Jorge Lopez, Vijayendra Suman, John Donnelly
On 10/24/22 12:44 PM, Waiman Long wrote:
> Change the boolean waiter->handoff_set to an enum type so that we can
> have more states in some later patches. Also use READ_ONCE() outside
> wait_lock critical sections for read and WRITE_ONCE() inside wait_lock
> critical sections for write for proper synchronization. There is no
> functional change.
Hi,
Do we need
Fixes: d257cc8cb8d5 ("locking/rwsem: Make handoff bit handling more
consistent")
Fixes: 91d2a812dfb9 ("locking/rwsem: Make handoff writer optimistically
spin on owner")
Cc: stable@vger.kernel.org
clauses added to patches 3,4,5 so they are all picked up in one series ?
Thank you,
John.
>
> Signed-off-by: Waiman Long <longman@redhat.com>
> ---
> kernel/locking/rwsem.c | 21 +++++++++++++--------
> 1 file changed, 13 insertions(+), 8 deletions(-)
>
> diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c
> index c68d76fc8c68..a8bfc905637a 100644
> --- a/kernel/locking/rwsem.c
> +++ b/kernel/locking/rwsem.c
> @@ -338,12 +338,17 @@ enum rwsem_waiter_type {
> RWSEM_WAITING_FOR_READ
> };
>
> +enum rwsem_handoff_state {
> + HANDOFF_NONE = 0,
> + HANDOFF_REQUESTED,
> +};
> +
> struct rwsem_waiter {
> struct list_head list;
> struct task_struct *task;
> enum rwsem_waiter_type type;
> + enum rwsem_handoff_state handoff_state;
> unsigned long timeout;
> - bool handoff_set;
> };
> #define rwsem_first_waiter(sem) \
> list_first_entry(&sem->wait_list, struct rwsem_waiter, list)
> @@ -470,7 +475,7 @@ static void rwsem_mark_wake(struct rw_semaphore *sem,
> adjustment -= RWSEM_FLAG_HANDOFF;
> lockevent_inc(rwsem_rlock_handoff);
> }
> - waiter->handoff_set = true;
> + WRITE_ONCE(waiter->handoff_state, HANDOFF_REQUESTED);
> }
>
> atomic_long_add(-adjustment, &sem->count);
> @@ -622,7 +627,7 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
> * waiter is the one that set it. Otherwisee, we
> * still try to acquire the rwsem.
> */
> - if (first->handoff_set && (waiter != first))
> + if (first->handoff_state && (waiter != first))
> return false;
> }
>
> @@ -650,11 +655,11 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
>
> /*
> * We have either acquired the lock with handoff bit cleared or set
> - * the handoff bit. Only the first waiter can have its handoff_set
> + * the handoff bit. Only the first waiter can have its handoff_state
> * set here to enable optimistic spinning in slowpath loop.
> */
> if (new & RWSEM_FLAG_HANDOFF) {
> - first->handoff_set = true;
> + WRITE_ONCE(first->handoff_state, HANDOFF_REQUESTED);
> lockevent_inc(rwsem_wlock_handoff);
> return false;
> }
> @@ -1043,7 +1048,7 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int stat
> waiter.task = current;
> waiter.type = RWSEM_WAITING_FOR_READ;
> waiter.timeout = jiffies + RWSEM_WAIT_TIMEOUT;
> - waiter.handoff_set = false;
> + waiter.handoff_state = HANDOFF_NONE;
>
> raw_spin_lock_irq(&sem->wait_lock);
> if (list_empty(&sem->wait_list)) {
> @@ -1131,7 +1136,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
> waiter.task = current;
> waiter.type = RWSEM_WAITING_FOR_WRITE;
> waiter.timeout = jiffies + RWSEM_WAIT_TIMEOUT;
> - waiter.handoff_set = false;
> + waiter.handoff_state = HANDOFF_NONE;
>
> raw_spin_lock_irq(&sem->wait_lock);
> rwsem_add_waiter(sem, &waiter);
> @@ -1176,7 +1181,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
> * In this case, we attempt to acquire the lock again
> * without sleeping.
> */
> - if (waiter.handoff_set) {
> + if (READ_ONCE(waiter.handoff_state)) {
> enum owner_state owner_state;
>
> preempt_disable();
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 3/5] locking/rwsem: Change waiter->hanodff_set to a handoff_state enum
2022-10-24 18:25 ` john.p.donnelly
@ 2022-10-24 18:46 ` Waiman Long
0 siblings, 0 replies; 8+ messages in thread
From: Waiman Long @ 2022-10-24 18:46 UTC (permalink / raw)
To: john.p.donnelly, Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Jack Vogel, Jorge Lopez, Vijayendra Suman
On 10/24/22 14:25, john.p.donnelly@oracle.com wrote:
> On 10/24/22 12:44 PM, Waiman Long wrote:
>> Change the boolean waiter->handoff_set to an enum type so that we can
>> have more states in some later patches. Also use READ_ONCE() outside
>> wait_lock critical sections for read and WRITE_ONCE() inside wait_lock
>> critical sections for write for proper synchronization. There is no
>> functional change.
>
> Hi,
>
> Do we need
>
>
> Fixes: d257cc8cb8d5 ("locking/rwsem: Make handoff bit handling more
> consistent")
>
> Fixes: 91d2a812dfb9 ("locking/rwsem: Make handoff writer
> optimistically spin on owner")
>
> Cc: stable@vger.kernel.org
>
>
> clauses added to patches 3,4,5 so they are all picked up in one series ?
>
> Thank you,
>
> John.
I am not planning to do that. The handoff code rewrite represent a
moderate amount of changes. So I don't want them to be backported to
stable in case there are some hidden bugs inside. That is why I have
patch 2 is essentially reverted in patch 4.
Cheers,
Longman
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 4/5] locking/rwsem: Enable direct rwsem lock handoff
2022-10-24 17:44 [PATCH v4 0/5] lockinig/rwsem: Fix rwsem bugs & enable true lock handoff Waiman Long
` (2 preceding siblings ...)
2022-10-24 17:44 ` [PATCH v4 3/5] locking/rwsem: Change waiter->hanodff_set to a handoff_state enum Waiman Long
@ 2022-10-24 17:44 ` Waiman Long
2022-10-24 17:44 ` [PATCH v4 5/5] locking/rwsem: Update handoff lock events tracking Waiman Long
4 siblings, 0 replies; 8+ messages in thread
From: Waiman Long @ 2022-10-24 17:44 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, john.p.donnelly, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Waiman Long
The lock handoff provided in rwsem isn't a true handoff like that in
the mutex. Instead, it is more like a quiescent state where optimistic
spinning and lock stealing are disabled to make it easier for the first
waiter to acquire the lock.
Reworking the code to enable a true lock handoff is more complex due to
the following facts:
1) The RWSEM_FLAG_HANDOFF bit is protected by the wait_lock and it
is too expensive to always take the wait_lock in the unlock path
to prevent racing.
2) The reader lock fast path may add a RWSEM_READER_BIAS at the wrong
time to prevent a proper lock handoff from a reader owned rwsem.
A lock handoff can only be initiated when the following conditions are
true:
1) The RWSEM_FLAG_HANDOFF bit is set.
2) The task to do the handoff don't see any other active lock
excluding the lock that it might have held.
The new handoff mechanism performs handoff in rwsem_wakeup() to minimize
overhead. The rwsem count will be known at that point to determine if
handoff should be done. However, there is a small time gap between the
rwsem becomes free and the wait_lock is taken where a reader can come
in and add a RWSEM_READER_BIAS to the count or the current first waiter
can take the rwsem and clear RWSEM_FLAG_HANDOFF in the interim. That
will fail the handoff operation. To handle the former case, a secondary
handoff will also be done in the rwsem_down_read_slowpath() to catch it.
With true lock handoff, there is no need to do a NULL owner spinning
anymore as wakeup will be performed if handoff is possible. So it
is likely that the first waiter won't actually go to sleep even when
schedule() is called in this case.
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/locking/rwsem.c | 141 +++++++++++++++++++++++++++++++----------
1 file changed, 109 insertions(+), 32 deletions(-)
diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c
index a8bfc905637a..287606aee0e6 100644
--- a/kernel/locking/rwsem.c
+++ b/kernel/locking/rwsem.c
@@ -341,6 +341,7 @@ enum rwsem_waiter_type {
enum rwsem_handoff_state {
HANDOFF_NONE = 0,
HANDOFF_REQUESTED,
+ HANDOFF_GRANTED,
};
struct rwsem_waiter {
@@ -489,6 +490,12 @@ static void rwsem_mark_wake(struct rw_semaphore *sem,
*/
owner = waiter->task;
__rwsem_set_reader_owned(sem, owner);
+ } else if (waiter->handoff_state == HANDOFF_GRANTED) {
+ /*
+ * rwsem_handoff() has added to count RWSEM_READER_BIAS of
+ * the first waiter.
+ */
+ adjustment = RWSEM_READER_BIAS;
}
/*
@@ -586,7 +593,7 @@ rwsem_del_wake_waiter(struct rw_semaphore *sem, struct rwsem_waiter *waiter,
struct wake_q_head *wake_q)
__releases(&sem->wait_lock)
{
- bool first = rwsem_first_waiter(sem) == waiter;
+ struct rwsem_waiter *first = rwsem_first_waiter(sem);
wake_q_init(wake_q);
@@ -595,8 +602,21 @@ rwsem_del_wake_waiter(struct rw_semaphore *sem, struct rwsem_waiter *waiter,
* the first waiter, we wake up the remaining waiters as they may
* be eligible to acquire or spin on the lock.
*/
- if (rwsem_del_waiter(sem, waiter) && first)
+ if (rwsem_del_waiter(sem, waiter) && (waiter == first)) {
+ switch (waiter->handoff_state) {
+ case HANDOFF_GRANTED:
+ raw_spin_unlock_irq(&sem->wait_lock);
+ return;
+ case HANDOFF_REQUESTED:
+ /* Pass handoff state to the new first waiter */
+ first = rwsem_first_waiter(sem);
+ WRITE_ONCE(first->handoff_state, HANDOFF_REQUESTED);
+ fallthrough;
+ default:
+ break;
+ }
rwsem_mark_wake(sem, RWSEM_WAKE_ANY, wake_q);
+ }
raw_spin_unlock_irq(&sem->wait_lock);
if (!wake_q_empty(wake_q))
wake_up_q(wake_q);
@@ -764,6 +784,11 @@ rwsem_spin_on_owner(struct rw_semaphore *sem)
owner = rwsem_owner_flags(sem, &flags);
state = rwsem_owner_state(owner, flags);
+
+ /* A handoff may have been granted */
+ if (!flags && (owner == current))
+ return OWNER_NONSPINNABLE;
+
if (state != OWNER_WRITER)
return state;
@@ -977,6 +1002,32 @@ rwsem_spin_on_owner(struct rw_semaphore *sem)
}
#endif
+/*
+ * Hand off the lock to the first waiter
+ */
+static void rwsem_handoff(struct rw_semaphore *sem, long adj,
+ struct wake_q_head *wake_q)
+{
+ struct rwsem_waiter *waiter;
+ enum rwsem_wake_type wake_type;
+
+ lockdep_assert_held(&sem->wait_lock);
+ adj -= RWSEM_FLAG_HANDOFF;
+ waiter = rwsem_first_waiter(sem);
+ WRITE_ONCE(waiter->handoff_state, HANDOFF_GRANTED);
+ if (waiter->type == RWSEM_WAITING_FOR_WRITE) {
+ wake_type = RWSEM_WAKE_ANY;
+ adj += RWSEM_WRITER_LOCKED;
+ atomic_long_set(&sem->owner, (long)waiter->task);
+ } else {
+ wake_type = RWSEM_WAKE_READ_OWNED;
+ adj += RWSEM_READER_BIAS;
+ __rwsem_set_reader_owned(sem, waiter->task);
+ }
+ atomic_long_add(adj, &sem->count);
+ rwsem_mark_wake(sem, wake_type, wake_q);
+}
+
/*
* Prepare to wake up waiter(s) in the wait queue by putting them into the
* given wake_q if the rwsem lock owner isn't a writer. If rwsem is likely
@@ -1051,6 +1102,7 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int stat
waiter.handoff_state = HANDOFF_NONE;
raw_spin_lock_irq(&sem->wait_lock);
+ count = atomic_long_read(&sem->count);
if (list_empty(&sem->wait_list)) {
/*
* In case the wait queue is empty and the lock isn't owned
@@ -1058,7 +1110,7 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int stat
* immediately as its RWSEM_READER_BIAS has already been set
* in the count.
*/
- if (!(atomic_long_read(&sem->count) & RWSEM_WRITER_MASK)) {
+ if (!(count & RWSEM_WRITER_MASK)) {
/* Provide lock ACQUIRE */
smp_acquire__after_ctrl_dep();
raw_spin_unlock_irq(&sem->wait_lock);
@@ -1067,13 +1119,33 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int stat
return sem;
}
adjustment += RWSEM_FLAG_WAITERS;
+ } else if ((count & RWSEM_FLAG_HANDOFF) &&
+ ((count & RWSEM_LOCK_MASK) == RWSEM_READER_BIAS)) {
+ /*
+ * If the waiter to be handed off is a reader, this reader
+ * can piggyback on top of top of that.
+ */
+ if (rwsem_first_waiter(sem)->type == RWSEM_WAITING_FOR_READ)
+ adjustment = 0;
+ rwsem_handoff(sem, adjustment, &wake_q);
+
+ if (!adjustment) {
+ raw_spin_unlock_irq(&sem->wait_lock);
+ wake_up_q(&wake_q);
+ return sem;
+ }
+ adjustment = 0;
}
rwsem_add_waiter(sem, &waiter);
- /* we're now waiting on the lock, but no longer actively locking */
- count = atomic_long_add_return(adjustment, &sem->count);
-
- rwsem_cond_wake_waiter(sem, count, &wake_q);
+ if (adjustment) {
+ /*
+ * We are now waiting on the lock with no handoff, but no
+ * longer actively locking.
+ */
+ count = atomic_long_add_return(adjustment, &sem->count);
+ rwsem_cond_wake_waiter(sem, count, &wake_q);
+ }
raw_spin_unlock_irq(&sem->wait_lock);
if (!wake_q_empty(&wake_q))
@@ -1120,7 +1192,6 @@ static struct rw_semaphore __sched *
rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
{
struct rwsem_waiter waiter;
- int null_owner_retries;
DEFINE_WAKE_Q(wake_q);
/* do optimistic spinning and steal lock if possible */
@@ -1162,7 +1233,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
set_current_state(state);
trace_contention_begin(sem, LCB_F_WRITE);
- for (null_owner_retries = 0;;) {
+ for (;;) {
if (rwsem_try_write_lock(sem, &waiter)) {
/* rwsem_try_write_lock() implies ACQUIRE on success */
break;
@@ -1182,37 +1253,28 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
* without sleeping.
*/
if (READ_ONCE(waiter.handoff_state)) {
- enum owner_state owner_state;
-
- preempt_disable();
- owner_state = rwsem_spin_on_owner(sem);
- preempt_enable();
-
- /*
- * owner is NULL doesn't guarantee the lock is free.
- * An incoming reader will temporarily increment the
- * reader count without changing owner and the
- * rwsem_try_write_lock() will fails if the reader
- * is not able to decrement it in time. Allow 8
- * trylock attempts when hitting a NULL owner before
- * going to sleep.
- */
- if ((owner_state == OWNER_NULL) &&
- (null_owner_retries < 8)) {
- null_owner_retries++;
- goto trylock_again;
+ if (READ_ONCE(waiter.handoff_state) == HANDOFF_REQUESTED) {
+ preempt_disable();
+ rwsem_spin_on_owner(sem);
+ preempt_enable();
}
- null_owner_retries = 0;
+ if (READ_ONCE(waiter.handoff_state) == HANDOFF_GRANTED)
+ goto skip_sleep;
}
schedule();
lockevent_inc(rwsem_sleep_writer);
set_current_state(state);
-trylock_again:
+skip_sleep:
raw_spin_lock_irq(&sem->wait_lock);
+ if (waiter.handoff_state == HANDOFF_GRANTED) {
+ rwsem_del_waiter(sem, &waiter);
+ break;
+ }
}
__set_current_state(TASK_RUNNING);
raw_spin_unlock_irq(&sem->wait_lock);
+out_lock:
lockevent_inc(rwsem_wlock);
trace_contention_end(sem, 0);
return sem;
@@ -1221,6 +1283,9 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
__set_current_state(TASK_RUNNING);
raw_spin_lock_irq(&sem->wait_lock);
rwsem_del_wake_waiter(sem, &waiter, &wake_q);
+ if (unlikely(READ_ONCE(waiter.handoff_state) == HANDOFF_GRANTED))
+ goto out_lock;
+
lockevent_inc(rwsem_wlock_fail);
trace_contention_end(sem, -EINTR);
return ERR_PTR(-EINTR);
@@ -1232,12 +1297,24 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
*/
static struct rw_semaphore *rwsem_wake(struct rw_semaphore *sem)
{
- unsigned long flags;
DEFINE_WAKE_Q(wake_q);
+ unsigned long flags;
+ long count;
raw_spin_lock_irqsave(&sem->wait_lock, flags);
- if (!list_empty(&sem->wait_list))
+ if (list_empty(&sem->wait_list)) {
+ raw_spin_unlock_irqrestore(&sem->wait_lock, flags);
+ return sem;
+ }
+ /*
+ * If the rwsem is free and handoff flag is set with wait_lock held,
+ * no other CPUs can take an active lock.
+ */
+ count = atomic_long_read(&sem->count);
+ if (!(count & RWSEM_LOCK_MASK) && (count & RWSEM_FLAG_HANDOFF))
+ rwsem_handoff(sem, 0, &wake_q);
+ else
rwsem_mark_wake(sem, RWSEM_WAKE_ANY, &wake_q);
raw_spin_unlock_irqrestore(&sem->wait_lock, flags);
--
2.31.1
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v4 5/5] locking/rwsem: Update handoff lock events tracking
2022-10-24 17:44 [PATCH v4 0/5] lockinig/rwsem: Fix rwsem bugs & enable true lock handoff Waiman Long
` (3 preceding siblings ...)
2022-10-24 17:44 ` [PATCH v4 4/5] locking/rwsem: Enable direct rwsem lock handoff Waiman Long
@ 2022-10-24 17:44 ` Waiman Long
4 siblings, 0 replies; 8+ messages in thread
From: Waiman Long @ 2022-10-24 17:44 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng
Cc: linux-kernel, john.p.donnelly, Hillf Danton, Mukesh Ojha,
Ting11 Wang 王婷,
Waiman Long
With the new direct rwsem lock handoff, the corresponding handoff lock
events are updated to also track the number of secondary lock handoffs
in rwsem_down_read_slowpath() to see how prevalent those handoff
events are. The number of primary lock handoffs in the unlock paths is
(rwsem_handoff_read + rwsem_handoff_write - rwsem_handoff_rslow).
After running a 96-thread rwsem microbenchmark with equal number
of readers and writers on a 2-socket 96-thread system for 40s, the
following handoff stats were obtained:
rwsem_handoff_read=189
rwsem_handoff_rslow=1
rwsem_handoff_write=6678
rwsem_handoff_wspin=6681
The number of primary handoffs was 6866, whereas there was only one
secondary handoff for this test run.
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/locking/lock_events_list.h | 6 ++++--
kernel/locking/rwsem.c | 9 +++++----
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/kernel/locking/lock_events_list.h b/kernel/locking/lock_events_list.h
index 97fb6f3f840a..04d101767c2c 100644
--- a/kernel/locking/lock_events_list.h
+++ b/kernel/locking/lock_events_list.h
@@ -63,7 +63,9 @@ LOCK_EVENT(rwsem_rlock) /* # of read locks acquired */
LOCK_EVENT(rwsem_rlock_steal) /* # of read locks by lock stealing */
LOCK_EVENT(rwsem_rlock_fast) /* # of fast read locks acquired */
LOCK_EVENT(rwsem_rlock_fail) /* # of failed read lock acquisitions */
-LOCK_EVENT(rwsem_rlock_handoff) /* # of read lock handoffs */
LOCK_EVENT(rwsem_wlock) /* # of write locks acquired */
LOCK_EVENT(rwsem_wlock_fail) /* # of failed write lock acquisitions */
-LOCK_EVENT(rwsem_wlock_handoff) /* # of write lock handoffs */
+LOCK_EVENT(rwsem_handoff_read) /* # of read lock handoffs */
+LOCK_EVENT(rwsem_handoff_write) /* # of write lock handoffs */
+LOCK_EVENT(rwsem_handoff_rslow) /* # of handoffs in read slowpath */
+LOCK_EVENT(rwsem_handoff_wspin) /* # of handoff spins in write slowpath */
diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c
index 287606aee0e6..46aea1994bf8 100644
--- a/kernel/locking/rwsem.c
+++ b/kernel/locking/rwsem.c
@@ -472,10 +472,8 @@ static void rwsem_mark_wake(struct rw_semaphore *sem,
* force the issue.
*/
if (time_after(jiffies, waiter->timeout)) {
- if (!(oldcount & RWSEM_FLAG_HANDOFF)) {
+ if (!(oldcount & RWSEM_FLAG_HANDOFF))
adjustment -= RWSEM_FLAG_HANDOFF;
- lockevent_inc(rwsem_rlock_handoff);
- }
WRITE_ONCE(waiter->handoff_state, HANDOFF_REQUESTED);
}
@@ -680,7 +678,6 @@ static inline bool rwsem_try_write_lock(struct rw_semaphore *sem,
*/
if (new & RWSEM_FLAG_HANDOFF) {
WRITE_ONCE(first->handoff_state, HANDOFF_REQUESTED);
- lockevent_inc(rwsem_wlock_handoff);
return false;
}
@@ -1019,10 +1016,12 @@ static void rwsem_handoff(struct rw_semaphore *sem, long adj,
wake_type = RWSEM_WAKE_ANY;
adj += RWSEM_WRITER_LOCKED;
atomic_long_set(&sem->owner, (long)waiter->task);
+ lockevent_inc(rwsem_handoff_write);
} else {
wake_type = RWSEM_WAKE_READ_OWNED;
adj += RWSEM_READER_BIAS;
__rwsem_set_reader_owned(sem, waiter->task);
+ lockevent_inc(rwsem_handoff_read);
}
atomic_long_add(adj, &sem->count);
rwsem_mark_wake(sem, wake_type, wake_q);
@@ -1128,6 +1127,7 @@ rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int stat
if (rwsem_first_waiter(sem)->type == RWSEM_WAITING_FOR_READ)
adjustment = 0;
rwsem_handoff(sem, adjustment, &wake_q);
+ lockevent_inc(rwsem_handoff_rslow);
if (!adjustment) {
raw_spin_unlock_irq(&sem->wait_lock);
@@ -1257,6 +1257,7 @@ rwsem_down_write_slowpath(struct rw_semaphore *sem, int state)
preempt_disable();
rwsem_spin_on_owner(sem);
preempt_enable();
+ lockevent_inc(rwsem_handoff_wspin);
}
if (READ_ONCE(waiter.handoff_state) == HANDOFF_GRANTED)
goto skip_sleep;
--
2.31.1
^ permalink raw reply [flat|nested] 8+ messages in thread