scoutfs: use forest in locking and transaction

Transaction commit now has to ask the forest to write the btrees during
a transaction commit instead of writing dirty items in segments.  It
also determines if holds fit in the dirty transaction by looking at
dirty btree blocks instead of item counts.

Locking no longer has to invalidate a private item cache because the
forest paths use the btree block cache where inconsistency is discovered
and invalidated as blocks are read.

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2020-01-17 11:21:36 -08:00
committed by Zach Brown
parent 48448d3926
commit 58f062a2c1
5 changed files with 23 additions and 96 deletions
+1 -4
View File
@@ -100,7 +100,7 @@
EXPAND_COUNTER(lock_grace_wait) \
EXPAND_COUNTER(lock_grant_request) \
EXPAND_COUNTER(lock_grant_response) \
EXPAND_COUNTER(lock_invalidate_clean_item) \
EXPAND_COUNTER(lock_invalidate_commit) \
EXPAND_COUNTER(lock_invalidate_coverage) \
EXPAND_COUNTER(lock_invalidate_inode) \
EXPAND_COUNTER(lock_invalidate_request) \
@@ -113,7 +113,6 @@
EXPAND_COUNTER(lock_shrink_request_aborted) \
EXPAND_COUNTER(lock_unlock) \
EXPAND_COUNTER(lock_wait) \
EXPAND_COUNTER(lock_write_dirty_item) \
EXPAND_COUNTER(manifest_compact_migrate) \
EXPAND_COUNTER(manifest_hard_stale_error) \
EXPAND_COUNTER(manifest_read_excluded_key) \
@@ -157,8 +156,6 @@
EXPAND_COUNTER(trans_commit_item_flush) \
EXPAND_COUNTER(trans_commit_sync_fs) \
EXPAND_COUNTER(trans_commit_timer) \
EXPAND_COUNTER(trans_level0_seg_write_bytes) \
EXPAND_COUNTER(trans_level0_seg_writes) \
EXPAND_COUNTER(trans_write_item) \
EXPAND_COUNTER(trans_write_deletion_item)
+5 -59
View File
@@ -21,7 +21,7 @@
#include "super.h"
#include "lock.h"
#include "item.h"
#include "forest.h"
#include "scoutfs_trace.h"
#include "msg.h"
#include "cmp.h"
@@ -145,12 +145,10 @@ static void invalidate_inode(struct super_block *sb, u64 ino)
static int lock_invalidate(struct super_block *sb, struct scoutfs_lock *lock,
int prev, int mode)
{
struct scoutfs_key *start = &lock->start;
struct scoutfs_key *end = &lock->end;
struct scoutfs_lock_coverage *cov;
struct scoutfs_lock_coverage *tmp;
u64 ino, last;
int ret;
int ret = 0;
trace_scoutfs_lock_invalidate(sb, lock);
@@ -159,12 +157,12 @@ static int lock_invalidate(struct super_block *sb, struct scoutfs_lock *lock,
mode != SCOUTFS_LOCK_NULL);
/* any transition from a mode allowed to dirty items has to write */
if (lock_mode_can_write(prev)) {
ret = scoutfs_item_writeback(sb, start, end);
if (lock_mode_can_write(prev) && scoutfs_forest_has_dirty(sb)) {
ret = scoutfs_trans_sync(sb, 1);
if (ret < 0)
return ret;
if (ret > 0) {
scoutfs_add_counter(sb, lock_write_dirty_item, ret);
scoutfs_add_counter(sb, lock_invalidate_commit, ret);
ret = 0;
}
}
@@ -195,13 +193,6 @@ retry:
ino++;
}
}
ret = scoutfs_item_invalidate(sb, start, end);
if (ret > 0) {
scoutfs_add_counter(sb, lock_invalidate_clean_item,
ret);
ret = 0;
}
}
return ret;
@@ -548,49 +539,6 @@ static void extend_grace(struct super_block *sb, struct scoutfs_lock *lock)
lock->grace_deadline = ktime_add(now, GRACE_PERIOD_KT);
}
/*
* The given lock is processing a received a grant response. Trigger a
* bug if the cache is inconsistent.
*
* We only have two modes that can create dirty items. We can't have
* dirty items when transitioning from write_only to write because the
* writer can't trust the cached items in the cache for reading. And we
* don't currently transition directly from write to write_only, we
* first go through null. So if we have dirty items as we're granted a
* mode it's always incorrect.
*
* And we can't have cached items that we're going to use for reading if
* the previous mode didn't allow reading.
*
* Inconsistencies have come from all sorts of bugs: invalidation missed
* items, the cache was populated outside of locking coverage, lock
* holders performed the wrong item operations under their lock,
* overlapping locks, out of order granting or invalidating, etc.
*/
static void bug_on_inconsistent_grant_cache(struct super_block *sb,
struct scoutfs_lock *lock,
int old_mode, int new_mode)
{
bool cached = scoutfs_item_range_cached(sb, &lock->start, &lock->end,
false);
bool dirty = scoutfs_item_range_cached(sb, &lock->start, &lock->end,
true);
if (dirty ||
(cached && (!lock_mode_can_read(old_mode) || !lock_mode_can_read(new_mode)))) {
scoutfs_err(sb, "granted lock item cache inconsistency, cached %u dirty %u old_mode %d new_mode %d: start "SK_FMT" end "SK_FMT" refresh_gen %llu mode %u waiters: rd %u wr %u wo %u users: rd %u wr %u wo %u",
cached, dirty, old_mode, new_mode, SK_ARG(&lock->start),
SK_ARG(&lock->end), lock->refresh_gen, lock->mode,
lock->waiters[SCOUTFS_LOCK_READ],
lock->waiters[SCOUTFS_LOCK_WRITE],
lock->waiters[SCOUTFS_LOCK_WRITE_ONLY],
lock->users[SCOUTFS_LOCK_READ],
lock->users[SCOUTFS_LOCK_WRITE],
lock->users[SCOUTFS_LOCK_WRITE_ONLY]);
BUG();
}
}
/*
* The client is receiving a lock response message from the server.
* This can be reordered with incoming invlidation requests from the
@@ -631,8 +579,6 @@ int scoutfs_lock_grant_response(struct super_block *sb,
spin_lock(&linfo->lock);
}
bug_on_inconsistent_grant_cache(sb, lock, nl->old_mode, nl->new_mode);
if (!lock_mode_can_read(nl->old_mode) &&
lock_mode_can_read(nl->new_mode)) {
lock->refresh_gen =
+3 -3
View File
@@ -570,13 +570,13 @@ TRACE_EVENT(scoutfs_sync_fs,
);
TRACE_EVENT(scoutfs_trans_write_func,
TP_PROTO(struct super_block *sb, int dirty),
TP_PROTO(struct super_block *sb, unsigned long dirty),
TP_ARGS(sb, dirty),
TP_STRUCT__entry(
SCSB_TRACE_FIELDS
__field(int, dirty)
__field(unsigned long, dirty)
),
TP_fast_assign(
@@ -584,7 +584,7 @@ TRACE_EVENT(scoutfs_trans_write_func,
__entry->dirty = dirty;
),
TP_printk(SCSBF" dirty %d", SCSB_TRACE_ARGS, __entry->dirty)
TP_printk(SCSBF" dirty %lu", SCSB_TRACE_ARGS, __entry->dirty)
);
TRACE_EVENT(scoutfs_release_trans,
+2 -1
View File
@@ -435,7 +435,8 @@ static int scoutfs_fill_super(struct super_block *sb, void *data, int silent)
scoutfs_server_setup(sb) ?:
scoutfs_client_setup(sb) ?:
scoutfs_lock_rid(sb, SCOUTFS_LOCK_WRITE, 0, sbi->rid,
&sbi->rid_lock);
&sbi->rid_lock) ?:
scoutfs_forest_get_log_trees(sb);
if (ret)
goto out;
+12 -29
View File
@@ -23,6 +23,7 @@
#include "data.h"
#include "bio.h"
#include "item.h"
#include "forest.h"
#include "manifest.h"
#include "seg.h"
#include "counters.h"
@@ -110,44 +111,27 @@ void scoutfs_trans_write_func(struct work_struct *work)
trans_write_work.work);
struct super_block *sb = sbi->sb;
DECLARE_TRANS_INFO(sb, tri);
struct scoutfs_bio_completion comp;
struct scoutfs_segment *seg = NULL;
u64 segno;
int ret = 0;
scoutfs_bio_init_comp(&comp);
sbi->trans_task = current;
wait_event(sbi->trans_hold_wq, drained_holders(tri));
trace_scoutfs_trans_write_func(sb, scoutfs_item_has_dirty(sb));
trace_scoutfs_trans_write_func(sb, scoutfs_forest_dirty_bytes(sb));
if (scoutfs_item_has_dirty(sb)) {
if (scoutfs_forest_has_dirty(sb)) {
if (sbi->trans_deadline_expired)
scoutfs_inc_counter(sb, trans_commit_timer);
/*
* XXX only straight pass through, we're not worrying
* about leaking segnos nor duplicate manifest entries
* on crashes between us and the server.
*/
ret = scoutfs_inode_walk_writeback(sb, true) ?:
scoutfs_client_alloc_segno(sb, &segno) ?:
scoutfs_seg_alloc(sb, segno, &seg) ?:
scoutfs_item_dirty_seg(sb, seg) ?:
scoutfs_seg_submit_write(sb, seg, &comp) ?:
scoutfs_forest_write(sb) ?:
scoutfs_inode_walk_writeback(sb, false) ?:
scoutfs_bio_wait_comp(sb, &comp) ?:
scoutfs_client_record_segment(sb, seg, 0) ?:
scoutfs_client_advance_seq(sb, &sbi->trans_seq);
scoutfs_seg_put(seg);
scoutfs_forest_commit(sb) ?:
scoutfs_client_advance_seq(sb, &sbi->trans_seq) ?:
scoutfs_forest_get_log_trees(sb);
if (ret)
goto out;
scoutfs_inc_counter(sb, trans_level0_seg_writes);
scoutfs_add_counter(sb, trans_level0_seg_write_bytes,
scoutfs_seg_total_bytes(seg));
} else if (sbi->trans_deadline_expired) {
/*
* If we're not writing data then we only advance the
@@ -295,7 +279,6 @@ static bool acquired_hold(struct super_block *sb,
bool acquired = false;
unsigned items;
unsigned vals;
bool fits;
spin_lock(&tri->lock);
@@ -316,8 +299,9 @@ static bool acquired_hold(struct super_block *sb,
/* see if we can reserve space for our item count */
items = tri->reserved_items + cnt->items;
vals = tri->reserved_vals + cnt->vals;
fits = scoutfs_item_dirty_fits_single(sb, items, vals);
if (!fits) {
/* XXX just limit to 256K transactions */
if (scoutfs_forest_dirty_bytes(sb) >= (256 * 1024)) {
scoutfs_inc_counter(sb, trans_commit_full);
queue_trans_work(sbi);
goto out;
@@ -352,8 +336,7 @@ int scoutfs_hold_trans(struct super_block *sb,
* Caller shouldn't provide garbage counts, nor counts that
* can't fit in segments by themselves.
*/
if (WARN_ON_ONCE(cnt.items <= 0 || cnt.vals < 0) ||
WARN_ON_ONCE(!scoutfs_seg_fits_single(cnt.items, cnt.vals)))
if (WARN_ON_ONCE(cnt.items <= 0 || cnt.vals < 0))
return -EINVAL;
if (current == sbi->trans_task)