mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-27 21:14:44 +08:00
32feee36c3
The journal no-space deadlock was reported time to time. Such deadlock can happen in the following situation. When all journal buckets are fully filled by active jset with heavy write I/O load, the cache set registration (after a reboot) will load all active jsets and inserting them into the btree again (which is called journal replay). If a journaled bkey is inserted into a btree node and results btree node split, new journal request might be triggered. For example, the btree grows one more level after the node split, then the root node record in cache device super block will be upgrade by bch_journal_meta() from bch_btree_set_root(). But there is no space in journal buckets, the journal replay has to wait for new journal bucket to be reclaimed after at least one journal bucket replayed. This is one example that how the journal no-space deadlock happens. The solution to avoid the deadlock is to reserve 1 journal bucket in run time, and only permit the reserved journal bucket to be used during cache set registration procedure for things like journal replay. Then the journal space will never be fully filled, there is no chance for journal no-space deadlock to happen anymore. This patch adds a new member "bool do_reserve" in struct journal, it is inititalized to 0 (false) when struct journal is allocated, and set to 1 (true) by bch_journal_space_reserve() when all initialization done in run_cache_set(). In the run time when journal_reclaim() tries to allocate a new journal bucket, free_journal_buckets() is called to check whether there are enough free journal buckets to use. If there is only 1 free journal bucket and journal->do_reserve is 1 (true), the last bucket is reserved and free_journal_buckets() will return 0 to indicate no free journal bucket. Then journal_reclaim() will give up, and try next time to see whetheer there is free journal bucket to allocate. By this method, there is always 1 jouranl bucket reserved in run time. During the cache set registration, journal->do_reserve is 0 (false), so the reserved journal bucket can be used to avoid the no-space deadlock. Reported-by: Nikhil Kshirsagar <nkshirsagar@gmail.com> Signed-off-by: Coly Li <colyli@suse.de> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20220524102336.10684-5-colyli@suse.de Signed-off-by: Jens Axboe <axboe@kernel.dk>
189 lines
6.5 KiB
C
189 lines
6.5 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _BCACHE_JOURNAL_H
|
|
#define _BCACHE_JOURNAL_H
|
|
|
|
/*
|
|
* THE JOURNAL:
|
|
*
|
|
* The journal is treated as a circular buffer of buckets - a journal entry
|
|
* never spans two buckets. This means (not implemented yet) we can resize the
|
|
* journal at runtime, and will be needed for bcache on raw flash support.
|
|
*
|
|
* Journal entries contain a list of keys, ordered by the time they were
|
|
* inserted; thus journal replay just has to reinsert the keys.
|
|
*
|
|
* We also keep some things in the journal header that are logically part of the
|
|
* superblock - all the things that are frequently updated. This is for future
|
|
* bcache on raw flash support; the superblock (which will become another
|
|
* journal) can't be moved or wear leveled, so it contains just enough
|
|
* information to find the main journal, and the superblock only has to be
|
|
* rewritten when we want to move/wear level the main journal.
|
|
*
|
|
* Currently, we don't journal BTREE_REPLACE operations - this will hopefully be
|
|
* fixed eventually. This isn't a bug - BTREE_REPLACE is used for insertions
|
|
* from cache misses, which don't have to be journaled, and for writeback and
|
|
* moving gc we work around it by flushing the btree to disk before updating the
|
|
* gc information. But it is a potential issue with incremental garbage
|
|
* collection, and it's fragile.
|
|
*
|
|
* OPEN JOURNAL ENTRIES:
|
|
*
|
|
* Each journal entry contains, in the header, the sequence number of the last
|
|
* journal entry still open - i.e. that has keys that haven't been flushed to
|
|
* disk in the btree.
|
|
*
|
|
* We track this by maintaining a refcount for every open journal entry, in a
|
|
* fifo; each entry in the fifo corresponds to a particular journal
|
|
* entry/sequence number. When the refcount at the tail of the fifo goes to
|
|
* zero, we pop it off - thus, the size of the fifo tells us the number of open
|
|
* journal entries
|
|
*
|
|
* We take a refcount on a journal entry when we add some keys to a journal
|
|
* entry that we're going to insert (held by struct btree_op), and then when we
|
|
* insert those keys into the btree the btree write we're setting up takes a
|
|
* copy of that refcount (held by struct btree_write). That refcount is dropped
|
|
* when the btree write completes.
|
|
*
|
|
* A struct btree_write can only hold a refcount on a single journal entry, but
|
|
* might contain keys for many journal entries - we handle this by making sure
|
|
* it always has a refcount on the _oldest_ journal entry of all the journal
|
|
* entries it has keys for.
|
|
*
|
|
* JOURNAL RECLAIM:
|
|
*
|
|
* As mentioned previously, our fifo of refcounts tells us the number of open
|
|
* journal entries; from that and the current journal sequence number we compute
|
|
* last_seq - the oldest journal entry we still need. We write last_seq in each
|
|
* journal entry, and we also have to keep track of where it exists on disk so
|
|
* we don't overwrite it when we loop around the journal.
|
|
*
|
|
* To do that we track, for each journal bucket, the sequence number of the
|
|
* newest journal entry it contains - if we don't need that journal entry we
|
|
* don't need anything in that bucket anymore. From that we track the last
|
|
* journal bucket we still need; all this is tracked in struct journal_device
|
|
* and updated by journal_reclaim().
|
|
*
|
|
* JOURNAL FILLING UP:
|
|
*
|
|
* There are two ways the journal could fill up; either we could run out of
|
|
* space to write to, or we could have too many open journal entries and run out
|
|
* of room in the fifo of refcounts. Since those refcounts are decremented
|
|
* without any locking we can't safely resize that fifo, so we handle it the
|
|
* same way.
|
|
*
|
|
* If the journal fills up, we start flushing dirty btree nodes until we can
|
|
* allocate space for a journal write again - preferentially flushing btree
|
|
* nodes that are pinning the oldest journal entries first.
|
|
*/
|
|
|
|
/*
|
|
* Only used for holding the journal entries we read in btree_journal_read()
|
|
* during cache_registration
|
|
*/
|
|
struct journal_replay {
|
|
struct list_head list;
|
|
atomic_t *pin;
|
|
struct jset j;
|
|
};
|
|
|
|
/*
|
|
* We put two of these in struct journal; we used them for writes to the
|
|
* journal that are being staged or in flight.
|
|
*/
|
|
struct journal_write {
|
|
struct jset *data;
|
|
#define JSET_BITS 3
|
|
|
|
struct cache_set *c;
|
|
struct closure_waitlist wait;
|
|
bool dirty;
|
|
bool need_write;
|
|
};
|
|
|
|
/* Embedded in struct cache_set */
|
|
struct journal {
|
|
spinlock_t lock;
|
|
spinlock_t flush_write_lock;
|
|
bool btree_flushing;
|
|
bool do_reserve;
|
|
/* used when waiting because the journal was full */
|
|
struct closure_waitlist wait;
|
|
struct closure io;
|
|
int io_in_flight;
|
|
struct delayed_work work;
|
|
|
|
/* Number of blocks free in the bucket(s) we're currently writing to */
|
|
unsigned int blocks_free;
|
|
uint64_t seq;
|
|
DECLARE_FIFO(atomic_t, pin);
|
|
|
|
BKEY_PADDED(key);
|
|
|
|
struct journal_write w[2], *cur;
|
|
};
|
|
|
|
/*
|
|
* Embedded in struct cache. First three fields refer to the array of journal
|
|
* buckets, in cache_sb.
|
|
*/
|
|
struct journal_device {
|
|
/*
|
|
* For each journal bucket, contains the max sequence number of the
|
|
* journal writes it contains - so we know when a bucket can be reused.
|
|
*/
|
|
uint64_t seq[SB_JOURNAL_BUCKETS];
|
|
|
|
/* Journal bucket we're currently writing to */
|
|
unsigned int cur_idx;
|
|
|
|
/* Last journal bucket that still contains an open journal entry */
|
|
unsigned int last_idx;
|
|
|
|
/* Next journal bucket to be discarded */
|
|
unsigned int discard_idx;
|
|
|
|
#define DISCARD_READY 0
|
|
#define DISCARD_IN_FLIGHT 1
|
|
#define DISCARD_DONE 2
|
|
/* 1 - discard in flight, -1 - discard completed */
|
|
atomic_t discard_in_flight;
|
|
|
|
struct work_struct discard_work;
|
|
struct bio discard_bio;
|
|
struct bio_vec discard_bv;
|
|
|
|
/* Bio for journal reads/writes to this device */
|
|
struct bio bio;
|
|
struct bio_vec bv[8];
|
|
};
|
|
|
|
#define BTREE_FLUSH_NR 8
|
|
|
|
#define journal_pin_cmp(c, l, r) \
|
|
(fifo_idx(&(c)->journal.pin, (l)) > fifo_idx(&(c)->journal.pin, (r)))
|
|
|
|
#define JOURNAL_PIN 20000
|
|
|
|
#define journal_full(j) \
|
|
(!(j)->blocks_free || fifo_free(&(j)->pin) <= 1)
|
|
|
|
struct closure;
|
|
struct cache_set;
|
|
struct btree_op;
|
|
struct keylist;
|
|
|
|
atomic_t *bch_journal(struct cache_set *c,
|
|
struct keylist *keys,
|
|
struct closure *parent);
|
|
void bch_journal_next(struct journal *j);
|
|
void bch_journal_mark(struct cache_set *c, struct list_head *list);
|
|
void bch_journal_meta(struct cache_set *c, struct closure *cl);
|
|
int bch_journal_read(struct cache_set *c, struct list_head *list);
|
|
int bch_journal_replay(struct cache_set *c, struct list_head *list);
|
|
|
|
void bch_journal_free(struct cache_set *c);
|
|
int bch_journal_alloc(struct cache_set *c);
|
|
void bch_journal_space_reserve(struct journal *j);
|
|
|
|
#endif /* _BCACHE_JOURNAL_H */
|