Files
scoutfs/kmod/src/triggers.c
T
Auke Kok b724567b2a Add log_merge_force_partial trigger for testing partial merges.
Add a trigger that forces btree_merge() to return -ERANGE after
modifying a leaf's worth of items, causing many small partial merges
per merge cycle. This is used by tests to reliably reproduce races
that depend on partial merges splicing items into fs_root while
finalized logs still exist.

The trigger check lives inside btree_merge() where it can observe
actual item modification progress, rather than overriding the
caller's dirty byte limit argument which applies to the whole
writer context.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-10 12:25:30 -07:00

129 lines
3.3 KiB
C

/*
* Copyright (C) 2017 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/slab.h>
#include <linux/fs.h>
#include <linux/atomic.h>
#include <linux/debugfs.h>
#include "super.h"
#include "triggers.h"
#include "scoutfs_trace.h"
/*
* We have debugfs files we can write to which arm triggers which
* atomically fire once for testing or debugging.
*/
/*
* The atomic cachelines are kept hot and shared by being read by fast
* paths. They're very rarely modified by debugfs writes which arm them
* and then the next read will atomically clear and return true.
*/
struct scoutfs_triggers {
struct dentry *dir;
atomic_t atomics[SCOUTFS_TRIGGER_NR];
};
#define DECLARE_TRIGGERS(sb, name) \
struct scoutfs_triggers *name = SCOUTFS_SB(sb)->triggers
static char *names[] = {
[SCOUTFS_TRIGGER_BLOCK_REMOVE_STALE] = "block_remove_stale",
[SCOUTFS_TRIGGER_LOG_MERGE_FORCE_FINALIZE_OURS] = "log_merge_force_finalize_ours",
[SCOUTFS_TRIGGER_SRCH_COMPACT_LOGS_PAD_SAFE] = "srch_compact_logs_pad_safe",
[SCOUTFS_TRIGGER_SRCH_FORCE_LOG_ROTATE] = "srch_force_log_rotate",
[SCOUTFS_TRIGGER_SRCH_MERGE_STOP_SAFE] = "srch_merge_stop_safe",
[SCOUTFS_TRIGGER_STATFS_LOCK_PURGE] = "statfs_lock_purge",
[SCOUTFS_TRIGGER_RECLAIM_SKIP_FINALIZE] = "reclaim_skip_finalize",
[SCOUTFS_TRIGGER_LOG_MERGE_FORCE_PARTIAL] = "log_merge_force_partial",
};
bool scoutfs_trigger_test_and_clear(struct super_block *sb, unsigned int t)
{
DECLARE_TRIGGERS(sb, triggers);
atomic_t *atom;
int old;
int mem;
bool fired;
BUG_ON(t >= SCOUTFS_TRIGGER_NR);
atom = &triggers->atomics[t];
mem = atomic_read(atom);
if (likely(!mem))
return 0;
do {
old = mem;
mem = atomic_cmpxchg(atom, old, 0);
} while (mem && mem != old);
fired = !!mem;
if (fired)
trace_scoutfs_trigger_fired(sb, names[t]);
return fired;
}
int scoutfs_setup_triggers(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
struct scoutfs_triggers *triggers;
int ret;
int i;
BUILD_BUG_ON(ARRAY_SIZE(names) != SCOUTFS_TRIGGER_NR);
for (i = 0; i < ARRAY_SIZE(names); i++) {
if (WARN_ON(!names[i]))
return -EINVAL;
}
triggers = kzalloc(sizeof(struct scoutfs_triggers), GFP_KERNEL);
if (!triggers)
return -ENOMEM;
sbi->triggers = triggers;
triggers->dir = debugfs_create_dir("trigger", sbi->debug_root);
if (!triggers->dir) {
ret = -ENOMEM;
goto out;
}
for (i = 0; i < ARRAY_SIZE(triggers->atomics); i++)
debugfs_create_atomic_t(names[i], 0644, triggers->dir,
&triggers->atomics[i]);
ret = 0;
out:
if (ret)
scoutfs_destroy_triggers(sb);
return ret;
}
void scoutfs_destroy_triggers(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
struct scoutfs_triggers *triggers = sbi->triggers;
if (triggers) {
if (triggers->dir)
debugfs_remove_recursive(triggers->dir);
kfree(triggers);
sbi->triggers = NULL;
}
}