Viewing: ldlm_extent.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
* Use is subject to license terms.
*
* Copyright (c) 2010, 2017, Intel Corporation.
*/
/*
* This file is part of Lustre, http://www.lustre.org/
*
* Author: Peter Braam <braam@clusterfs.com>
* Author: Phil Schwan <phil@clusterfs.com>
*/
/*
* This file contains implementation of EXTENT lock type
*
* EXTENT lock type is for locking a contiguous range of values, represented
* by 64-bit starting and ending offsets (inclusive). There are several extent
* lock modes, some of which may be mutually incompatible. Extent locks are
* considered incompatible if their modes are incompatible and their extents
* intersect. See the lock mode compatibility matrix in lustre_dlm.h.
*/
#define DEBUG_SUBSYSTEM S_LDLM
#include <lustre_dlm.h>
#include <obd_support.h>
#include <obd.h>
#include <obd_class.h>
#include <lustre_lib.h>
#include "ldlm_internal.h"
#include <linux/interval_tree_generic.h>
#define START(node) ((node)->l_policy_data.l_extent.start)
#define LAST(node) ((node)->l_policy_data.l_extent.end)
INTERVAL_TREE_DEFINE(struct ldlm_lock, l_rb, u64, l_subtree_last,
START, LAST, static inline, extent);
static inline struct ldlm_lock *extent_next_lock(struct ldlm_lock *lock)
{
lock = list_next_entry(lock, l_same_extent);
if (RB_EMPTY_NODE(&lock->l_rb))
return lock;
return extent_next(lock);
}
static inline struct ldlm_lock *extent_prev_lock(struct ldlm_lock *lock)
{
lock = list_prev_entry(lock, l_same_extent);
if (RB_EMPTY_NODE(&lock->l_rb))
return lock;
return extent_prev(lock);
}
static inline struct ldlm_lock *extent_insert_unique(
struct ldlm_lock *node, struct rb_root_cached *root)
{
struct rb_node **link, *rb_parent = NULL;
u64 start = START(node), last = LAST(node);
struct ldlm_lock *parent;
bool leftmost = true;
link = &root->rb_root.rb_node;
while (*link) {
rb_parent = *link;
parent = rb_entry(rb_parent, struct ldlm_lock, l_rb);
if (parent->l_subtree_last < last)
parent->l_subtree_last = last;
if (start == START(parent)) {
if (last == LAST(parent))
return parent;
if (last < LAST(parent)) {
link = &parent->l_rb.rb_left;
} else {
link = &parent->l_rb.rb_right;
leftmost = false;
}
} else if (start < START(parent))
link = &parent->l_rb.rb_left;
else {
link = &parent->l_rb.rb_right;
leftmost = false;
}
}
node->l_subtree_last = last;
rb_link_node(&node->l_rb, rb_parent, link);
rb_insert_augmented_cached(&node->l_rb, root,
leftmost, &extent_augment);
return NULL;
}
static inline void extent_replace(struct ldlm_lock *in_tree,
struct ldlm_lock *new,
struct rb_root_cached *tree)
{
struct rb_node *p = rb_parent(&in_tree->l_rb);
/* place 'new' in the rbtree replacing in_tree */
new->l_rb.rb_left = in_tree->l_rb.rb_left;
new->l_rb.rb_right = in_tree->l_rb.rb_right;
if (new->l_rb.rb_left)
rb_set_parent_color(new->l_rb.rb_left, &new->l_rb,
rb_color(new->l_rb.rb_left));
if (new->l_rb.rb_right)
rb_set_parent_color(new->l_rb.rb_right, &new->l_rb,
rb_color(new->l_rb.rb_right));
rb_set_parent_color(&new->l_rb, p, rb_color(&in_tree->l_rb));
if (!p)
tree->rb_root.rb_node = &new->l_rb;
else if (p->rb_left == &in_tree->l_rb)
p->rb_left = &new->l_rb;
else
p->rb_right = &new->l_rb;
if (tree->rb_leftmost == &in_tree->l_rb)
tree->rb_leftmost = &new->l_rb;
}
#ifdef CONFIG_LUSTRE_FS_SERVER
# define LDLM_MAX_GROWN_EXTENT (32 * 1024 * 1024 - 1)
/**
* Fix up the ldlm_extent after expanding it.
*
* After expansion has been done, we might still want to do certain adjusting
* based on overall contention of the resource and the like to avoid granting
* overly wide locks.
*/
static void ldlm_extent_internal_policy_fixup(struct ldlm_lock *req,
struct ldlm_extent *new_ex,
int conflicting)
{
enum ldlm_mode req_mode = req->l_req_mode;
__u64 req_start = req->l_req_extent.start;
__u64 req_end = req->l_req_extent.end;
__u64 req_align, mask;
if (conflicting > 32 && (req_mode == LCK_PW || req_mode == LCK_CW)) {
if (req_end < req_start + LDLM_MAX_GROWN_EXTENT)
new_ex->end = min(req_start + LDLM_MAX_GROWN_EXTENT,
new_ex->end);
}
if (new_ex->start == 0 && new_ex->end == OBD_OBJECT_EOF) {
EXIT;
return;
}
/* we need to ensure that the lock extent is properly aligned to what
* the client requested. Also we need to make sure it's also server
* page size aligned otherwise a server page can be covered by two
* write locks.
*/
mask = PAGE_SIZE;
req_align = (req_end + 1) | req_start;
if (req_align != 0 && (req_align & (mask - 1)) == 0) {
while ((req_align & mask) == 0)
mask <<= 1;
}
mask -= 1;
/* We can only shrink the lock, not grow it.
* This should never cause lock to be smaller than requested,
* since requested lock was already aligned on these boundaries.
*/
new_ex->start = ((new_ex->start - 1) | mask) + 1;
new_ex->end = ((new_ex->end + 1) & ~mask) - 1;
LASSERTF(new_ex->start <= req_start,
"mask %#llx grant start %llu req start %llu\n",
mask, new_ex->start, req_start);
LASSERTF(new_ex->end >= req_end,
"mask %#llx grant end %llu req end %llu\n",
mask, new_ex->end, req_end);
}
/**
* Return the maximum extent that:
* - contains the requested extent
* - does not overlap existing conflicting extents outside the requested one
*
* This allows clients to request a small required extent range, but if there
* is no contention on the lock the full lock can be granted to the client.
* This avoids the need for many smaller lock requests to be granted in the
* common (uncontended) case.
*
* Use interval tree to expand the lock extent for granted lock.
*/
static void ldlm_extent_internal_policy_granted(struct ldlm_lock *req,
struct ldlm_extent *new_ex)
{
struct ldlm_resource *res = req->l_resource;
enum ldlm_mode req_mode = req->l_req_mode;
__u64 req_start = req->l_req_extent.start;
__u64 req_end = req->l_req_extent.end;
struct ldlm_interval_tree *tree;
int conflicting = 0;
int idx;
ENTRY;
lockmode_verify(req_mode);
/* Using interval tree to handle the LDLM extent granted locks. */
for (idx = 0; idx < LCK_MODE_NUM; idx++) {
tree = &res->lr_itree[idx];
if (lockmode_compat(tree->lit_mode, req_mode))
continue;
conflicting += tree->lit_size;
if (!RB_EMPTY_ROOT(&tree->lit_root.rb_root)) {
struct ldlm_lock *lck = NULL;
LASSERTF(!extent_iter_first(&tree->lit_root,
req_start, req_end),
"req_mode=%d, start=%llu, end=%llu\n",
req_mode, req_start, req_end);
/*
* If any tree is non-empty we don't bother
* expanding backwards, it won't be worth
* the effort.
*/
new_ex->start = req_start;
if (req_end < (U64_MAX - 1))
/* lck is the lock with the lowest endpoint
* which covers anything after 'req'
*/
lck = extent_iter_first(&tree->lit_root,
req_end + 1, U64_MAX);
if (lck)
new_ex->end = min(new_ex->end, START(lck) - 1);
}
if (new_ex->start == req_start && new_ex->end == req_end)
break;
}
LASSERT(new_ex->start <= req_start);
LASSERT(new_ex->end >= req_end);
ldlm_extent_internal_policy_fixup(req, new_ex, conflicting);
EXIT;
}
/* The purpose of this function is to return:
* - the maximum extent
* - containing the requested extent
* - and not overlapping existing conflicting extents outside the requested one
*/
static void
ldlm_extent_internal_policy_waiting(struct ldlm_lock *req,
struct ldlm_extent *new_ex)
{
struct ldlm_resource *res = req->l_resource;
enum ldlm_mode req_mode = req->l_req_mode;
__u64 req_start = req->l_req_extent.start;
__u64 req_end = req->l_req_extent.end;
struct ldlm_lock *lock;
int conflicting = 0;
ENTRY;
lockmode_verify(req_mode);
/* for waiting locks */
list_for_each_entry(lock, &res->lr_waiting, l_res_link) {
struct ldlm_extent *l_extent = &lock->l_policy_data.l_extent;
/* We already hit the minimum requested size, search no more */
if (new_ex->start == req_start && new_ex->end == req_end) {
EXIT;
return;
}
/* Don't conflict with ourselves */
if (req == lock)
continue;
/* Locks are compatible, overlap doesn't matter
* Until bug 20 is fixed, try to avoid granting overlapping
* locks on one client (they take a long time to cancel)
*/
if (lockmode_compat(lock->l_req_mode, req_mode) &&
lock->l_export != req->l_export)
continue;
/* If this is a high-traffic lock, don't grow downwards at all
* or grow upwards too much
*/
++conflicting;
if (conflicting > 4)
new_ex->start = req_start;
/* If lock doesn't overlap new_ex, skip it. */
if (!ldlm_extent_overlap(l_extent, new_ex))
continue;
/* Locks conflicting in requested extents and we can't satisfy
* both locks, so ignore it. Either we will ping-pong this
* extent (we would regardless of what extent we granted) or
* lock is unused and it shouldn't limit our extent growth.
*/
if (ldlm_extent_overlap(&lock->l_req_extent,
&req->l_req_extent))
continue;
/* We grow extents downwards only as far as they don't overlap
* with already-granted locks, on the assumption that clients
* will be writing beyond the initial requested end and would
* then need to enqueue a new lock beyond previous request.
* l_req_extent->end strictly < req_start, checked above.
*/
if (l_extent->start < req_start && new_ex->start != req_start) {
if (l_extent->end >= req_start)
new_ex->start = req_start;
else
new_ex->start = min(l_extent->end+1, req_start);
}
/* If we need to cancel this lock anyways because our request
* overlaps the granted lock, we grow up to its requested
* extent start instead of limiting this extent, assuming that
* clients are writing forwards and the lock had over grown
* its extent downwards before we enqueued our request.
*/
if (l_extent->end > req_end) {
if (l_extent->start <= req_end)
new_ex->end = max(lock->l_req_extent.start - 1,
req_end);
else
new_ex->end = max(l_extent->start - 1, req_end);
}
}
ldlm_extent_internal_policy_fixup(req, new_ex, conflicting);
EXIT;
}
/* In order to determine the largest possible extent we can grant, we need
* to scan all of the queues.
*/
static void ldlm_extent_policy(struct ldlm_resource *res,
struct ldlm_lock *lock, __u64 *flags)
{
struct ldlm_extent new_ex = { .start = 0, .end = OBD_OBJECT_EOF };
if (lock->l_export == NULL)
/* this is a local lock taken by server (e.g., as a part of
* OST-side locking, or unlink handling). Expansion doesn't
* make a lot of sense for local locks, because they are
* dropped immediately on operation completion and would only
* conflict with other threads.
*/
return;
if (lock->l_policy_data.l_extent.start == 0 &&
lock->l_policy_data.l_extent.end == OBD_OBJECT_EOF)
/* fast-path whole file locks */
return;
/* Because reprocess_queue zeroes flags and uses it to return
* LDLM_FL_LOCK_CHANGED, we must check for the NO_EXPANSION flag
* in the lock flags rather than the 'flags' argument
*/
if (likely(!(lock->l_flags & LDLM_FL_NO_EXPANSION))) {
ldlm_extent_internal_policy_granted(lock, &new_ex);
ldlm_extent_internal_policy_waiting(lock, &new_ex);
} else {
LDLM_DEBUG(lock, "Not expanding manually requested lock");
new_ex.start = lock->l_policy_data.l_extent.start;
new_ex.end = lock->l_policy_data.l_extent.end;
/* In case the request is not on correct boundaries, we call
* fixup. (normally called in ldlm_extent_internal_policy_*)
*/
ldlm_extent_internal_policy_fixup(lock, &new_ex, 0);
}
if (!ldlm_extent_equal(&new_ex, &lock->l_policy_data.l_extent)) {
*flags |= LDLM_FL_LOCK_CHANGED;
lock->l_policy_data.l_extent.start = new_ex.start;
lock->l_policy_data.l_extent.end = new_ex.end;
}
}
static bool ldlm_update_contention(struct ldlm_lock *lock, int contended_locks)
{
struct ldlm_resource *res = lock->l_resource;
struct ldlm_namespace *ns = ldlm_res_to_ns(res);
struct obd_counter_instance *instance = &res->lr_contention_hist;
time64_t now = ktime_get_seconds();
bool contended;
if (CFS_FAIL_CHECK(OBD_FAIL_LDLM_SET_CONTENTION))
return true;
CDEBUG(D_DLMTRACE, "add %d to "DLDLMRES" since=%us v=(%u %u)\n",
contended_locks, PLDLMRES(res),
(u32)(now - instance->oci_last_event_time),
instance->oci_hist[0], instance->oci_hist[1]);
/*
* Patrick: Cancellations due to a conflicting request are the
* problem we're worried about - that's really what contention is trying
* to measure.
* Cancellations (due to a conflict) mean other clients had to wait, and
* the client holding the lock had to flush its data to disk before they
* could do IO. This is what causes performance problems and makes it a
* good idea to switch to DIO.
*/
contended = obd_counter_add_test(instance, now, contended_locks,
ns->ns_contention_seconds,
ns->ns_contended_locks,
ns->ns_contention_hold_seconds);
if (contended_locks > 0 && contended)
atomic_inc(&ns->ns_contention_events);
RETURN(contended);
}
struct ldlm_extent_compat_args {
struct list_head *work_list;
struct ldlm_lock *lock;
enum ldlm_mode mode;
int *locks;
int *compat;
};
static bool ldlm_extent_compat_cb(struct ldlm_lock *lock, void *data)
{
struct ldlm_extent_compat_args *priv = data;
struct list_head *work_list = priv->work_list;
struct ldlm_lock *enq = priv->lock;
enum ldlm_mode mode = priv->mode;
ENTRY;
/* interval tree is for granted lock */
LASSERTF(mode == lock->l_granted_mode,
"mode = %s, lock->l_granted_mode = %s\n",
ldlm_lockname[mode],
ldlm_lockname[lock->l_granted_mode]);
if (lock->l_blocking_ast && lock->l_granted_mode != LCK_GROUP)
ldlm_add_ast_work_item(lock, enq, work_list);
/* don't count conflicting glimpse locks */
if (!(mode == LCK_PR && lock->l_policy_data.l_extent.start == 0 &&
lock->l_policy_data.l_extent.end == OBD_OBJECT_EOF))
*priv->locks += 1;
if (priv->compat)
*priv->compat = 0;
RETURN(false);
}
/**
* Determine if the lock is compatible with all locks on the queue.
*
* If \a work_list is provided, conflicting locks are linked there.
* If \a work_list is not provided, we exit this function on first conflict.
*
* \retval 0 if the lock is not compatible
* \retval 1 if the lock is compatible
* \retval 2 if \a req is a group lock and it is compatible and requires
* no further checking
* \retval negative error, such as EAGAIN for group locks
*/
static int
ldlm_extent_compat_queue(struct list_head *queue, struct ldlm_lock *req,
__u64 *flags, struct list_head *work_list)
{
struct ldlm_resource *res = req->l_resource;
enum ldlm_mode req_mode = req->l_req_mode;
__u64 req_start = req->l_req_extent.start;
__u64 req_end = req->l_req_extent.end;
struct ldlm_lock *lock;
int contended_locks = 0;
int contention = 0;
int compat = 1;
ENTRY;
lockmode_verify(req_mode);
/* Using interval tree for granted lock */
if (queue == &res->lr_granted) {
struct ldlm_interval_tree *tree;
struct ldlm_extent_compat_args data = {
.work_list = work_list,
.lock = req,
.locks = &contended_locks,
.compat = &compat };
int idx;
for (idx = 0; idx < LCK_MODE_NUM; idx++) {
tree = &res->lr_itree[idx];
if (RB_EMPTY_ROOT(&tree->lit_root.rb_root))
continue;
data.mode = tree->lit_mode;
if (lockmode_compat(req_mode, tree->lit_mode)) {
struct ldlm_lock *lock;
if (req_mode != LCK_GROUP)
continue;
/* group lock, grant it immediately if
* compatible
*/
lock = extent_top(tree);
if (req->l_policy_data.l_extent.gid ==
lock->l_policy_data.l_extent.gid)
RETURN(2);
}
if (tree->lit_mode == LCK_GROUP) {
if (*flags & (LDLM_FL_BLOCK_NOWAIT |
LDLM_FL_SPECULATIVE)) {
compat = -EAGAIN;
goto destroylock;
}
if (!work_list)
RETURN(0);
/* if work list is not NULL, add all
* locks in the tree to work list.
*/
compat = 0;
for (lock = extent_first(tree);
lock;
lock = extent_next_lock(lock))
ldlm_extent_compat_cb(lock, &data);
continue;
}
/* We've found a potentially blocking lock, check
* compatibility. This handles locks other than GROUP
* locks, which are handled separately above.
*
* Locks with FL_SPECULATIVE are asynchronous requests
* which must never wait behind another lock, so they
* fail if any conflicting lock is found.
*/
if (!work_list || (*flags & LDLM_FL_SPECULATIVE)) {
if (extent_iter_first(&tree->lit_root,
req_start, req_end)) {
if (!work_list)
RETURN(0);
compat = -EAGAIN;
contended_locks = 1;
goto spec_contention;
}
} else {
ldlm_extent_search(&tree->lit_root,
req_start, req_end,
ldlm_extent_compat_cb,
&data);
if (!list_empty(work_list) && compat)
compat = 0;
}
}
} else { /* for waiting queue */
list_for_each_entry(lock, queue, l_res_link) {
contention = 1;
/* We stop walking the queue if we hit ourselves so
* we don't take conflicting locks enqueued after us
* into account, or we'd wait forever.
*/
if (req == lock)
break;
/* locks are compatible, overlap doesn't matter */
if (lockmode_compat(lock->l_req_mode, req_mode)) {
if (req_mode == LCK_PR &&
((lock->l_policy_data.l_extent.start <=
req->l_policy_data.l_extent.start) &&
(lock->l_policy_data.l_extent.end >=
req->l_policy_data.l_extent.end))) {
/* If we met a PR lock just like us or
* wider, and nobody down the list
* conflicted with it, that means we
* can skip processing of the rest of
* the list and safely place ourselves
* at the end of the list, or grant
* (dependent if we met an conflicting
* locks before in the list). In case
* of 1st enqueue only we continue
* traversing if there is something
* conflicting down the list because
* we need to make sure that something
* is marked as AST_SENT as well, in
* case of empy worklist we would exit
* on first conflict met.
*/
/* There IS a case where such flag is
* not set for a lock, yet it blocks
* something. Luckily for us this is
* only during destroy, so lock is
* exclusive. So here we are safe
*/
if (!ldlm_is_ast_sent(lock))
RETURN(compat);
}
/* non-group locks are compatible,
* overlap doesn't matter
*/
if (likely(req_mode != LCK_GROUP))
continue;
/* If we are trying to get a GROUP lock and
* there is another one of this kind, we need
* to compare gid
*/
if (req->l_policy_data.l_extent.gid ==
lock->l_policy_data.l_extent.gid) {
/* If existing lock with matched gid
* is granted, we grant new one too.
*/
if (ldlm_is_granted(lock))
RETURN(2);
/* Otherwise we are scanning queue of
* waiting locks and it means current
* request would block along with
* existing lock (that is already
* blocked. If we are in nonblocking
* mode - return immediately
*/
if (*flags & (LDLM_FL_BLOCK_NOWAIT |
LDLM_FL_SPECULATIVE)) {
compat = -EAGAIN;
goto destroylock;
}
/* If this group lock is compatible with
* another group lock on the waiting
* list, they must be together in the
* list, so they can be granted at the
* same time. Otherwise the later lock
* can get stuck behind another,
* incompatible, lock.
*/
ldlm_resource_insert_lock_after(lock,
req);
/* Because 'lock' is not granted, we can
* stop processing this queue and return
* immediately. There is no need to
* check the rest of the list.
*/
RETURN(0);
}
}
if (unlikely(req_mode == LCK_GROUP &&
!ldlm_is_granted(lock))) {
compat = 0;
if (lock->l_req_mode != LCK_GROUP) {
/* Ok, we hit non-GROUP lock,
* there
* should be no more GROUP
* locks later
* on, queue in
* front of first
* non-GROUP lock
*/
ldlm_resource_insert_lock_before(lock,
req);
break;
}
LASSERT(req->l_policy_data.l_extent.gid !=
lock->l_policy_data.l_extent.gid);
continue;
}
if (unlikely(lock->l_req_mode == LCK_GROUP)) {
/* If compared lock is GROUP, then requested is
* PR/PW so this is not compatible; extent
* range does not matter
*/
if (*flags & (LDLM_FL_BLOCK_NOWAIT |
LDLM_FL_SPECULATIVE)) {
compat = -EAGAIN;
goto destroylock;
}
} else if (lock->l_policy_data.l_extent.end < req_start ||
lock->l_policy_data.l_extent.start > req_end) {
/* if non group lock doesn't overlap skip it */
continue;
} else if (lock->l_req_extent.end < req_start ||
lock->l_req_extent.start > req_end) {
/* false contention, the requests doesn't
* really overlap
*/
contention = 0;
}
if (!work_list)
RETURN(0);
/* don't count conflicting glimpse locks */
if (lock->l_req_mode == LCK_PR &&
lock->l_policy_data.l_extent.start == 0 &&
lock->l_policy_data.l_extent.end == OBD_OBJECT_EOF)
contention = 0;
contended_locks += contention;
if (*flags & LDLM_FL_SPECULATIVE) {
compat = -EAGAIN;
goto spec_contention;
}
compat = 0;
if (lock->l_blocking_ast &&
lock->l_req_mode != LCK_GROUP)
ldlm_add_ast_work_item(lock, req, work_list);
}
}
/* compatible locks aren't contending, and !work_list means RESCAN, so
* we've already processed this lock
*/
if (compat > 0 || !work_list)
contended_locks = 0;
/* Check for contention & inform client by setting lock flag (but don't
* send the flag to clients which don't know about it.
*/
if (ldlm_update_contention(req, contended_locks) &&
req->l_export && exp_connect_lock_contention(req->l_export))
ldlm_set_contention(req);
RETURN(compat);
/* We need to count speculative non-glimpse locks in contention, but
* they are destroyed if blocked, so we count them separately.
*/
spec_contention:
ldlm_update_contention(req, contended_locks);
destroylock:
list_del_init(&req->l_res_link);
if (ldlm_is_local(req))
ldlm_lock_decref_internal_nolock(req, req_mode);
ldlm_lock_destroy_nolock(req);
RETURN(compat);
}
/**
* This function refresh eviction timer for cancelled lock.
* \param[in] lock ldlm lock for refresh
* \param[in] arg ldlm prolong arguments, timeout, export, extent
* and counter are used
*/
void ldlm_lock_prolong_one(struct ldlm_lock *lock,
struct ldlm_prolong_args *arg)
{
timeout_t timeout;
CFS_FAIL_TIMEOUT(OBD_FAIL_LDLM_PROLONG_PAUSE, 3);
if (arg->lpa_export != lock->l_export ||
lock->l_flags & LDLM_FL_DESTROYED)
/* ignore unrelated locks */
return;
arg->lpa_locks_cnt++;
if (!(lock->l_flags & LDLM_FL_AST_SENT))
/* ignore locks not being cancelled */
return;
arg->lpa_blocks_cnt++;
/* OK. this is a possible lock the user holds doing I/O
* let's refresh eviction timer for it.
*/
timeout = ptlrpc_export_prolong_timeout(arg->lpa_req, false);
LDLM_DEBUG(lock, "refreshed to %ds. ", timeout);
ldlm_refresh_waiting_lock(lock, timeout);
}
EXPORT_SYMBOL(ldlm_lock_prolong_one);
static bool ldlm_resource_prolong_cb(struct ldlm_lock *lock, void *data)
{
struct ldlm_prolong_args *arg = data;
ENTRY;
ldlm_lock_prolong_one(lock, arg);
RETURN(false);
}
/**
* Walk through granted tree and prolong locks if they overlaps extent.
*
* \param[in] arg prolong args
*/
void ldlm_resource_prolong(struct ldlm_prolong_args *arg)
{
struct ldlm_interval_tree *tree;
struct ldlm_resource *res;
int idx;
ENTRY;
res = ldlm_resource_get(arg->lpa_export->exp_obd->obd_namespace,
&arg->lpa_resid, LDLM_EXTENT, 0);
if (IS_ERR(res)) {
CDEBUG(D_DLMTRACE, "Failed to get resource for resid %llu/%llu\n",
arg->lpa_resid.name[0], arg->lpa_resid.name[1]);
RETURN_EXIT;
}
lock_res(res);
for (idx = 0; idx < LCK_MODE_NUM; idx++) {
tree = &res->lr_itree[idx];
if (RB_EMPTY_ROOT(&tree->lit_root.rb_root))
continue;
/* There is no possibility to check for the groupID
* so all the group locks are considered as valid
* here, especially because the client is supposed
* to check it has such a lock before sending an RPC.
*/
if (!(tree->lit_mode & arg->lpa_mode))
continue;
ldlm_extent_search(&tree->lit_root, arg->lpa_extent.start,
arg->lpa_extent.end,
ldlm_resource_prolong_cb, arg);
}
unlock_res(res);
ldlm_resource_putref(res);
EXIT;
}
EXPORT_SYMBOL(ldlm_resource_prolong);
/**
* Process a granting attempt for extent lock.
* Must be called with ns lock held.
*
* This function looks for any conflicts for \a lock in the granted or
* waiting queues. The lock is granted if no conflicts are found in
* either queue.
*/
int ldlm_process_extent_lock(struct ldlm_lock *lock, __u64 *flags,
enum ldlm_process_intention intention,
enum ldlm_error *err, struct list_head *work_list)
{
struct ldlm_resource *res = lock->l_resource;
int rc, rc2 = 0;
struct list_head *grant_work = intention == LDLM_PROCESS_ENQUEUE ?
NULL : work_list;
ENTRY;
LASSERT(!ldlm_is_granted(lock));
LASSERT(!(*flags & LDLM_FL_DENY_ON_CONTENTION) ||
!ldlm_is_ast_discard_data(lock));
check_res_locked(res);
*err = ELDLM_OK;
if (intention == LDLM_PROCESS_RESCAN) {
/* Careful observers will note that we don't handle -EAGAIN
* here, but it's ok for a non-obvious reason -- compat_queue
* can only return -EAGAIN if (flags & BLOCK_NOWAIT |
* SPECULATIVE). flags should always be zero here, and if that
* ever stops being true, we want to find out. */
LASSERT(*flags == 0);
rc = ldlm_extent_compat_queue(&res->lr_granted, lock, flags,
NULL);
if (rc == 1) {
rc = ldlm_extent_compat_queue(&res->lr_waiting, lock,
flags, NULL);
}
if (rc == 0)
RETURN(LDLM_ITER_STOP);
ldlm_resource_unlink_lock(lock);
if (!CFS_FAIL_CHECK(OBD_FAIL_LDLM_CANCEL_EVICT_RACE))
ldlm_extent_policy(res, lock, flags);
ldlm_grant_lock(lock, grant_work);
RETURN(LDLM_ITER_CONTINUE);
}
rc = ldlm_extent_compat_queue(&res->lr_granted, lock, flags, work_list);
if (rc < 0)
GOTO(out, *err = rc);
if (rc != 2) {
rc2 = ldlm_extent_compat_queue(&res->lr_waiting, lock,
flags, work_list);
if (rc2 < 0)
GOTO(out, *err = rc = rc2);
}
if (rc + rc2 == 2) {
ldlm_extent_policy(res, lock, flags);
ldlm_resource_unlink_lock(lock);
ldlm_grant_lock(lock, grant_work);
} else {
/* Adding LDLM_FL_NO_TIMEOUT flag to granted lock to
* force client to wait for the lock endlessly once
* the lock is enqueued -bzzz */
*flags |= LDLM_FL_NO_TIMEOUT;
}
RETURN(LDLM_ITER_CONTINUE);
out:
return rc;
}
#endif /* CONFIG_LUSTRE_FS_SERVER */
/* When a lock is cancelled by a client, the KMS may undergo change if this
* is the "highest lock". This function returns the new KMS value, updating
* it only if we were the highest lock.
*
* Caller must hold lr_lock already.
*
* NB: A lock on [x,y] protects a KMS of up to y + 1 bytes!
*/
__u64 ldlm_extent_shift_kms(struct ldlm_lock *lock, __u64 old_kms)
{
struct ldlm_resource *res = lock->l_resource;
struct ldlm_interval_tree *tree;
struct ldlm_lock *lck;
__u64 kms = 0;
int idx = 0;
bool complete = false;
ENTRY;
/* don't let another thread in ldlm_extent_shift_kms race in
* just after we finish and take our lock into account in its
* calculation of the kms
*/
ldlm_set_kms_ignore(lock);
/* We iterate over the lock trees, looking for the largest kms
* smaller than the current one.
*/
for (idx = 0; idx < LCK_MODE_NUM; idx++) {
tree = &res->lr_itree[idx];
/* If our already known kms is >= than the highest 'end' in
* this tree, we don't need to check this tree, because
* the kms from a tree can be lower than in_max_high (due to
* kms_ignore), but it can never be higher. */
lck = extent_top(tree);
if (!lck || kms >= lck->l_subtree_last)
continue;
for (lck = extent_last(tree);
lck;
lck = extent_prev(lck)) {
if (ldlm_is_kms_ignore(lck)) {
struct ldlm_lock *lk;
list_for_each_entry(lk, &lck->l_same_extent,
l_same_extent)
if (!ldlm_is_kms_ignore(lk)) {
lk = NULL;
break;
}
if (lk)
continue;
}
/* If this lock has a greater or equal kms, we are not
* the highest lock (or we share that distinction
* with another lock), and don't need to update KMS.
* Record old_kms and stop looking.
*/
if (lck->l_policy_data.l_extent.end == OBD_OBJECT_EOF ||
lck->l_policy_data.l_extent.end + 1 >= old_kms) {
kms = old_kms;
complete = true;
break;
}
if (lck->l_policy_data.l_extent.end + 1 > kms)
kms = lck->l_policy_data.l_extent.end + 1;
/* Since we start with the highest lock and work
* down, for PW locks, we only need to check if we
* should update the kms, then stop walking the tree.
* PR locks are not exclusive, so the highest start
* does not imply the highest end and we must
* continue. (Only one group lock is allowed per
* resource, so this is irrelevant for group locks.)
*/
if (lck->l_granted_mode == LCK_PW)
break;
}
/* This tells us we're not the highest lock, so we don't need
* to check the remaining trees
*/
if (complete)
break;
}
LASSERTF(kms <= old_kms, "kms %llu old_kms %llu\n", kms, old_kms);
RETURN(kms);
}
EXPORT_SYMBOL(ldlm_extent_shift_kms);
static inline int ldlm_mode_to_index(enum ldlm_mode mode)
{
int index;
LASSERT(mode != 0);
LASSERT(is_power_of_2(mode));
index = ilog2(mode);
LASSERT(index < LCK_MODE_NUM);
return index;
}
/** Add newly granted lock into interval tree for the resource. */
void ldlm_extent_add_lock(struct ldlm_resource *res,
struct ldlm_lock *lock)
{
struct ldlm_interval_tree *tree;
struct ldlm_lock *orig;
int idx;
LASSERT(ldlm_is_granted(lock));
LASSERT(RB_EMPTY_NODE(&lock->l_rb));
LASSERT(list_empty(&lock->l_same_extent));
idx = ldlm_mode_to_index(lock->l_granted_mode);
LASSERT(lock->l_granted_mode == BIT(idx));
LASSERT(lock->l_granted_mode == res->lr_itree[idx].lit_mode);
tree = &res->lr_itree[idx];
orig = extent_insert_unique(lock, &tree->lit_root);
if (orig)
list_add(&lock->l_same_extent, &orig->l_same_extent);
tree->lit_size++;
/* even though we use interval tree to manage the extent lock, we also
* add the locks into grant list, for debug purpose, ..
*/
ldlm_resource_add_lock(res, &res->lr_granted, lock);
if (CFS_FAIL_CHECK(OBD_FAIL_LDLM_GRANT_CHECK)) {
struct ldlm_lock *lck;
list_for_each_entry_reverse(lck, &res->lr_granted, l_res_link) {
if (lck == lock)
continue;
if (lockmode_compat(lck->l_granted_mode,
lock->l_granted_mode))
continue;
if (ldlm_extent_overlap(&lck->l_req_extent,
&lock->l_req_extent)) {
CDEBUG(D_ERROR,
"granting conflicting lock %p %p\n",
lck, lock);
ldlm_resource_dump(D_ERROR, res);
LBUG();
}
}
}
}
/** Remove cancelled lock from resource interval tree. */
void ldlm_extent_unlink_lock(struct ldlm_lock *lock)
{
struct ldlm_resource *res = lock->l_resource;
struct ldlm_interval_tree *tree;
int idx;
if (RB_EMPTY_NODE(&lock->l_rb) &&
list_empty(&lock->l_same_extent)) /* duplicate unlink */
return;
idx = ldlm_mode_to_index(lock->l_granted_mode);
LASSERT(lock->l_granted_mode == BIT(idx));
tree = &res->lr_itree[idx];
LASSERT(!RB_EMPTY_ROOT(&tree->lit_root.rb_root));
tree->lit_size--;
if (RB_EMPTY_NODE(&lock->l_rb)) {
list_del_init(&lock->l_same_extent);
} else if (list_empty(&lock->l_same_extent)) {
extent_remove(lock, &tree->lit_root);
RB_CLEAR_NODE(&lock->l_rb);
} else {
struct ldlm_lock *next = list_next_entry(lock, l_same_extent);
list_del_init(&lock->l_same_extent);
extent_remove(lock, &tree->lit_root);
RB_CLEAR_NODE(&lock->l_rb);
extent_insert(next, &tree->lit_root);
}
}
void ldlm_extent_policy_wire_to_local(const union ldlm_wire_policy_data *wpolicy,
union ldlm_policy_data *lpolicy)
{
lpolicy->l_extent.start = wpolicy->l_extent.start;
lpolicy->l_extent.end = wpolicy->l_extent.end;
lpolicy->l_extent.gid = wpolicy->l_extent.gid;
}
void ldlm_extent_policy_local_to_wire(const union ldlm_policy_data *lpolicy,
union ldlm_wire_policy_data *wpolicy)
{
memset(wpolicy, 0, sizeof(*wpolicy));
wpolicy->l_extent.start = lpolicy->l_extent.start;
wpolicy->l_extent.end = lpolicy->l_extent.end;
wpolicy->l_extent.gid = lpolicy->l_extent.gid;
}
void ldlm_extent_search(struct rb_root_cached *root,
u64 start, u64 end,
bool (*matches)(struct ldlm_lock *lock, void *data),
void *data)
{
struct ldlm_lock *lock, *lock2;
for (lock = extent_iter_first(root, start, end);
lock;
lock = extent_iter_next(lock, start, end)) {
if (matches(lock, data))
return;
list_for_each_entry(lock2, &lock->l_same_extent, l_same_extent)
if (matches(lock2, data))
return;
}
}