Files
scoutfs/kmod/src/item.c
T
2026-06-05 09:49:45 -07:00

2650 lines
70 KiB
C

/*
* Copyright (C) 2020 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/rbtree.h>
#include <linux/list_sort.h>
#include <linux/cpu.h>
#include <linux/mm.h>
#include "super.h"
#include "item.h"
#include "forest.h"
#include "block.h"
#include "msg.h"
#include "trans.h"
#include "counters.h"
#include "scoutfs_trace.h"
#include "util.h"
/*
* The item cache maintains a consistent view of items that are read
* from and written to the forest of btrees under the protection of
* cluster locks.
*
* The cache is built around pages of items. A page has the range of
* keys that it caches and the items that are present in that range.
* Pages are non-overlapping, there is only one page that can contain a
* given key at a time. The pages are tracked by an rbtree, and each
* page has an rbtree of items.
*
* The cache is populated by reading items from the forest of btrees
* into a private set of pages. The regions of those pages which
* weren't already cached are then inserted into the cache.
*
* CPUs can concurrently modify items that are in different pages. The
* page rbtree can be read locked to find a page, and then the page is
* locked to work with its items. We then add per-cpu references to
* recently used pages so that the global page rbtree can be skipped in
* the typical case of repeated calls to localized portions of the key
* space.
*
* Dirty items are kept in a per-page dirty list, and pages with dirty
* items are kept in a global dirty list. This reduces contention on
* the global list by accessing it at page granularity instead of every
* time an item is dirtied. The dirty items are not sorted until it
* comes time to commit them to the btrees. This reduces the cost of
* tracking dirty items during the transaction, particularly moving them
* between pages as pages are split to make room for new items.
*
* The size of the cache is only limited by memory reclaim. Pages are
* kept in a very coarse lru. Dirtying doesn't remove pages from the
* lru, and is operating against lock ordering with trylocks, so
* shrinking can rarely have to skip pages in the LRU.
*
* The locking is built around the fast path of everyone checking the
* the page rbtree, then locking pages, and then adding or removing
* pages from the lru or dirty lists. Writing and the shrinker work
* work in reverse, starting with the dirty or lru lists and have to use
* trylock to lock the pages. When we split we have to lock multiple
* pages and we use trylock which is guaranteed to succeed because the
* pages are private.
*/
struct item_cache_info {
/* almost always read, barely written */
struct super_block *sb;
struct item_percpu_pages __percpu *pcpu_pages;
KC_DEFINE_SHRINKER(shrinker);
/* often walked, but per-cpu refs are fast path */
rwlock_t rwlock;
struct rb_root pg_root;
/* stop readers from caching stale items behind reclaimed cleaned written items */
u64 read_dirty_barrier;
/* page-granular modification by writers, then exclusive to commit */
spinlock_t dirty_lock;
struct list_head dirty_list;
atomic_t dirty_pages;
/* page-granular modification by readers */
spinlock_t lru_lock;
struct list_head lru_list;
unsigned long lru_pages;
};
#define DECLARE_ITEM_CACHE_INFO(sb, name) \
struct item_cache_info *name = SCOUTFS_SB(sb)->item_cache_info
#define PG_PER_CPU 32
struct item_percpu_pages {
struct rb_root root;
struct list_head list;
struct pcpu_page_ref {
struct scoutfs_key start;
struct scoutfs_key end;
struct cached_page *pg;
struct rb_node node;
struct list_head head;
} refs[PG_PER_CPU];
};
struct cached_page {
/* often read by concurrent rbtree walks */
struct rb_node node;
struct scoutfs_key start;
struct scoutfs_key end;
/* often modified by page rwlock holder */
rwlock_t rwlock;
struct rb_root item_root;
struct list_head lru_head;
unsigned long lru_time;
struct list_head dirty_list;
struct list_head dirty_head;
u64 max_seq;
struct page *page;
unsigned int page_off;
unsigned int erased_bytes;
atomic_t refcount;
};
struct cached_item {
struct rb_node node;
struct list_head dirty_head;
unsigned int dirty:1, /* needs to be written */
persistent:1, /* in btrees, needs deletion item */
deletion:1, /* negative del item for writing */
delta:1; /* item vales are combined, freed after write */
unsigned int val_len;
struct scoutfs_key key;
u64 seq;
char val[0];
};
#define CACHED_ITEM_ALIGN 8
static int item_val_bytes(int val_len)
{
return round_up(offsetof(struct cached_item, val[val_len]),
CACHED_ITEM_ALIGN);
}
/*
* Return if the page has room to allocate an item with the given value
* length at its free page offset. This must be called with the page
* writelock held because it can modify the page to reclaim free space
* to mkae room for the allocation. Today all it does is recognize that
* the page is empty and reset the page_off.
*/
static bool page_has_room(struct cached_page *pg, int val_len)
{
if (RB_EMPTY_ROOT(&pg->item_root))
pg->page_off = 0;
return pg->page_off + item_val_bytes(val_len) <= PAGE_SIZE;
}
static struct cached_page *first_page(struct rb_root *root)
{
struct rb_node *node;
if (!root || !(node = rb_first(root)))
return NULL;
return rb_entry(node, struct cached_page, node);
}
static struct cached_item *first_item(struct rb_root *root)
{
struct rb_node *node;
if (!root || !(node = rb_first(root)))
return NULL;
return rb_entry(node, struct cached_item, node);
}
static struct cached_item *last_item(struct rb_root *root)
{
struct rb_node *node;
if (!root || !(node = rb_last(root)))
return NULL;
return rb_entry(node, struct cached_item, node);
}
static struct cached_item *next_item(struct cached_item *item)
{
struct rb_node *node;
if (!item || !(node = rb_next(&item->node)))
return NULL;
return rb_entry(node, struct cached_item, node);
}
static struct cached_item *prev_item(struct cached_item *item)
{
struct rb_node *node;
if (!item || !(node = rb_prev(&item->node)))
return NULL;
return rb_entry(node, struct cached_item, node);
}
static void rbtree_insert(struct rb_node *node, struct rb_node *par,
struct rb_node **pnode, struct rb_root *root)
{
rb_link_node(node, par, pnode);
rb_insert_color(node, root);
}
static void rbtree_erase(struct rb_node *node, struct rb_root *root)
{
rb_erase(node, root);
RB_CLEAR_NODE(node);
}
static void rbtree_replace_node(struct rb_node *victim, struct rb_node *new,
struct rb_root *root)
{
rb_replace_node(victim, new, root);
RB_CLEAR_NODE(victim);
}
/*
* This is far too expensive to use regularly, but it's very helpful for
* discovering corruption after modifications to cached pages.
*/
static __attribute__((unused)) void verify_page_rbtree(struct rb_root *root)
{
struct cached_item *item;
struct cached_page *par;
struct cached_page *pg;
struct cached_page *n;
char *reason = NULL;
struct rb_node *p;
int cmp;
rbtree_postorder_for_each_entry_safe(pg, n, root, node) {
item = NULL;
par = NULL;
if (scoutfs_key_compare(&pg->start, &pg->end) > 0) {
reason = "start > end";
break;
}
item = first_item(&pg->item_root);
if (item && scoutfs_key_compare(&item->key, &pg->start) < 0) {
reason = "first item < start";
break;
}
item = last_item(&pg->item_root);
if (item && scoutfs_key_compare(&item->key, &pg->end) > 0) {
reason = "last item > end";
break;
}
p = rb_parent(&pg->node);
if (!p)
continue;
par = rb_entry(p, struct cached_page, node);
cmp = scoutfs_key_compare_ranges(&pg->start, &pg->end,
&par->start, &par->end);
if (cmp == 0) {
reason = "parent and child overlap";
break;
}
if (par->node.rb_right == &pg->node && cmp < 0) {
reason = "right child < parent";
break;
}
if (par->node.rb_left == &pg->node && cmp > 0) {
reason = "left child > parent";
break;
}
}
if (!reason)
return;
printk("bad item page rbtree: %s\n", reason);
printk("pg %p start "SK_FMT" end "SK_FMT"\n",
pg, SK_ARG(&pg->start), SK_ARG(&pg->end));
if (par)
printk("par %p start "SK_FMT" end "SK_FMT"\n",
par, SK_ARG(&par->start), SK_ARG(&par->end));
if (item)
printk("item %p key "SK_FMT"\n", item, SK_ARG(&item->key));
rbtree_postorder_for_each_entry_safe(pg, n, root, node) {
printk(" pg %p left %p right %p start "SK_FMT" end "SK_FMT"\n",
pg,
pg->node.rb_left ? rb_entry(pg->node.rb_left,
struct cached_page, node) :
NULL,
pg->node.rb_right ? rb_entry(pg->node.rb_right,
struct cached_page, node) :
NULL,
SK_ARG(&pg->start),
SK_ARG(&pg->end));
}
BUG();
}
/*
* This lets us lock newly allocated pages without having to add nesting
* annotation. The non-acquired path is never executed.
*/
static void write_trylock_will_succeed(rwlock_t *rwlock)
__acquires(rwlock)
{
while (!write_trylock(rwlock))
BUG();
}
static struct cached_page *alloc_pg(struct super_block *sb, gfp_t gfp)
{
struct cached_page *pg;
struct page *page;
pg = kzalloc(sizeof(struct cached_page), GFP_NOFS | gfp);
page = alloc_page(GFP_NOFS | gfp);
if (!page || !pg) {
kfree(pg);
if (page)
__free_page(page);
return NULL;
}
scoutfs_inc_counter(sb, item_page_alloc);
RB_CLEAR_NODE(&pg->node);
rwlock_init(&pg->rwlock);
pg->item_root = RB_ROOT;
INIT_LIST_HEAD(&pg->lru_head);
INIT_LIST_HEAD(&pg->dirty_list);
INIT_LIST_HEAD(&pg->dirty_head);
pg->page = page;
atomic_set(&pg->refcount, 1);
return pg;
}
static void get_pg(struct cached_page *pg)
{
atomic_inc(&pg->refcount);
}
static void put_pg(struct super_block *sb, struct cached_page *pg)
{
if (pg && atomic_dec_and_test(&pg->refcount)) {
scoutfs_inc_counter(sb, item_page_free);
BUG_ON(!RB_EMPTY_NODE(&pg->node));
BUG_ON(!list_empty(&pg->lru_head));
BUG_ON(!list_empty(&pg->dirty_list));
BUG_ON(!list_empty(&pg->dirty_head));
__free_page(pg->page);
kfree(pg);
}
}
static void update_pg_max_seq(struct cached_page *pg, struct cached_item *item)
{
if (item->seq > pg->max_seq)
pg->max_seq = item->seq;
}
/*
* Allocate space for a new item from the free offset at the end of a
* cached page. This isn't a blocking allocation, and it's likely that
* the caller has ensured it will succeed by allocating from a new empty
* page or checking the free space first.
*/
static struct cached_item *alloc_item(struct cached_page *pg,
struct scoutfs_key *key, u64 seq, bool deletion,
void *val, int val_len)
{
struct cached_item *item;
if (!page_has_room(pg, val_len))
return NULL;
item = page_address(pg->page) + pg->page_off;
pg->page_off += item_val_bytes(val_len);
RB_CLEAR_NODE(&item->node);
INIT_LIST_HEAD(&item->dirty_head);
item->dirty = 0;
item->persistent = 0;
item->deletion = !!deletion;
item->delta = 0;
item->val_len = val_len;
item->key = *key;
item->seq = seq;
if (val_len)
memcpy(item->val, val, val_len);
update_pg_max_seq(pg, item);
return item;
}
static void erase_item(struct cached_page *pg, struct cached_item *item)
{
rbtree_erase(&item->node, &pg->item_root);
pg->erased_bytes += item_val_bytes(item->val_len);
}
static void lru_add(struct super_block *sb, struct item_cache_info *cinf,
struct cached_page *pg)
{
spin_lock(&cinf->lru_lock);
if (list_empty(&pg->lru_head)) {
scoutfs_inc_counter(sb, item_page_lru_add);
list_add_tail(&pg->lru_head, &cinf->lru_list);
cinf->lru_pages++;
}
spin_unlock(&cinf->lru_lock);
}
static void __lru_remove(struct super_block *sb, struct item_cache_info *cinf,
struct cached_page *pg)
{
if (!list_empty(&pg->lru_head)) {
scoutfs_inc_counter(sb, item_page_lru_remove);
list_del_init(&pg->lru_head);
cinf->lru_pages--;
}
}
static void lru_remove(struct super_block *sb, struct item_cache_info *cinf,
struct cached_page *pg)
{
spin_lock(&cinf->lru_lock);
__lru_remove(sb, cinf, pg);
spin_unlock(&cinf->lru_lock);
}
/*
* Make sure that the page the caller just accessed is reasonably close
* to the tail of the lru so it will be less likely to be reclaimed by
* the shrinker.
*
* We want to quickly determine that the page is close enough to the
* tail by only looking at the page. We use a coarse clock tick to
* determine if we've already moved the head to the tail sufficiently
* recently. We can't differentiate shrinking priority amongst the
* number of pages that the cpu can access within given chunk of time.
*
* We don't care that the lru_time accessed aren't locked and could see
* rare corruption. It's just a shrink priority heuristic.
*/
static void lru_accessed(struct super_block *sb, struct item_cache_info *cinf,
struct cached_page *pg)
{
unsigned long time = jiffies_to_msecs(jiffies);
scoutfs_inc_counter(sb, item_page_accessed);
if (pg->lru_time != time) {
lru_remove(sb, cinf, pg);
pg->lru_time = time;
lru_add(sb, cinf, pg);
}
}
/*
* Return the pg that contains the key and set the parent nodes for insertion.
* When we find the pg we go right so that the caller can insert a new
* page to the right of the found page if it had to split the page.
*/
static struct cached_page *page_rbtree_walk(struct super_block *sb,
struct rb_root *root,
struct scoutfs_key *start,
struct scoutfs_key *end,
struct cached_page **prev,
struct cached_page **next,
struct rb_node **par,
struct rb_node ***pnode)
{
struct rb_node **node = &root->rb_node;
struct rb_node *parent = NULL;
struct cached_page *ret = NULL;
struct cached_page *pg;
int cmp;
scoutfs_inc_counter(sb, item_page_rbtree_walk);
if (next)
*next = NULL;
if (prev)
*prev = NULL;
while (*node) {
parent = *node;
pg = container_of(*node, struct cached_page, node);
cmp = scoutfs_key_compare_ranges(start, end, &pg->start,
&pg->end);
if (cmp < 0) {
if (next)
*next = pg;
node = &(*node)->rb_left;
} else if (cmp > 0) {
if (prev)
*prev = pg;
node = &(*node)->rb_right;
} else {
ret = pg;
node = &(*node)->rb_right;
}
}
if (par)
*par = parent;
if (pnode)
*pnode = node;
return ret;
}
#define for_each_page_safe(root, pg, tmp) \
for (tmp = rb_first(root); \
tmp && (pg = container_of(tmp, struct cached_page, node)) && \
((tmp = rb_next(tmp)), 1); )
static struct cached_item *item_rbtree_walk(struct rb_root *root,
struct scoutfs_key *key,
struct cached_item **next,
struct rb_node **par,
struct rb_node ***pnode)
{
struct rb_node **node = &root->rb_node;
struct rb_node *parent = NULL;
struct cached_item *ret = NULL;
struct cached_item *item;
int cmp;
if (next)
*next = NULL;
while (*node) {
parent = *node;
item = container_of(*node, struct cached_item, node);
cmp = scoutfs_key_compare(key, &item->key);
if (cmp < 0) {
if (next)
*next = item;
node = &(*node)->rb_left;
} else if (cmp > 0) {
node = &(*node)->rb_right;
} else {
ret = item;
node = &(*node)->rb_left;
}
}
if (par)
*par = parent;
if (pnode)
*pnode = node;
return ret;
}
#define for_each_item_from_safe(root, item, tmp, key) \
for (item = item_rbtree_walk(root, key, &tmp, NULL, NULL) ?: tmp; \
item && ((tmp = next_item(item)), 1); \
item = tmp)
#define for_each_item_safe(root, item, tmp) \
for (tmp = rb_first(root); \
tmp && (item = container_of(tmp, struct cached_item, node)) && \
((tmp = rb_next(tmp)), 1); )
/*
* As we mark the first and clear the last items in a page, we add or
* delete the page from the dirty list. The caller can give us a page
* to add the newly dirtied page after, rather than at the tail of the
* list.
*/
static void mark_item_dirty(struct super_block *sb,
struct item_cache_info *cinf,
struct cached_page *pg,
struct cached_page *after,
struct cached_item *item)
{
if (!item->dirty) {
if (list_empty(&pg->dirty_list)) {
scoutfs_inc_counter(sb, item_page_mark_dirty);
spin_lock(&cinf->dirty_lock);
if (after)
list_add(&pg->dirty_head, &after->dirty_head);
else
list_add_tail(&pg->dirty_head,
&cinf->dirty_list);
atomic_inc(&cinf->dirty_pages);
spin_unlock(&cinf->dirty_lock);
}
scoutfs_inc_counter(sb, item_mark_dirty);
list_add_tail(&item->dirty_head, &pg->dirty_list);
item->dirty = 1;
}
update_pg_max_seq(pg, item);
}
static void clear_item_dirty(struct super_block *sb,
struct item_cache_info *cinf,
struct cached_page *pg,
struct cached_item *item)
{
if (item->dirty) {
scoutfs_inc_counter(sb, item_clear_dirty);
item->dirty = 0;
list_del_init(&item->dirty_head);
if (list_empty(&pg->dirty_list)) {
scoutfs_inc_counter(sb, item_page_clear_dirty);
spin_lock(&cinf->dirty_lock);
list_del_init(&pg->dirty_head);
atomic_dec(&cinf->dirty_pages);
spin_unlock(&cinf->dirty_lock);
}
}
}
static void erase_page_items(struct cached_page *pg,
struct scoutfs_key *start,
struct scoutfs_key *end)
{
struct cached_item *item;
struct cached_item *tmp;
for_each_item_from_safe(&pg->item_root, item, tmp, start) {
/* only called in unused read regions or read_pages pages */
BUG_ON(item->dirty);
if (scoutfs_key_compare(&item->key, end) > 0)
break;
erase_item(pg, item);
}
}
/*
* Move all the items starting from the key and stopping before moving
* the stop key. The right destination page must be empty. Items are
* copied in tree order which lets us easily insert after each previous
* item.
*
* This preserves dirty page and item ordering by adding the right page
* to the dirty list after the left page, and by adding items to the
* tail of right's dirty list in key sort order.
*
* The max_seq of the source page might be larger than all the items
* while protecting an erased item from being reclaimed while an older
* read is in flight. We don't know where it might be in the source
* page so we have to assume that it's in the key range being moved and
* update the destination page's max_seq accordingly.
*
* The caller is responsible for page locking and managing the lru.
*/
static void move_page_items(struct super_block *sb,
struct item_cache_info *cinf,
struct cached_page *left,
struct cached_page *right,
struct scoutfs_key *key,
struct scoutfs_key *stop)
{
struct cached_item *from;
struct cached_item *to;
struct cached_item *tmp;
struct rb_node **pnode;
struct rb_node *par;
/* really empty right destination? */
BUG_ON(!RB_EMPTY_ROOT(&right->item_root));
par = NULL;
pnode = &right->item_root.rb_node;
for_each_item_from_safe(&left->item_root, from, tmp, key) {
if (stop && scoutfs_key_compare(&from->key, stop) >= 0)
break;
to = alloc_item(right, &from->key, from->seq, from->deletion, from->val,
from->val_len);
rbtree_insert(&to->node, par, pnode, &right->item_root);
par = &to->node;
pnode = &to->node.rb_right;
if (from->dirty) {
mark_item_dirty(sb, cinf, right, left, to);
clear_item_dirty(sb, cinf, left, from);
}
to->persistent = from->persistent;
to->delta = from->delta;
erase_item(left, from);
}
if (left->max_seq > right->max_seq)
right->max_seq = left->max_seq;
}
enum page_intersection_type {
PGI_DISJOINT,
PGI_INSIDE,
PGI_START_OLAP,
PGI_END_OLAP,
PGI_BISECT_NEEDED,
PGI_BISECT,
};
/*
* Remove items from the page with intersect with the range. We return
* a code to indicate which kind of intersection occurred. The caller
* provides the right page to move items to if the page is bisected by
* the range.
*
* This modifies the page keys so it needs to be held with a write page
* rbtree lock if the page is in the page rbtree.
*/
static int trim_page_intersection(struct super_block *sb,
struct item_cache_info *cinf,
struct cached_page *pg,
struct cached_page *right,
struct scoutfs_key *start,
struct scoutfs_key *end)
{
int ps_e = scoutfs_key_compare(&pg->start, end);
int pe_s = scoutfs_key_compare(&pg->end, start);
int ps_s;
int pe_e;
/*
* page and range don't intersect
*
* ps |----------| pe
* s |----------| e
* (or)
* ps |----------| pe
* s |----------| e
*/
if (ps_e > 0 || pe_s < 0)
return PGI_DISJOINT;
ps_s = scoutfs_key_compare(&pg->start, start);
pe_e = scoutfs_key_compare(&pg->end, end);
/*
* page entirely inside range
*
* ps |----------| pe
* s |----------| e
*/
if (ps_s >= 0 && pe_e <= 0)
return PGI_INSIDE;
/*
* page surrounds range, and is bisected by it
*
* ps |----------| pe
* s |------| e
*/
if (ps_s < 0 && pe_e > 0) {
if (!right)
return PGI_BISECT_NEEDED;
right->start = *end;
scoutfs_key_inc(&right->start);
right->end = pg->end;
pg->end = *start;
scoutfs_key_dec(&pg->end);
erase_page_items(pg, start, end);
move_page_items(sb, cinf, pg, right, &right->start, NULL);
return PGI_BISECT;
}
/*
* start of page overlaps with range
*
* ps |----------| pe
* s |----------| e
*/
if (pe_e > 0) {
/* start of page overlaps range */
pg->start = *end;
scoutfs_key_inc(&pg->start);
erase_page_items(pg, start, end);
return PGI_START_OLAP;
}
/*
* end of page overlaps with range
*
* ps |----------| pe
* s |----------| e
*/
pg->end = *start;
scoutfs_key_dec(&pg->end);
erase_page_items(pg, start, end);
return PGI_END_OLAP;
}
/*
* The caller wants to allocate an item in the page but there isn't room
* at the page_off. If erasing items has left sufficient internal free
* space we can pack the existing items to the start of the page to make
* room for the insertion.
*
* The caller's empty pg is only used for its page struct, which we swap
* with our old empty page. We don't touch its pg struct.
*
* This is a coarse bulk way of dealing with free space, as opposed to
* specifically tracking internal free regions and using them to satisfy
* item allocations.
*/
static void compact_page_items(struct super_block *sb,
struct cached_page *pg,
struct cached_page *empty)
{
struct cached_item *from;
struct cached_item *to;
struct rb_root item_root = RB_ROOT;
struct rb_node *par = NULL;
struct rb_node **pnode = &item_root.rb_node;
unsigned int page_off = 0;
LIST_HEAD(dirty_list);
if (pg->erased_bytes < item_val_bytes(SCOUTFS_MAX_VAL_SIZE))
return;
if (WARN_ON_ONCE(empty->page_off != 0) ||
WARN_ON_ONCE(!RB_EMPTY_ROOT(&empty->item_root)) ||
WARN_ON_ONCE(!list_empty(&empty->dirty_list)))
return;
scoutfs_inc_counter(sb, item_page_compact);
for (from = first_item(&pg->item_root); from; from = next_item(from)) {
to = page_address(empty->page) + page_off;
page_off += item_val_bytes(from->val_len);
/* copy the entire item, struct members and all */
memcpy(to, from, item_val_bytes(from->val_len));
rbtree_insert(&to->node, par, pnode, &item_root);
par = &to->node;
pnode = &to->node.rb_right;
if (to->dirty)
list_add_tail(&to->dirty_head, &dirty_list);
}
pg->item_root = item_root;
list_replace(&dirty_list, &pg->dirty_list);
swap(pg->page, empty->page);
pg->page_off = page_off;
pg->erased_bytes = 0;
}
/*
* This behaves a little differently than the other walks because we
* want to minimize compares and there are only simple searching and
* inserting callers.
*/
static struct pcpu_page_ref *pcpu_page_rbtree_walk(struct rb_root *root,
struct scoutfs_key *key,
struct pcpu_page_ref *ins)
{
struct rb_node **node = &root->rb_node;
struct rb_node *parent = NULL;
struct pcpu_page_ref *ret = NULL;
struct pcpu_page_ref *ref;
int cmp;
while (*node) {
parent = *node;
ref = container_of(*node, struct pcpu_page_ref, node);
cmp = scoutfs_key_compare_ranges(key, key,
&ref->start, &ref->end);
if (cmp < 0) {
node = &(*node)->rb_left;
} else if (cmp > 0) {
node = &(*node)->rb_right;
} else {
ret = ref;
if (!ins)
return ret;
node = &(*node)->rb_right;
}
}
if (ins)
rbtree_insert(&ins->node, parent, node, root);
return ret;
}
/*
* Search the per-cpu page references for a page that contains the key
* the caller needs. These lookups are very frequent and key
* comparisons are relatively expensive, so we use an rbtree to decrease
* the comparison costs, particularly of misses.
*
* All the references in all the cpus go stale as page key boundaries
* are modified by reading, insertion, and invalidation. If we find a
* stale ref we will drop it, but otherwise we let stale refs age out as
* new refs are inserted.
*/
static struct cached_page *get_pcpu_page(struct super_block *sb,
struct item_cache_info *cinf,
struct scoutfs_key *key,
bool write)
{
struct item_percpu_pages *pages = get_cpu_ptr(cinf->pcpu_pages);
struct cached_page *pg = NULL;
struct pcpu_page_ref *ref;
ref = pcpu_page_rbtree_walk(&pages->root, key, NULL);
if (ref) {
pg = ref->pg;
if (write)
write_lock(&pg->rwlock);
else
read_lock(&pg->rwlock);
if (scoutfs_key_compare_ranges(key, key,
&pg->start, &pg->end)) {
if (write)
write_unlock(&pg->rwlock);
else
read_unlock(&pg->rwlock);
scoutfs_inc_counter(sb, item_pcpu_page_miss_keys);
rbtree_erase(&ref->node, &pages->root);
list_move_tail(&ref->head, &pages->list);
put_pg(sb, pg);
ref->pg = NULL;
pg = NULL;
} else {
if (pages->list.next != &ref->head)
list_move(&ref->head, &pages->list);
__release(pg_rwlock);
}
}
put_cpu_ptr(cinf->pcpu_pages);
if (pg)
scoutfs_inc_counter(sb, item_pcpu_page_hit);
else
scoutfs_inc_counter(sb, item_pcpu_page_miss);
return pg;
}
/*
* The caller has a locked page that it knows is authoritative for its
* range of keys. Add it to this cpu's cache and remove any other page
* in the pool which intersects with its range.
*/
static void add_pcpu_page(struct super_block *sb, struct item_cache_info *cinf,
struct cached_page *pg)
{
struct item_percpu_pages *pages = get_cpu_ptr(cinf->pcpu_pages);
struct pcpu_page_ref *old;
struct pcpu_page_ref *ref;
ref = list_last_entry(&pages->list, struct pcpu_page_ref, head);
if (ref->pg) {
rbtree_erase(&ref->node, &pages->root);
put_pg(sb, ref->pg);
}
ref->start = pg->start;
ref->end = pg->end;
ref->pg = pg;
get_pg(pg);
list_move(&ref->head, &pages->list);
old = pcpu_page_rbtree_walk(&pages->root, &ref->end, ref);
if (old) {
scoutfs_inc_counter(sb, item_pcpu_add_replaced);
rbtree_erase(&old->node, &pages->root);
list_move_tail(&old->head, &pages->list);
put_pg(sb, old->pg);
old->pg = NULL;
}
put_cpu_ptr(cinf->pcpu_pages);
}
/*
* If a page is removed from the page rbtree we clear its keys so that percpu
* references won't use the page and will drop their reference. Must be
* called with a write page rwlock.
*/
static void invalidate_pcpu_page(struct cached_page *pg)
{
scoutfs_key_set_zeros(&pg->start);
scoutfs_key_set_zeros(&pg->end);
}
static void init_pcpu_pages(struct item_cache_info *cinf, int cpu)
{
struct item_percpu_pages *pages = per_cpu_ptr(cinf->pcpu_pages, cpu);
struct pcpu_page_ref *ref;
int i;
pages->root = RB_ROOT;
INIT_LIST_HEAD(&pages->list);
for (i = 0; i < ARRAY_SIZE(pages->refs); i++) {
ref = &pages->refs[i];
ref->pg = NULL;
list_add_tail(&ref->head, &pages->list);
}
}
static void drop_pcpu_pages(struct super_block *sb,
struct item_cache_info *cinf, int cpu)
{
struct item_percpu_pages *pages = per_cpu_ptr(cinf->pcpu_pages, cpu);
struct pcpu_page_ref *ref;
int i;
for (i = 0; i < ARRAY_SIZE(pages->refs); i++) {
ref = &pages->refs[i];
if (ref->pg)
put_pg(sb, ref->pg);
ref->pg = NULL;
}
pages->root = RB_ROOT;
}
/*
* Set the keys of the destination pages of a split. We try to find the
* key which balances the space consumed by items in the resulting split
* pages. We move the split key to the right, setting the left end by
* decrementing that key. We bias towards advancing the left item first
* so that we don't use it and possibly decrementing the starting page
* key. We can't have a page that covers a single key. Callers of
* split should have tried compacting which ensures that if we split we
* must have multiple items, even if they all have the max value length.
*/
static void set_split_keys(struct cached_page *pg, struct cached_page *left,
struct cached_page *right)
{
struct cached_item *left_item = first_item(&pg->item_root);
struct cached_item *right_item = last_item(&pg->item_root);
struct cached_item *mid;
int left_tot = 0;
int right_tot = 0;
BUILD_BUG_ON((PAGE_SIZE / SCOUTFS_MAX_VAL_SIZE) < 4);
BUG_ON(scoutfs_key_compare(&pg->start, &pg->end) > 0);
BUG_ON(left_item == NULL);
BUG_ON(right_item == NULL);
BUG_ON(left_item == right_item);
while (left_item && right_item && left_item != right_item) {
if (left_tot <= right_tot) {
left_tot += item_val_bytes(left_item->val_len);
left_item = next_item(left_item);
} else {
right_tot += item_val_bytes(right_item->val_len);
right_item = prev_item(right_item);
}
}
mid = left_item ?: right_item;
left->start = pg->start;
left->end = mid->key;
scoutfs_key_dec(&left->end);
right->start = mid->key;
right->end = pg->end;
}
/*
* The caller found a page that didn't have room for the item they
* wanted to allocate. We allocate pages for the split and see if the
* page still needs splitting once we've locked it.
*
* To modify page keys we need a write lock on the page rbtree, which
* globally prevents reads from finding pages. We want to minimize this
* so we add empty pages with the split ranges to the rbtree and then
* perform the item motion only with the page locks held. This will
* exclude any users of the items in the affected range.
*/
static int try_split_page(struct super_block *sb, struct item_cache_info *cinf,
struct scoutfs_key *key, int val_len)
{
struct cached_page *right;
struct cached_page *left;
struct cached_page *pg;
struct cached_item *item;
struct rb_node **pnode;
struct rb_node *par;
int ret;
left = alloc_pg(sb, 0);
right = alloc_pg(sb, 0);
if (!left || !right) {
ret = -ENOMEM;
goto out;
}
write_lock(&cinf->rwlock);
pg = page_rbtree_walk(sb, &cinf->pg_root, key, key, NULL, NULL,
&par, &pnode);
if (pg == NULL) {
write_unlock(&cinf->rwlock);
ret = 0;
goto out;
}
write_lock(&pg->rwlock);
if (!page_has_room(pg, val_len))
compact_page_items(sb, pg, left);
if (page_has_room(pg, val_len)) {
write_unlock(&cinf->rwlock);
write_unlock(&pg->rwlock);
ret = 0;
goto out;
}
/* special case adding an empty page when key is after the last item */
item = last_item(&pg->item_root);
if (scoutfs_key_compare(key, &item->key) > 0) {
right->start = *key;
right->end = pg->end;
pg->end = *key;
scoutfs_key_dec(&pg->end);
write_trylock_will_succeed(&right->rwlock);
rbtree_insert(&right->node, par, pnode, &cinf->pg_root);
lru_accessed(sb, cinf, right);
/* adding right first removes pg */
add_pcpu_page(sb, cinf, right);
add_pcpu_page(sb, cinf, pg);
write_unlock(&cinf->rwlock);
write_unlock(&pg->rwlock);
write_unlock(&right->rwlock);
right = NULL;
ret = 0;
goto out;
}
scoutfs_inc_counter(sb, item_page_split);
/* pages are still private, tylock will succeed */
write_trylock_will_succeed(&left->rwlock);
write_trylock_will_succeed(&right->rwlock);
set_split_keys(pg, left, right);
rbtree_insert(&right->node, par, pnode, &cinf->pg_root);
rbtree_replace_node(&pg->node, &left->node, &cinf->pg_root);
lru_remove(sb, cinf, pg);
write_unlock(&cinf->rwlock);
/* move items while only holding page locks, visible once unlocked */
move_page_items(sb, cinf, pg, left, &left->start, &right->start);
lru_accessed(sb, cinf, left);
add_pcpu_page(sb, cinf, left);
write_unlock(&left->rwlock);
left = NULL;
move_page_items(sb, cinf, pg, right, &right->start, NULL);
lru_accessed(sb, cinf, right);
add_pcpu_page(sb, cinf, right);
write_unlock(&right->rwlock);
right = NULL;
/* and drop the source page, it was replaced above */
invalidate_pcpu_page(pg);
write_unlock(&pg->rwlock);
put_pg(sb, pg);
ret = 0;
out:
put_pg(sb, left);
put_pg(sb, right);
return ret;
}
/*
* The caller has a write-only cluster lock and wants to populate the
* cache so that it can insert an item without reading. They found a
* hole but unlocked so we check again under the lock after allocating.
* We insert an empty page that covers the key and extends to either the
* neighbours or the caller's (lock's) range.
*/
static int cache_empty_page(struct super_block *sb,
struct item_cache_info *cinf,
struct scoutfs_key *key, struct scoutfs_key *start,
struct scoutfs_key *end)
{
struct cached_page *prev;
struct cached_page *next;
struct cached_page *pg;
struct rb_node **pnode;
struct rb_node *par;
pg = alloc_pg(sb, 0);
if (!pg)
return -ENOMEM;
write_lock(&cinf->rwlock);
if (!page_rbtree_walk(sb, &cinf->pg_root, key, key, &prev, &next,
&par, &pnode)) {
pg->start = *start;
if (prev && scoutfs_key_compare(&prev->end, start) > 0) {
pg->start = prev->end;
scoutfs_key_inc(&pg->start);
}
pg->end = *end;
if (next && scoutfs_key_compare(&next->start, end) < 0) {
pg->end = next->start;
scoutfs_key_dec(&pg->end);
}
rbtree_insert(&pg->node, par, pnode, &cinf->pg_root);
lru_accessed(sb, cinf, pg);
pg = NULL;
}
write_unlock(&cinf->rwlock);
put_pg(sb, pg);
return 0;
}
/*
* Add a newly read item to the pages that we're assembling for
* insertion into the cache. These pages are private, they only exist
* on our root and aren't in dirty or lru lists.
*
* We need to store deletion items here as we read items from all the
* btrees so that they can override older items. The deletion items
* will be deleted before we insert the pages into the cache. We don't
* insert old versions of items into the tree here so that the trees
* don't have to compare seqs.
*/
static int read_page_item(struct super_block *sb, struct scoutfs_key *key, u64 seq, u8 flags,
void *val, int val_len, int fic, void *arg)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
const bool deletion = !!(flags & SCOUTFS_ITEM_FLAG_DELETION);
struct rb_root *root = arg;
struct cached_page *right = NULL;
struct cached_page *left = NULL;
struct cached_page *pg;
struct cached_item *found;
struct cached_item *item;
struct rb_node *p_par;
struct rb_node *par;
struct rb_node **p_pnode;
struct rb_node **pnode;
pg = page_rbtree_walk(sb, root, key, key, NULL, NULL, &p_par, &p_pnode);
found = item_rbtree_walk(&pg->item_root, key, NULL, &par, &pnode);
if (found && (found->seq >= seq))
return 0;
if (!page_has_room(pg, val_len)) {
left = alloc_pg(sb, 0);
/* split needs multiple items, sparse may not have enough */
if (!left)
return -ENOMEM;
compact_page_items(sb, pg, left);
found = item_rbtree_walk(&pg->item_root, key, NULL, &par,
&pnode);
}
item = alloc_item(pg, key, seq, deletion, val, val_len);
if (!item) {
/* simpler split of private pages, no locking/dirty/lru */
if (!left)
left = alloc_pg(sb, 0);
right = alloc_pg(sb, 0);
if (!left || !right) {
put_pg(sb, left);
put_pg(sb, right);
return -ENOMEM;
}
scoutfs_inc_counter(sb, item_read_pages_split);
set_split_keys(pg, left, right);
rbtree_insert(&right->node, p_par, p_pnode, root);
rbtree_replace_node(&pg->node, &left->node, root);
move_page_items(sb, cinf, pg, left,
&left->start, &right->start);
move_page_items(sb, cinf, pg, right, &right->start, NULL);
put_pg(sb, pg);
pg = scoutfs_key_compare(key, &left->end) <= 0 ? left : right;
item = alloc_item(pg, key, seq, deletion, val, val_len);
found = item_rbtree_walk(&pg->item_root, key, NULL, &par,
&pnode);
left = NULL;
right = NULL;
}
/* if deleted a deletion item will be required */
item->persistent = 1;
rbtree_insert(&item->node, par, pnode, &pg->item_root);
if (found)
erase_item(pg, found);
put_pg(sb, left);
put_pg(sb, right);
return 0;
}
/*
* The caller couldn't find a page that contains the key we're looking
* for. We combine a block's worth of items around the key in all the
* forest btrees and store them in pages. After filtering out deletions
* and duplicates, we insert any resulting pages which don't overlap
* with existing cached pages.
*
* The forest item reader is reading stable trees that could be
* overwritten. It can return -ESTALE which we return to the caller who
* will retry the operation and work with a new set of more recent
* btrees.
*
* We only insert uncached regions because this is called with cluster
* locks held, but without locking the cache. The regions we read can
* be stale with respect to the current cache, which can be read and
* dirtied by other cluster lock holders on our node, but the cluster
* locks protect the stable items we read.
*
* Using the presence of locally written dirty pages to override stale
* read pages only works if, well, the more recent locally written pages
* are still present. Readers are totally decoupled from writers and
* can have a set of items that is very old indeed. In the mean time
* more recent items would have been dirtied locally, committed,
* cleaned, and reclaimed. We have a coarse barrier which ensures that
* readers can't insert items read from old roots from before local data
* was written. If a write completes while a read is in progress the
* read will have to retry. The retried read can use cached blocks so
* we're relying on reads being much faster than writes to reduce the
* overhead to mostly cpu work of recollecting the items from cached
* blocks via a more recent root from the server.
*/
static int read_pages(struct super_block *sb, struct item_cache_info *cinf,
struct scoutfs_key *key, struct scoutfs_lock *lock)
{
struct rb_root root = RB_ROOT;
struct cached_page *right = NULL;
struct cached_page *pg;
struct cached_page *rd;
struct cached_item *item;
struct scoutfs_key start;
struct scoutfs_key end;
struct scoutfs_key inf;
struct scoutfs_key edge;
struct rb_node **pnode;
struct rb_node *par;
struct rb_node *pg_tmp;
struct rb_node *item_tmp;
u64 rdbar;
int pgi;
int ret;
/* start with an empty page that covers the whole lock */
pg = alloc_pg(sb, 0);
if (!pg) {
ret = -ENOMEM;
goto out;
}
pg->start = lock->start;
pg->end = lock->end;
rbtree_insert(&pg->node, NULL, &root.rb_node, &root);
read_lock(&cinf->rwlock);
rdbar = cinf->read_dirty_barrier;
read_unlock(&cinf->rwlock);
start = lock->start;
end = lock->end;
ret = scoutfs_forest_read_items(sb, key, &lock->start, &start, &end, read_page_item, &root);
if (ret < 0)
goto out;
/* clean up our read items and pages before locking */
for_each_page_safe(&root, pg, pg_tmp) {
/* trim any items we read outside the read range */
scoutfs_key_set_zeros(&inf);
edge = start;
scoutfs_key_dec(&edge);
pgi = trim_page_intersection(sb, cinf, pg, NULL, &inf, &edge);
if (pgi != PGI_INSIDE) {
scoutfs_key_set_ones(&inf);
edge = end;
scoutfs_key_inc(&edge);
pgi = trim_page_intersection(sb, cinf, pg, NULL, &edge,
&inf);
}
if (pgi == PGI_INSIDE) {
rbtree_erase(&pg->node, &root);
put_pg(sb, pg);
continue;
}
/* drop deletion items, we don't need them in the cache */
for_each_item_safe(&pg->item_root, item, item_tmp) {
if (item->deletion)
erase_item(pg, item);
}
}
retry:
write_lock(&cinf->rwlock);
/* can't insert if write has cleaned since we read */
if (cinf->read_dirty_barrier != rdbar) {
scoutfs_inc_counter(sb, item_read_pages_barrier);
ret = -ESTALE;
goto unlock;
}
while ((rd = first_page(&root))) {
pg = page_rbtree_walk(sb, &cinf->pg_root, &rd->start, &rd->end,
NULL, NULL, &par, &pnode);
if (!pg) {
/* insert read pages that don't intersect */
rbtree_erase(&rd->node, &root);
rbtree_insert(&rd->node, par, pnode, &cinf->pg_root);
lru_accessed(sb, cinf, rd);
trace_scoutfs_item_read_page(sb, key, &rd->start,
&rd->end);
continue;
}
pgi = trim_page_intersection(sb, cinf, rd, right, &pg->start,
&pg->end);
if (pgi == PGI_INSIDE) {
rbtree_erase(&rd->node, &root);
put_pg(sb, rd);
} else if (pgi == PGI_BISECT_NEEDED) {
write_unlock(&cinf->rwlock);
right = alloc_pg(sb, 0);
if (!right) {
ret = -ENOMEM;
goto out;
}
goto retry;
} else if (pgi == PGI_BISECT) {
page_rbtree_walk(sb, &root, &right->start, &right->end,
NULL, NULL, &par, &pnode);
rbtree_insert(&right->node, par, pnode, &root);
right = NULL;
}
}
ret = 0;
unlock:
write_unlock(&cinf->rwlock);
out:
/* free any pages we left dangling on error */
for_each_page_safe(&root, rd, pg_tmp) {
rbtree_erase(&rd->node, &root);
put_pg(sb, rd);
}
put_pg(sb, right);
return ret;
}
/*
* Get a locked cached page for the caller to work with. This populates
* the cache on misses and can ensure that the locked page has enough
* room for an item allocation for the caller. Unfortunately, sparse
* doesn't seem to deal very well with the pattern of conditional lock
* acquisition. Callers manually add __acquire.
*/
static int get_cached_page(struct super_block *sb,
struct item_cache_info *cinf,
struct scoutfs_lock *lock, struct scoutfs_key *key,
bool write, bool alloc, int val_len,
struct cached_page **pg_ret)
{
struct cached_page *pg = NULL;
struct rb_node **pnode;
struct rb_node *par;
int ret;
if (WARN_ON_ONCE(alloc && !write))
return -EINVAL;
pg = get_pcpu_page(sb, cinf, key, write);
if (pg) {
__acquire(pg->rwlock);
if (!alloc || page_has_room(pg, val_len))
goto found;
if (write)
write_unlock(&pg->rwlock);
else
read_unlock(&pg->rwlock);
pg = NULL;
}
retry:
read_lock(&cinf->rwlock);
pg = page_rbtree_walk(sb, &cinf->pg_root, key, key, NULL, NULL,
&par, &pnode);
if (pg == NULL) {
read_unlock(&cinf->rwlock);
if (lock->mode == SCOUTFS_LOCK_WRITE_ONLY)
ret = cache_empty_page(sb, cinf, key, &lock->start,
&lock->end);
else
ret = read_pages(sb, cinf, key, lock);
if (ret < 0 && ret != -ESTALE)
goto out;
scoutfs_inc_counter(sb, item_read_pages_retry);
goto retry;
}
if (write)
write_lock(&pg->rwlock);
else
read_lock(&pg->rwlock);
if (alloc && !page_has_room(pg, val_len)) {
read_unlock(&cinf->rwlock);
if (write)
write_unlock(&pg->rwlock);
else
read_unlock(&pg->rwlock);
ret = try_split_page(sb, cinf, key, val_len);
if (ret < 0)
goto out;
goto retry;
}
read_unlock(&cinf->rwlock);
add_pcpu_page(sb, cinf, pg);
found:
__release(pg_rwlock);
lru_accessed(sb, cinf, pg);
ret = 0;
out:
if (ret < 0)
*pg_ret = NULL;
else
*pg_ret = pg;
return ret;
}
static int lock_safe(struct super_block *sb, struct scoutfs_lock *lock, struct scoutfs_key *key,
int mode)
{
bool prot = scoutfs_lock_protected(lock, key, mode);
if (!prot) {
static bool once = false;
if (!once) {
scoutfs_err(sb, "lock (start "SK_FMT" end "SK_FMT" mode 0x%x) does not protect operation (key "SK_FMT" mode 0x%x)",
SK_ARG(&lock->start), SK_ARG(&lock->end), lock->mode,
SK_ARG(key), mode);
dump_stack();
once = true;
}
return -EINVAL;
}
return 0;
}
static int optional_lock_mode_match(struct scoutfs_lock *lock, int mode)
{
if (WARN_ON_ONCE(lock && lock->mode != mode))
return -EINVAL;
else
return 0;
}
/*
* Copy the cached item's value into the caller's value. The number of
* bytes copied is returned. A null val returns 0.
*/
static int copy_val(void *dst, int dst_len, void *src, int src_len)
{
int ret;
BUG_ON(dst_len < 0 || src_len < 0);
ret = min(dst_len, src_len);
if (ret)
memcpy(dst, src, ret);
return ret;
}
/*
* Find an item with the given key and copy its value to the caller.
* The amount of bytes copied is returned which can be 0 or truncated if
* the caller's buffer isn't big enough.
*/
static int item_lookup(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, int len_limit, struct scoutfs_lock *lock)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct cached_item *item;
struct cached_page *pg;
int ret;
scoutfs_inc_counter(sb, item_lookup);
if ((ret = lock_safe(sb, lock, key, SCOUTFS_LOCK_READ)))
goto out;
ret = get_cached_page(sb, cinf, lock, key, false, false, 0, &pg);
if (ret < 0)
goto out;
__acquire(&pg->rwlock);
item = item_rbtree_walk(&pg->item_root, key, NULL, NULL, NULL);
if (!item || item->deletion)
ret = -ENOENT;
else if (len_limit > 0 && item->val_len > len_limit)
ret = -EIO;
else
ret = copy_val(val, val_len, item->val, item->val_len);
read_unlock(&pg->rwlock);
out:
return ret;
}
int scoutfs_item_lookup(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, struct scoutfs_lock *lock)
{
return item_lookup(sb, key, val, val_len, 0, lock);
}
/*
* Copy an item's value into the caller's buffer. If the item's value
* is larger than the caller's buffer then -EIO is returned. If the
* item is smaller then the bytes from the end of the copied value to
* the end of the buffer are zeroed. The number of value bytes copied
* is returned, and 0 can be returned for an item with no value.
*/
int scoutfs_item_lookup_smaller_zero(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, struct scoutfs_lock *lock)
{
int ret;
ret = item_lookup(sb, key, val, val_len, val_len, lock);
if (ret >= 0 && ret < val_len)
memset(val + ret, 0, val_len - ret);
return ret;
}
int scoutfs_item_lookup_exact(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len,
struct scoutfs_lock *lock)
{
int ret;
ret = item_lookup(sb, key, val, val_len, 0, lock);
if (ret == val_len)
ret = 0;
else if (ret >= 0)
ret = -EIO;
return ret;
}
/*
* Return the next item starting with the given key and returning the
* last key at most.
*
* The range covered by the lock also limits the last item that can be
* returned. -ENOENT can be returned when there are no next items
* covered by the lock but there are still items before the last key
* outside of the lock. The caller needs to know to reacquire the next
* lock to continue iteration.
*
* -ENOENT is returned if there are no items between the given and last
* keys inside the range covered by the lock.
*
* The next item's key is copied to the caller's key.
*
* The next item's value is copied into the callers value. The number
* of value bytes copied is returned. The copied value can be truncated
* by the caller's value buffer length.
*/
int scoutfs_item_next(struct super_block *sb, struct scoutfs_key *key,
struct scoutfs_key *last, void *val, int val_len,
struct scoutfs_lock *lock)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct cached_item *item;
struct cached_item *next;
struct cached_page *pg = NULL;
struct scoutfs_key pos;
int ret;
scoutfs_inc_counter(sb, item_next);
/* use the end key as the last key if it's closer */
if (scoutfs_key_compare(&lock->end, last) < 0)
last = &lock->end;
if (scoutfs_key_compare(key, last) > 0) {
ret = -ENOENT;
goto out;
}
if ((ret = lock_safe(sb, lock, key, SCOUTFS_LOCK_READ)))
goto out;
pos = *key;
for (;;) {
ret = get_cached_page(sb, cinf, lock, &pos, false, false, 0,
&pg);
if (ret < 0)
goto out;
__acquire(&pg->rwlock);
item = item_rbtree_walk(&pg->item_root, &pos, &next,
NULL, NULL) ?: next;
while (item && scoutfs_key_compare(&item->key, last) <= 0) {
if (!item->deletion) {
*key = item->key;
ret = copy_val(val, val_len, item->val,
item->val_len);
goto unlock;
}
item = next_item(item);
}
if (scoutfs_key_compare(&pg->end, last) >= 0) {
ret = -ENOENT;
goto unlock;
}
pos = pg->end;
read_unlock(&pg->rwlock);
scoutfs_key_inc(&pos);
}
unlock:
read_unlock(&pg->rwlock);
out:
return ret;
}
/*
* An item's seq is greater of the client transaction's seq and the
* lock's write_seq. This ensures that multiple commits in one lock
* grant will have increasing seqs, and new locks in open commits will
* also increase the seqs. It lets us limit the inputs of item merging
* to the last stable seq and ensure that all the items in open
* transactions and granted locks will have greater seqs.
*
* This is a little awkward for WRITE_ONLY locks which can have much
* older versions than the version of locked primary data that they're
* operating on behalf of. Callers can optionally provide that primary
* lock to get the version from. This ensures that items created under
* WRITE_ONLY locks can not have versions less than their primary data.
*/
static u64 item_seq(struct super_block *sb, struct scoutfs_lock *lock,
struct scoutfs_lock *primary)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
return max3(sbi->trans_seq, lock->write_seq, primary ? primary->write_seq : 0);
}
/*
* Mark the item dirty. Dirtying while holding a transaction pins the
* page holding the item and guarantees that the item can be deleted or
* updated (without increasing the value length) during the transaction
* without errors.
*/
int scoutfs_item_dirty(struct super_block *sb, struct scoutfs_key *key,
struct scoutfs_lock *lock)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct cached_item *item;
struct cached_page *pg;
int ret;
scoutfs_inc_counter(sb, item_dirty);
if ((ret = lock_safe(sb, lock, key, SCOUTFS_LOCK_WRITE)))
goto out;
ret = scoutfs_forest_set_bloom_bits(sb, lock);
if (ret < 0)
goto out;
ret = get_cached_page(sb, cinf, lock, key, true, false, 0, &pg);
if (ret < 0)
goto out;
__acquire(pg->rwlock);
item = item_rbtree_walk(&pg->item_root, key, NULL, NULL, NULL);
if (!item || item->deletion) {
ret = -ENOENT;
} else {
item->seq = item_seq(sb, lock, NULL);
mark_item_dirty(sb, cinf, pg, NULL, item);
ret = 0;
}
write_unlock(&pg->rwlock);
out:
return ret;
}
/*
* Create a new cached item with the given value. -EEXIST is returned
* if the item already exists. Forcing creates the item without knowldge
* of any existing items.. it doesn't read and can't return -EEXIST.
*/
static int item_create(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, struct scoutfs_lock *lock,
struct scoutfs_lock *primary, int mode, bool force)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
const u64 seq = item_seq(sb, lock, primary);
struct cached_item *found;
struct cached_item *item;
struct cached_page *pg;
struct rb_node **pnode;
struct rb_node *par;
int ret;
scoutfs_inc_counter(sb, item_create);
if ((ret = lock_safe(sb, lock, key, mode)) ||
(ret = optional_lock_mode_match(primary, SCOUTFS_LOCK_WRITE)))
goto out;
ret = scoutfs_forest_set_bloom_bits(sb, lock);
if (ret < 0)
goto out;
ret = get_cached_page(sb, cinf, lock, key, true, true, val_len, &pg);
if (ret < 0)
goto out;
__acquire(pg->rwlock);
found = item_rbtree_walk(&pg->item_root, key, NULL, &par, &pnode);
if (!force && found && !found->deletion) {
ret = -EEXIST;
goto unlock;
}
item = alloc_item(pg, key, seq, false, val, val_len);
rbtree_insert(&item->node, par, pnode, &pg->item_root);
mark_item_dirty(sb, cinf, pg, NULL, item);
if (found) {
item->persistent = found->persistent;
clear_item_dirty(sb, cinf, pg, found);
erase_item(pg, found);
}
if (force)
item->persistent = 1;
ret = 0;
unlock:
write_unlock(&pg->rwlock);
out:
return ret;
}
int scoutfs_item_create(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, struct scoutfs_lock *lock)
{
return item_create(sb, key, val, val_len, lock, NULL,
SCOUTFS_LOCK_WRITE, false);
}
int scoutfs_item_create_force(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len,
struct scoutfs_lock *lock, struct scoutfs_lock *primary)
{
return item_create(sb, key, val, val_len, lock, primary,
SCOUTFS_LOCK_WRITE_ONLY, true);
}
/*
* Update an item with a new value. If the new value is smaller and the
* item is dirty then this is guaranteed to succeed. It can fail if the
* item doesn't exist or it gets errors reading or allocating new pages
* for a larger value.
*/
int scoutfs_item_update(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, struct scoutfs_lock *lock)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
const u64 seq = item_seq(sb, lock, NULL);
struct cached_item *item;
struct cached_item *found;
struct cached_page *pg;
struct rb_node **pnode;
struct rb_node *par;
int ret;
scoutfs_inc_counter(sb, item_update);
if ((ret = lock_safe(sb, lock, key, SCOUTFS_LOCK_WRITE)))
goto out;
ret = scoutfs_forest_set_bloom_bits(sb, lock);
if (ret < 0)
goto out;
ret = get_cached_page(sb, cinf, lock, key, true, true, val_len, &pg);
if (ret < 0)
goto out;
__acquire(pg->rwlock);
found = item_rbtree_walk(&pg->item_root, key, NULL, &par, &pnode);
if (!found || found->deletion) {
ret = -ENOENT;
goto unlock;
}
if (val_len <= found->val_len) {
if (val_len)
memcpy(found->val, val, val_len);
if (val_len < found->val_len)
pg->erased_bytes += item_val_bytes(found->val_len) -
item_val_bytes(val_len);
found->val_len = val_len;
found->seq = seq;
mark_item_dirty(sb, cinf, pg, NULL, found);
} else {
item = alloc_item(pg, key, seq, false, val, val_len);
item->persistent = found->persistent;
rbtree_insert(&item->node, par, pnode, &pg->item_root);
mark_item_dirty(sb, cinf, pg, NULL, item);
clear_item_dirty(sb, cinf, pg, found);
erase_item(pg, found);
}
ret = 0;
unlock:
write_unlock(&pg->rwlock);
out:
return ret;
}
/*
* Add a delta item. Delta items are an incremental change relative to
* the current persistent delta items. We never have to read the
* current items so the caller always writes with write only locks. If
* combining the current delta item and the caller's item results in a
* null we can just drop it, we don't have to emit a deletion item.
*
* Delta items don't have to worry about creating items with old
* versions under write_only locks. The versions don't impact how we
* merge two items.
*/
int scoutfs_item_delta(struct super_block *sb, struct scoutfs_key *key,
void *val, int val_len, struct scoutfs_lock *lock)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
const u64 seq = item_seq(sb, lock, NULL);
struct cached_item *item;
struct cached_page *pg;
struct rb_node **pnode;
struct rb_node *par;
int ret;
scoutfs_inc_counter(sb, item_delta);
if ((ret = lock_safe(sb, lock, key, SCOUTFS_LOCK_WRITE_ONLY)))
goto out;
ret = scoutfs_forest_set_bloom_bits(sb, lock);
if (ret < 0)
goto out;
ret = get_cached_page(sb, cinf, lock, key, true, true, val_len, &pg);
if (ret < 0)
goto out;
__acquire(pg->rwlock);
item = item_rbtree_walk(&pg->item_root, key, NULL, &par, &pnode);
if (item) {
if (!item->delta) {
ret = -EIO;
goto unlock;
}
ret = scoutfs_forest_combine_deltas(key, item->val, item->val_len, val, val_len);
if (ret <= 0) {
if (ret == 0)
ret = -EIO;
goto unlock;
}
if (ret == SCOUTFS_DELTA_COMBINED) {
item->seq = seq;
mark_item_dirty(sb, cinf, pg, NULL, item);
} else if (ret == SCOUTFS_DELTA_COMBINED_NULL) {
clear_item_dirty(sb, cinf, pg, item);
erase_item(pg, item);
} else {
ret = -EIO;
goto unlock;
}
ret = 0;
} else {
item = alloc_item(pg, key, seq, false, val, val_len);
rbtree_insert(&item->node, par, pnode, &pg->item_root);
mark_item_dirty(sb, cinf, pg, NULL, item);
item->delta = 1;
ret = 0;
}
unlock:
write_unlock(&pg->rwlock);
out:
return ret;
}
/*
* Delete an item from the cache. We can leave behind a dirty deletion
* item if there is a persistent item that needs to be overwritten.
* This can't fail if the caller knows that the item exists and it has
* been dirtied during the transaction it holds. If we're forcing then
* we're not reading the old state of the item and have to create a
* deletion item if there isn't one already cached.
*/
static int item_delete(struct super_block *sb, struct scoutfs_key *key,
struct scoutfs_lock *lock, struct scoutfs_lock *primary,
int mode, bool force)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
const u64 seq = item_seq(sb, lock, primary);
struct cached_item *item;
struct cached_page *pg;
struct rb_node **pnode;
struct rb_node *par;
int ret;
scoutfs_inc_counter(sb, item_delete);
if ((ret = lock_safe(sb, lock, key, mode)) ||
(ret = optional_lock_mode_match(primary, SCOUTFS_LOCK_WRITE)))
goto out;
ret = scoutfs_forest_set_bloom_bits(sb, lock);
if (ret < 0)
goto out;
ret = get_cached_page(sb, cinf, lock, key, true, force, 0, &pg);
if (ret < 0)
goto out;
__acquire(pg->rwlock);
item = item_rbtree_walk(&pg->item_root, key, NULL, &par, &pnode);
if (!force && (!item || item->deletion)) {
ret = -ENOENT;
goto unlock;
}
if (!item) {
item = alloc_item(pg, key, seq, false, NULL, 0);
rbtree_insert(&item->node, par, pnode, &pg->item_root);
}
if (force)
item->persistent = 1;
if (!item->persistent) {
/* can just forget items that aren't yet persistent */
clear_item_dirty(sb, cinf, pg, item);
erase_item(pg, item);
} else {
/* must emit deletion to clobber old persistent item */
item->seq = seq;
item->deletion = 1;
pg->erased_bytes += item_val_bytes(item->val_len) -
item_val_bytes(0);
item->val_len = 0;
mark_item_dirty(sb, cinf, pg, NULL, item);
}
ret = 0;
unlock:
write_unlock(&pg->rwlock);
out:
return ret;
}
int scoutfs_item_delete(struct super_block *sb, struct scoutfs_key *key,
struct scoutfs_lock *lock)
{
return item_delete(sb, key, lock, NULL, SCOUTFS_LOCK_WRITE, false);
}
int scoutfs_item_delete_force(struct super_block *sb, struct scoutfs_key *key,
struct scoutfs_lock *lock, struct scoutfs_lock *primary)
{
return item_delete(sb, key, lock, primary, SCOUTFS_LOCK_WRITE_ONLY, true);
}
u64 scoutfs_item_dirty_pages(struct super_block *sb)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
return (u64)atomic_read(&cinf->dirty_pages);
}
static int cmp_pg_start(void *priv, KC_LIST_CMP_CONST struct list_head *A, KC_LIST_CMP_CONST struct list_head *B)
{
KC_LIST_CMP_CONST struct cached_page *a = list_entry(A, KC_LIST_CMP_CONST struct cached_page, dirty_head);
KC_LIST_CMP_CONST struct cached_page *b = list_entry(B, KC_LIST_CMP_CONST struct cached_page, dirty_head);
return scoutfs_key_compare(&a->start, &b->start);
}
static int cmp_item_key(void *priv, KC_LIST_CMP_CONST struct list_head *A, KC_LIST_CMP_CONST struct list_head *B)
{
KC_LIST_CMP_CONST struct cached_item *a = list_entry(A, KC_LIST_CMP_CONST struct cached_item, dirty_head);
KC_LIST_CMP_CONST struct cached_item *b = list_entry(B, KC_LIST_CMP_CONST struct cached_item, dirty_head);
return scoutfs_key_compare(&a->key, &b->key);
}
/*
* Write all the dirty items into dirty blocks in the forest of btrees.
* If this succeeds then the dirty blocks can be submitted to commit
* their transaction. If this returns an error then the dirty blocks
* could have a partial set of the dirty items and result in an
* inconsistent state. The blocks should only be committed once all the
* dirty items have been written.
*
* This is called during transaction commit which prevents item writers
* from entering a transaction and dirtying items. The set of dirty
* items will be constant.
*
* But the pages that contain the dirty items can be changing. A
* neighbouring read lock can be invalidated and require bisecting a
* page, moving dirty items to a new page. That new page will be put
* after the original page on the dirty list. This will be done under
* the page rwlock and the global dirty_lock.
*
* We first sort the pages by their keys, then lock each page and copy
* its items into a private allocated singly-linked list of the items to
* dirty. Once we have that we can hand it off to the forest of btrees
* to write into items without causing any contention with other page
* users.
*/
int scoutfs_item_write_dirty(struct super_block *sb)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct scoutfs_btree_item_list *first;
struct scoutfs_btree_item_list **prev;
struct scoutfs_btree_item_list *lst;
struct cached_item *item;
struct cached_page *pg;
struct page *second = NULL;
struct page *page;
LIST_HEAD(pages);
LIST_HEAD(pos);
u64 max_seq = 0;
int bytes;
int off;
int ret;
if (atomic_read(&cinf->dirty_pages) == 0)
return 0;
scoutfs_inc_counter(sb, item_write_dirty);
/* sort page dirty list by keys */
read_lock(&cinf->rwlock);
spin_lock(&cinf->dirty_lock);
/* sort cached pages by key, add our pos head */
list_sort(NULL, &cinf->dirty_list, cmp_pg_start);
list_add(&pos, &cinf->dirty_list);
read_unlock(&cinf->rwlock);
spin_unlock(&cinf->dirty_lock);
page = alloc_page(GFP_NOFS);
if (!page) {
ret = -ENOMEM;
goto out;
}
list_add(&page->lru, &pages);
first = NULL;
prev = &first;
off = 0;
while (!list_empty_careful(&pos)) {
if (!second) {
second = alloc_page(GFP_NOFS);
if (!second) {
ret = -ENOMEM;
goto out;
}
list_add(&second->lru, &pages);
}
/* read lock next sorted page, we're only dirty_list user */
spin_lock(&cinf->dirty_lock);
pg = list_entry(pos.next, struct cached_page, dirty_head);
if (!read_trylock(&pg->rwlock)) {
spin_unlock(&cinf->dirty_lock);
cpu_relax();
continue;
}
spin_unlock(&cinf->dirty_lock);
list_sort(NULL, &pg->dirty_list, cmp_item_key);
list_for_each_entry(item, &pg->dirty_list, dirty_head) {
bytes = offsetof(struct scoutfs_btree_item_list,
val[item->val_len]);
max_seq = max(max_seq, item->seq);
if (off + bytes > PAGE_SIZE) {
page = second;
second = NULL;
off = 0;
}
lst = (void *)page_address(page) + off;
off += round_up(bytes, CACHED_ITEM_ALIGN);
lst->next = NULL;
*prev = lst;
prev = &lst->next;
lst->key = item->key;
lst->seq = item->seq;
lst->flags = item->deletion ? SCOUTFS_ITEM_FLAG_DELETION : 0;
lst->val_len = item->val_len;
memcpy(lst->val, item->val, item->val_len);
}
spin_lock(&cinf->dirty_lock);
if (pg->dirty_head.next == &cinf->dirty_list)
list_del_init(&pos);
else
list_move(&pos, &pg->dirty_head);
spin_unlock(&cinf->dirty_lock);
read_unlock(&pg->rwlock);
}
/* store max item seq in forest's log_trees */
scoutfs_forest_set_max_seq(sb, max_seq);
/* write all the dirty items into log btree blocks */
ret = scoutfs_forest_insert_list(sb, first);
out:
list_for_each_entry_safe(page, second, &pages, lru) {
list_del_init(&page->lru);
__free_page(page);
}
return ret;
}
/*
* The caller has successfully committed all the dirty btree blocks that
* contained the currently dirty items. Clear all the dirty items and
* pages.
*
* This strange lock/trylock loop comes from sparse issuing spurious
* mismatched context warnings if we do anything (like unlock and relax)
* in the else branch of the failed trylock. We're jumping through
* hoops to not use the else but still drop and reacquire the dirty_lock
* if the trylock fails.
*/
int scoutfs_item_write_done(struct super_block *sb)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct cached_item *item;
struct cached_item *tmp;
struct cached_page *pg;
/* don't let read_pages miss written+cleaned items */
write_lock(&cinf->rwlock);
cinf->read_dirty_barrier++;
write_unlock(&cinf->rwlock);
spin_lock(&cinf->dirty_lock);
while ((pg = list_first_entry_or_null(&cinf->dirty_list, struct cached_page, dirty_head))) {
if (write_trylock(&pg->rwlock)) {
spin_unlock(&cinf->dirty_lock);
list_for_each_entry_safe(item, tmp, &pg->dirty_list,
dirty_head) {
clear_item_dirty(sb, cinf, pg, item);
if (item->delta)
scoutfs_inc_counter(sb, item_delta_written);
/* free deletion items */
if (item->deletion || item->delta)
erase_item(pg, item);
else
item->persistent = 1;
}
write_unlock(&pg->rwlock);
spin_lock(&cinf->dirty_lock);
}
spin_unlock(&cinf->dirty_lock);
spin_lock(&cinf->dirty_lock);
} while (pg);
spin_unlock(&cinf->dirty_lock);
return 0;
}
/*
* Return true if the item cache covers the given range and set *dirty
* to true if any items in the cached range are dirty.
*
* This is relatively rarely called as locks are granted to make sure
* that we *don't* have existing cache covered by the lock which then
* must be inconsistent. Finding pages is the critical error case,
* under correct operation this will be a read locked walk of the page
* rbtree that doesn't find anything.
*/
bool scoutfs_item_range_cached(struct super_block *sb,
struct scoutfs_key *start,
struct scoutfs_key *end, bool *dirty)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct cached_item *item;
struct cached_page *pg;
struct scoutfs_key pos;
bool cached;
cached = false;
*dirty = false;
pos = *start;
read_lock(&cinf->rwlock);
while (!(*dirty) && scoutfs_key_compare(&pos, end) <= 0 &&
(pg = page_rbtree_walk(sb, &cinf->pg_root, &pos, end, NULL, NULL,
NULL, NULL))) {
cached = true;
read_lock(&pg->rwlock);
read_unlock(&cinf->rwlock);
/* the dirty list isn't sorted :/ */
list_for_each_entry(item, &pg->dirty_list, dirty_head) {
if (!scoutfs_key_compare_ranges(&item->key, &item->key,
start, end)) {
*dirty = true;
break;
}
}
pos = pg->end;
scoutfs_key_inc(&pos);
read_unlock(&pg->rwlock);
read_lock(&cinf->rwlock);
}
read_unlock(&cinf->rwlock);
return cached;
}
/*
* Remove the cached items in the given range. We drop pages that are
* fully inside the range and trim any pages that intersect it. This is
* being by locking for a lock that can't be used so there can't be item
* calls within the range. It can race with all our other page uses.
*/
void scoutfs_item_invalidate(struct super_block *sb, struct scoutfs_key *start,
struct scoutfs_key *end)
{
DECLARE_ITEM_CACHE_INFO(sb, cinf);
struct cached_page *right = NULL;
struct cached_page *pg;
struct rb_node **pnode;
struct rb_node *par;
int pgi;
scoutfs_inc_counter(sb, item_invalidate);
retry:
write_lock(&cinf->rwlock);
while ((pg = page_rbtree_walk(sb, &cinf->pg_root, start, end, NULL,
NULL, &par, &pnode))) {
scoutfs_inc_counter(sb, item_invalidate_page);
write_lock(&pg->rwlock);
pgi = trim_page_intersection(sb, cinf, pg, right, start, end);
trace_scoutfs_item_invalidate_page(sb, start, end,
&pg->start, &pg->end, pgi);
BUG_ON(pgi == PGI_DISJOINT); /* walk wouldn't ret disjoint */
if (pgi == PGI_INSIDE) {
/* free entirely invalidated page */
lru_remove(sb, cinf, pg);
rbtree_erase(&pg->node, &cinf->pg_root);
invalidate_pcpu_page(pg);
write_unlock(&pg->rwlock);
put_pg(sb, pg);
continue;
} else if (pgi == PGI_BISECT_NEEDED) {
/* allocate so we can bisect a larger page */
write_unlock(&cinf->rwlock);
write_unlock(&pg->rwlock);
right = alloc_pg(sb, __GFP_NOFAIL);
goto retry;
} else if (pgi == PGI_BISECT) {
/* inv was entirely inside page, done after bisect */
write_trylock_will_succeed(&right->rwlock);
rbtree_insert(&right->node, par, pnode, &cinf->pg_root);
lru_accessed(sb, cinf, right);
write_unlock(&right->rwlock);
write_unlock(&pg->rwlock);
right = NULL;
break;
}
/* OLAP trimmed edge, keep searching */
write_unlock(&pg->rwlock);
}
write_unlock(&cinf->rwlock);
put_pg(sb, right);
}
static unsigned long item_cache_count_objects(struct shrinker *shrink,
struct shrink_control *sc)
{
struct item_cache_info *cinf = KC_SHRINKER_CONTAINER_OF(shrink, struct item_cache_info);
struct super_block *sb = cinf->sb;
scoutfs_inc_counter(sb, item_cache_count_objects);
return shrinker_min_long(cinf->lru_pages);
}
/*
* Shrink the size the item cache. We're operating against the fast
* path lock ordering and we skip pages if we can't acquire locks. We
* can run into dirty pages or pages with items that weren't visible to
* the earliest active reader which must be skipped.
*/
static unsigned long item_cache_scan_objects(struct shrinker *shrink,
struct shrink_control *sc)
{
struct item_cache_info *cinf = KC_SHRINKER_CONTAINER_OF(shrink, struct item_cache_info);
struct super_block *sb = cinf->sb;
struct cached_page *tmp;
struct cached_page *pg;
unsigned long freed = 0;
int nr = sc->nr_to_scan;
scoutfs_inc_counter(sb, item_cache_scan_objects);
write_lock(&cinf->rwlock);
spin_lock(&cinf->lru_lock);
list_for_each_entry_safe(pg, tmp, &cinf->lru_list, lru_head) {
if (!write_trylock(&pg->rwlock)) {
scoutfs_inc_counter(sb, item_shrink_page_trylock);
continue;
}
if (!list_empty(&pg->dirty_list)) {
scoutfs_inc_counter(sb, item_shrink_page_dirty);
write_unlock(&pg->rwlock);
continue;
}
scoutfs_inc_counter(sb, item_shrink_page);
__lru_remove(sb, cinf, pg);
rbtree_erase(&pg->node, &cinf->pg_root);
invalidate_pcpu_page(pg);
write_unlock(&pg->rwlock);
freed++;
put_pg(sb, pg);
if (--nr == 0)
break;
}
write_unlock(&cinf->rwlock);
spin_unlock(&cinf->lru_lock);
return freed;
}
int scoutfs_item_setup(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
struct item_cache_info *cinf;
int cpu;
cinf = kzalloc(sizeof(struct item_cache_info), GFP_KERNEL);
if (!cinf)
return -ENOMEM;
cinf->sb = sb;
rwlock_init(&cinf->rwlock);
cinf->pg_root = RB_ROOT;
spin_lock_init(&cinf->dirty_lock);
INIT_LIST_HEAD(&cinf->dirty_list);
atomic_set(&cinf->dirty_pages, 0);
spin_lock_init(&cinf->lru_lock);
INIT_LIST_HEAD(&cinf->lru_list);
cinf->pcpu_pages = alloc_percpu(struct item_percpu_pages);
if (!cinf->pcpu_pages)
return -ENOMEM;
for_each_possible_cpu(cpu)
init_pcpu_pages(cinf, cpu);
KC_INIT_SHRINKER_FUNCS(&cinf->shrinker, item_cache_count_objects,
item_cache_scan_objects);
KC_REGISTER_SHRINKER(&cinf->shrinker, "scoutfs-item:" SCSBF, SCSB_ARGS(sb));
sbi->item_cache_info = cinf;
return 0;
}
/*
* There must be no more item callers at this point.
*/
void scoutfs_item_destroy(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
struct item_cache_info *cinf = sbi->item_cache_info;
struct cached_page *tmp;
struct cached_page *pg;
int cpu;
if (cinf) {
KC_UNREGISTER_SHRINKER(&cinf->shrinker);
for_each_possible_cpu(cpu)
drop_pcpu_pages(sb, cinf, cpu);
free_percpu(cinf->pcpu_pages);
rbtree_postorder_for_each_entry_safe(pg, tmp, &cinf->pg_root,
node) {
RB_CLEAR_NODE(&pg->node);
INIT_LIST_HEAD(&pg->lru_head);
INIT_LIST_HEAD(&pg->dirty_list);
INIT_LIST_HEAD(&pg->dirty_head);
put_pg(sb, pg);
}
kfree(cinf);
sbi->item_cache_info = NULL;
}
}