Commit e8513c01 authored by Leo Martins's avatar Leo Martins Committed by David Sterba
Browse files

btrfs: implement ref_tracker for delayed_nodes



Add ref_tracker infrastructure for struct btrfs_delayed_node.

It is a response to the largest btrfs related crash in our fleet.  We're
seeing soft lockups in btrfs_kill_all_delayed_nodes() that seem to be a
result of delayed_nodes not being released properly.

A ref_tracker object is allocated on reference count increases and freed
on reference count decreases. The ref_tracker object stores a stack
trace of where it is allocated. The ref_tracker_dir object is embedded
in btrfs_delayed_node and keeps track of all current and some old/freed
ref_tracker objects. When a leak is detected we can print the stack
traces for all ref_trackers that have not yet been freed.

Here is a common example of taking a reference to a delayed_node and
freeing it with ref_tracker.

    struct btrfs_ref_tracker tracker;
    struct btrfs_delayed_node *node;

    node = btrfs_get_delayed_node(inode, &tracker);
    // use delayed_node...
    btrfs_release_delayed_node(node, &tracker);

There are two special cases where the delayed_node reference is "long
lived", meaning that the thread that takes the reference and the thread
that releases the reference are different. The 'inode_cache_tracker'
tracks the delayed_node stored in btrfs_inode. The 'node_list_tracker'
tracks the delayed_node stored in the btrfs_delayed_root
node_list/prepare_list. These trackers are embedded in the
btrfs_delayed_node.

btrfs_ref_tracker and btrfs_ref_tracker_dir are wrappers that either
compile to the corresponding ref_tracker structs or empty structs
depending on CONFIG_BTRFS_DEBUG. There are also btrfs wrappers for
the ref_tracker API.

Signed-off-by: default avatarLeo Martins <loemra.dev@gmail.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent 67e78f98
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ config BTRFS_FS_RUN_SANITY_TESTS
config BTRFS_DEBUG
	bool "Btrfs debugging support"
	depends on BTRFS_FS
	select REF_TRACKER if STACKTRACE_SUPPORT
	help
	  Enable run-time debugging support for the btrfs filesystem.

+120 −55

File changed.

Preview size limit exceeded, changes collapsed.

+70 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/fs.h>
#include <linux/atomic.h>
#include <linux/refcount.h>
#include <linux/ref_tracker.h>
#include "ctree.h"

struct btrfs_disk_key;
@@ -44,6 +45,22 @@ struct btrfs_delayed_root {
	wait_queue_head_t wait;
};

struct btrfs_ref_tracker_dir {
#ifdef CONFIG_BTRFS_DEBUG
	struct ref_tracker_dir dir;
#else
	struct {} tracker;
#endif
};

struct btrfs_ref_tracker {
#ifdef CONFIG_BTRFS_DEBUG
	struct ref_tracker *tracker;
#else
	struct {} tracker;
#endif
};

#define BTRFS_DELAYED_NODE_IN_LIST	0
#define BTRFS_DELAYED_NODE_INODE_DIRTY	1
#define BTRFS_DELAYED_NODE_DEL_IREF	2
@@ -78,6 +95,12 @@ struct btrfs_delayed_node {
	 * actual number of leaves we end up using. Protected by @mutex.
	 */
	u32 index_item_leaves;
	/* Track all references to this delayed node. */
	struct btrfs_ref_tracker_dir ref_dir;
	/* Track delayed node reference stored in node list. */
	struct btrfs_ref_tracker node_list_tracker;
	/* Track delayed node reference stored in inode cache. */
	struct btrfs_ref_tracker inode_cache_tracker;
};

struct btrfs_delayed_item {
@@ -169,4 +192,51 @@ void __cold btrfs_delayed_inode_exit(void);
/* for debugging */
void btrfs_assert_delayed_root_empty(struct btrfs_fs_info *fs_info);

#define BTRFS_DELAYED_NODE_REF_TRACKER_QUARANTINE_COUNT		16
#define BTRFS_DELAYED_NODE_REF_TRACKER_DISPLAY_LIMIT		16

#ifdef CONFIG_BTRFS_DEBUG
static inline void btrfs_delayed_node_ref_tracker_dir_init(struct btrfs_delayed_node *node)
{
	ref_tracker_dir_init(&node->ref_dir.dir,
			     BTRFS_DELAYED_NODE_REF_TRACKER_QUARANTINE_COUNT,
			     "delayed_node");
}

static inline void btrfs_delayed_node_ref_tracker_dir_exit(struct btrfs_delayed_node *node)
{
	ref_tracker_dir_exit(&node->ref_dir.dir);
}

static inline int btrfs_delayed_node_ref_tracker_alloc(struct btrfs_delayed_node *node,
						       struct btrfs_ref_tracker *tracker,
						       gfp_t gfp)
{
	return ref_tracker_alloc(&node->ref_dir.dir, &tracker->tracker, gfp);
}

static inline int btrfs_delayed_node_ref_tracker_free(struct btrfs_delayed_node *node,
						      struct btrfs_ref_tracker *tracker)
{
	return ref_tracker_free(&node->ref_dir.dir, &tracker->tracker);
}
#else
static inline void btrfs_delayed_node_ref_tracker_dir_init(struct btrfs_delayed_node *node) { }

static inline void btrfs_delayed_node_ref_tracker_dir_exit(struct btrfs_delayed_node *node) { }

static inline int btrfs_delayed_node_ref_tracker_alloc(struct btrfs_delayed_node *node,
						       struct btrfs_ref_tracker *tracker,
						       gfp_t gfp)
{
	return 0;
}

static inline int btrfs_delayed_node_ref_tracker_free(struct btrfs_delayed_node *node,
						      struct btrfs_ref_tracker *tracker)
{
	return 0;
}
#endif

#endif