Skip to content

Commit

Permalink
bcachefs: six locks: Simplify optimistic spinning
Browse files Browse the repository at this point in the history
osq lock maintainers don't want it to be used outside of kernel/locking/
- but, we can do better.

Since we have lock handoff signalled via waitlist entries, there's no
reason for optimistic spinning to have to look at the lock at all -
aside from checking lock-owner; we can just spin looking at our waitlist
entry.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
  • Loading branch information
Kent Overstreet committed Nov 5, 2023
1 parent 63b6800 commit 07e4a9f
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 92 deletions.
117 changes: 32 additions & 85 deletions fs/bcachefs/six.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,101 +321,57 @@ bool six_relock_ip(struct six_lock *lock, enum six_lock_type type,
}
EXPORT_SYMBOL_GPL(six_relock_ip);

#ifdef CONFIG_SIX_LOCK_SPIN_ON_OWNER
#ifdef CONFIG_LOCK_SPIN_ON_OWNER

static inline bool six_can_spin_on_owner(struct six_lock *lock)
static inline bool six_owner_running(struct six_lock *lock)
{
struct task_struct *owner;
bool ret;

if (need_resched())
return false;

/*
* When there's no owner, we might have preempted between the owner
* acquiring the lock and setting the owner field. If we're an RT task
* that will live-lock because we won't let the owner complete.
*/
rcu_read_lock();
owner = READ_ONCE(lock->owner);
ret = !owner || owner_on_cpu(owner);
struct task_struct *owner = READ_ONCE(lock->owner);
bool ret = owner ? owner_on_cpu(owner) : !rt_task(current);
rcu_read_unlock();

return ret;
}

static inline bool six_spin_on_owner(struct six_lock *lock,
struct task_struct *owner,
u64 end_time)
static inline bool six_optimistic_spin(struct six_lock *lock,
struct six_lock_waiter *wait,
enum six_lock_type type)
{
bool ret = true;
unsigned loop = 0;

rcu_read_lock();
while (lock->owner == owner) {
/*
* Ensure we emit the owner->on_cpu, dereference _after_
* checking lock->owner still matches owner. If that fails,
* owner might point to freed memory. If it still matches,
* the rcu_read_lock() ensures the memory stays valid.
*/
barrier();

if (!owner_on_cpu(owner) || need_resched()) {
ret = false;
break;
}

if (!(++loop & 0xf) && (time_after64(sched_clock(), end_time))) {
six_set_bitmask(lock, SIX_LOCK_NOSPIN);
ret = false;
break;
}

cpu_relax();
}
rcu_read_unlock();

return ret;
}

static inline bool six_optimistic_spin(struct six_lock *lock, enum six_lock_type type)
{
struct task_struct *task = current;
u64 end_time;

if (type == SIX_LOCK_write)
return false;

preempt_disable();
if (!six_can_spin_on_owner(lock))
goto fail;
if (lock->wait_list.next != &wait->list)
return false;

if (!osq_lock(&lock->osq))
goto fail;
if (atomic_read(&lock->state) & SIX_LOCK_NOSPIN)
return false;

preempt_disable();
end_time = sched_clock() + 10 * NSEC_PER_USEC;

while (1) {
struct task_struct *owner;

while (!need_resched() && six_owner_running(lock)) {
/*
* If there's an owner, wait for it to either
* release the lock or go to sleep.
* Ensures that writes to the waitlist entry happen after we see
* wait->lock_acquired: pairs with the smp_store_release in
* __six_lock_wakeup
*/
owner = READ_ONCE(lock->owner);
if (owner && !six_spin_on_owner(lock, owner, end_time))
break;

if (do_six_trylock(lock, type, false)) {
osq_unlock(&lock->osq);
if (smp_load_acquire(&wait->lock_acquired)) {
preempt_enable();
return true;
}

/*
* When there's no owner, we might have preempted between the
* owner acquiring the lock and setting the owner field. If
* we're an RT task that will live-lock because we won't let
* the owner complete.
*/
if (!owner && (need_resched() || rt_task(task)))
if (!(++loop & 0xf) && (time_after64(sched_clock(), end_time))) {
six_set_bitmask(lock, SIX_LOCK_NOSPIN);
break;
}

/*
* The cpu_relax() call is a compiler barrier which forces
Expand All @@ -426,24 +382,15 @@ static inline bool six_optimistic_spin(struct six_lock *lock, enum six_lock_type
cpu_relax();
}

osq_unlock(&lock->osq);
fail:
preempt_enable();

/*
* If we fell out of the spin path because of need_resched(),
* reschedule now, before we try-lock again. This avoids getting
* scheduled out right after we obtained the lock.
*/
if (need_resched())
schedule();

return false;
}

#else /* CONFIG_SIX_LOCK_SPIN_ON_OWNER */
#else /* CONFIG_LOCK_SPIN_ON_OWNER */

static inline bool six_optimistic_spin(struct six_lock *lock, enum six_lock_type type)
static inline bool six_optimistic_spin(struct six_lock *lock,
struct six_lock_waiter *wait,
enum six_lock_type type)
{
return false;
}
Expand All @@ -467,9 +414,6 @@ static int six_lock_slowpath(struct six_lock *lock, enum six_lock_type type,
trace_contention_begin(lock, 0);
lock_contended(&lock->dep_map, ip);

if (six_optimistic_spin(lock, type))
goto out;

wait->task = current;
wait->lock_want = type;
wait->lock_acquired = false;
Expand Down Expand Up @@ -507,6 +451,9 @@ static int six_lock_slowpath(struct six_lock *lock, enum six_lock_type type,
ret = 0;
}

if (six_optimistic_spin(lock, wait, type))
goto out;

while (1) {
set_current_state(TASK_UNINTERRUPTIBLE);

Expand Down
7 changes: 0 additions & 7 deletions fs/bcachefs/six.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@
#include <linux/sched.h>
#include <linux/types.h>

#ifdef CONFIG_SIX_LOCK_SPIN_ON_OWNER
#include <linux/osq_lock.h>
#endif

enum six_lock_type {
SIX_LOCK_read,
SIX_LOCK_intent,
Expand All @@ -143,9 +139,6 @@ struct six_lock {
unsigned intent_lock_recurse;
struct task_struct *owner;
unsigned __percpu *readers;
#ifdef CONFIG_SIX_LOCK_SPIN_ON_OWNER
struct optimistic_spin_queue osq;
#endif
raw_spinlock_t wait_lock;
struct list_head wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
Expand Down

0 comments on commit 07e4a9f

Please sign in to comment.