scoutfs: stop iteration at lock end value

If item iteration finds a hole in the cache it tries to read items.
After the items are read it can look at the cached region and return
items or -ENOENT.  We recently added an end key to limit how far we can
read and cache items.

The end key addition correctly limited the cache read to the lock end
value.  It could never read item cache ranges beyond that.  This means
that it can't iterate past the end value and should return -ENOENT if it
gets past end.  But the code forgot to do that, it only checked for
iteration past last before returning -ENOENT.  It spins continually
finding a hole past end but inside last, tries to read items but limits
them to end, then finds the same hole again.

Triggering this requires a lock end that's nearer than the last
iteration key.  That's hard to do because most of our item reads are
covered by inode group locks which extend well past iteration inside a
given inode.  Inode index item can easily trigger this if there's no
items.  I tripped over it when walking empty indexes (data_seq or
online_blocks with no regular files).

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2017-08-10 14:49:55 -07:00
parent ca78757ca5
commit a8db7e5b74

View File

@@ -841,15 +841,17 @@ static struct cached_item *item_for_next(struct rb_root *root,
}
/*
* Return the next item starting with the given key, returning the last
* key at the most.
* Return the next item starting with the given key and returning the
* last key at most.
*
* While iteration stops the last key we can cache up to the end key so
* that a sequence of small iterations covered by one lock are satisfied
* with a large read of items from segments into the cache.
* If the end key is specified then it limits items that can be read
* into the cache. If it's less than the last key then it also limits
* iteration. These are different values because locking granularity
* can be smaller or larger than the iteration. Callers shouldn't have
* to be aware of that relationship.
*
* -ENOENT is returned if there are no items between the given and last
* keys.
* -ENOENT is returned if there are no items between the given and
* last/end keys.
*
* The next item's key is copied to the caller's key. The caller is
* responsible for dealing with key lengths and truncation.
@@ -871,6 +873,10 @@ int scoutfs_item_next(struct super_block *sb, struct scoutfs_key_buf *key,
bool cached;
int ret;
/* use the end key as the last key if it's closer to reduce compares */
if (end && scoutfs_key_compare(end, last) < 0)
last = end;
/* convenience to avoid searching if caller iterates past their last */
if (scoutfs_key_compare(key, last) > 0) {
ret = -ENOENT;