mirror of
https://github.com/torvalds/linux.git
synced 2024-12-05 02:23:16 +00:00
e53d03fe39
fstest generic/388 occasionally reproduces corruptions where an inode has extents beyond i_size. This is a deliberate crash and recovery test, and the post crash+recovery characteristics are usually the same: the inode exists on disk in an early (i.e. just allocated) state based on the journal sequence number associated with the inode. Subsequent inode updates exist in the journal at higher sequence numbers, but the inode hadn't been written back before the associated crash and the post-crash recovery processes a set of journal sequence numbers that doesn't include updates to the inode. In fact, the sequence with the most recent inode key update always happens to be the sequence just before the front of the journal processed by recovery. This last bit is a significant hint that the problem relates to an on-disk journal update of the front of the journal. The root cause of this problem is basically that the inode is updated (multiple times) in-core and in the key cache, each time bumping the key cache sequence number used to control the cache flush. The cache flush skips one or more times, bumping the associated key cache journal pin to the key cache seq value. This has a side effect of holding the inode in memory a bit longer than normal, which helps exacerbate this problem, but is also unsafe in certain cases where the key cache seq may have been updated by a transaction commit that didn't journal the associated key. For example, consider an inode that has been allocated, updated several times in the key cache, journaled, but not yet written back. At this stage, everything should be consistent if the fs happens to crash because the latest update has been journal. Now consider a key update via bch2_extent_update_i_size_sectors() that uses the BTREE_UPDATE_NOJOURNAL flag. While this update may not change inode state, it can have the side effect of bumping ck->seq in bch2_btree_insert_key_cached(). In turn, if a subsequent key cache flush skips due to seq not matching the former, the ck->journal pin is updated to ck->seq even though the most recent key update was not journaled. If this pin happens to reside at the front (tail) of the journal, this means a subsequent journal write can update last_seq to a value beyond that which includes the most recent update to the inode. If this occurs and the fs happens to crash before the inode happens to flush, recovery will see the latest last_seq, fail to recover the inode and leave the inode in the inconsistent state described above. To avoid this problem, skip the key cache seq update on NOJOURNAL commits, except on initial pin add. Pass the insert entry directly to bch2_btree_insert_key_cached() to make the associated flag available and be consistent with btree_insert_key_leaf(). Signed-off-by: Brian Foster <bfoster@redhat.com> Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
49 lines
1.6 KiB
C
49 lines
1.6 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _BCACHEFS_BTREE_KEY_CACHE_H
|
|
#define _BCACHEFS_BTREE_KEY_CACHE_H
|
|
|
|
static inline size_t bch2_nr_btree_keys_need_flush(struct bch_fs *c)
|
|
{
|
|
size_t nr_dirty = atomic_long_read(&c->btree_key_cache.nr_dirty);
|
|
size_t nr_keys = atomic_long_read(&c->btree_key_cache.nr_keys);
|
|
size_t max_dirty = 1024 + nr_keys / 2;
|
|
|
|
return max_t(ssize_t, 0, nr_dirty - max_dirty);
|
|
}
|
|
|
|
static inline bool bch2_btree_key_cache_must_wait(struct bch_fs *c)
|
|
{
|
|
size_t nr_dirty = atomic_long_read(&c->btree_key_cache.nr_dirty);
|
|
size_t nr_keys = atomic_long_read(&c->btree_key_cache.nr_keys);
|
|
size_t max_dirty = 4096 + (nr_keys * 3) / 4;
|
|
|
|
return nr_dirty > max_dirty;
|
|
}
|
|
|
|
int bch2_btree_key_cache_journal_flush(struct journal *,
|
|
struct journal_entry_pin *, u64);
|
|
|
|
struct bkey_cached *
|
|
bch2_btree_key_cache_find(struct bch_fs *, enum btree_id, struct bpos);
|
|
|
|
int bch2_btree_path_traverse_cached(struct btree_trans *, struct btree_path *,
|
|
unsigned);
|
|
|
|
bool bch2_btree_insert_key_cached(struct btree_trans *, unsigned,
|
|
struct btree_insert_entry *);
|
|
int bch2_btree_key_cache_flush(struct btree_trans *,
|
|
enum btree_id, struct bpos);
|
|
void bch2_btree_key_cache_drop(struct btree_trans *,
|
|
struct btree_path *);
|
|
|
|
void bch2_fs_btree_key_cache_exit(struct btree_key_cache *);
|
|
void bch2_fs_btree_key_cache_init_early(struct btree_key_cache *);
|
|
int bch2_fs_btree_key_cache_init(struct btree_key_cache *);
|
|
|
|
void bch2_btree_key_cache_to_text(struct printbuf *, struct btree_key_cache *);
|
|
|
|
void bch2_btree_key_cache_exit(void);
|
|
int __init bch2_btree_key_cache_init(void);
|
|
|
|
#endif /* _BCACHEFS_BTREE_KEY_CACHE_H */
|