Viewing: range_lock.c
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of Lustre, http://www.lustre.org/
*
* Range lock is used to allow multiple threads writing a single shared
* file given each thread is writing to a non-overlapping portion of the
* file.
*
* Refer to the possible upstream kernel version of range lock by
* Jan Kara <jack@suse.cz>: https://lkml.org/lkml/2013/1/31/480
*
* This file could later replaced by the upstream kernel version.
*
* Author: Prakash Surya <surya1@llnl.gov>
* Author: Bobi Jam <bobijam.xu@intel.com>
*/
#include <linux/sched/signal.h>
#include <linux/interval_tree_generic.h>
#include <uapi/linux/lustre/lustre_user.h>
#include <linux/libcfs/libcfs_debug.h>
#include <linux/libcfs/libcfs_private.h>
#include <range_lock.h>
#define START(node) ((node)->rl_start)
#define LAST(node) ((node)->rl_end)
INTERVAL_TREE_DEFINE(struct range_lock, rl_rb, __u64, rl_subtree_last,
START, LAST, static, range_lock)
/**
* range_lock_tree_init() - Initialize a range lock tree
* @tree: an empty range lock tree
*
* Pre: Caller should have allocated the range lock tree.
* Post: The range lock tree is ready to function.
*/
void range_lock_tree_init(struct range_lock_tree *tree)
{
tree->rlt_root = RB_ROOT_CACHED;
tree->rlt_sequence = 0;
spin_lock_init(&tree->rlt_lock);
}
EXPORT_SYMBOL(range_lock_tree_init);
/**
* range_lock_init() - Intialize a range lock node
* @lock: an empty range lock node
* @start: start of the covering region
* @end: end of the covering region
*
* Pre: Caller should have allocated the range lock node.
* Post: The range lock node is meant to cover [start, end] region
*/
void range_lock_init(struct range_lock *lock, __u64 start, __u64 end)
{
start >>= PAGE_SHIFT;
if (end != LUSTRE_EOF)
end >>= PAGE_SHIFT;
lock->rl_start = start;
lock->rl_end = end;
lock->rl_task = NULL;
lock->rl_blocking_ranges = 0;
lock->rl_sequence = 0;
}
EXPORT_SYMBOL(range_lock_init);
/**
* range_unlock() - Unlock a range lock, wake up locks blocked by this lock.
* @tree: range lock tree
* @lock: range lock to be deleted
*
* If this lock has been granted, relase it; if not, just delete it from
* the tree or the same region lock list. Wake up those locks only blocked
* by this lock.
*/
void range_unlock(struct range_lock_tree *tree, struct range_lock *lock)
{
struct range_lock *overlap;
ENTRY;
spin_lock(&tree->rlt_lock);
range_lock_remove(lock, &tree->rlt_root);
for (overlap = range_lock_iter_first(&tree->rlt_root,
lock->rl_start,
lock->rl_end);
overlap;
overlap = range_lock_iter_next(overlap,
lock->rl_start,
lock->rl_end))
if (overlap->rl_sequence > lock->rl_sequence) {
--overlap->rl_blocking_ranges;
if (overlap->rl_blocking_ranges == 0)
wake_up_process(overlap->rl_task);
}
spin_unlock(&tree->rlt_lock);
EXIT;
}
EXPORT_SYMBOL(range_unlock);
/**
* range_lock() - Lock a region
* @tree: range lock tree
* @lock: range lock node containing the region span
*
* If there exists overlapping range lock, the new lock will wait and
* retry, if later it find that it is not the chosen one to wake up,
* it wait again.
*
* Return:
* * %0 get the range lock
* * %<0 error code while not getting the range lock
*/
int range_lock(struct range_lock_tree *tree, struct range_lock *lock)
{
struct range_lock *overlap;
int rc = 0;
ENTRY;
spin_lock(&tree->rlt_lock);
/*
* We need to check for all conflicting intervals
* already in the tree.
*/
for (overlap = range_lock_iter_first(&tree->rlt_root,
lock->rl_start,
lock->rl_end);
overlap;
overlap = range_lock_iter_next(overlap,
lock->rl_start,
lock->rl_end))
lock->rl_blocking_ranges += 1;
range_lock_insert(lock, &tree->rlt_root);
lock->rl_sequence = ++tree->rlt_sequence;
while (lock->rl_blocking_ranges > 0) {
lock->rl_task = current;
__set_current_state(TASK_INTERRUPTIBLE);
spin_unlock(&tree->rlt_lock);
schedule();
if (fatal_signal_pending(current)) {
range_unlock(tree, lock);
GOTO(out, rc = -ERESTARTSYS);
}
spin_lock(&tree->rlt_lock);
}
spin_unlock(&tree->rlt_lock);
out:
RETURN(rc);
}
EXPORT_SYMBOL(range_lock);