X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libbcachefs%2Fjournal_io.c;h=45b1b839783de66f712aaac1b337cf522e365d84;hb=807b250927d9c6e829ddd1ff7eac57bf9b056dab;hp=68113a08f1b9c4a42cab61b57277c9fb4bafe537;hpb=e0a51ccce8533a91c7cc0cd0adc5662697c9bcfa;p=bcachefs-tools-debian diff --git a/libbcachefs/journal_io.c b/libbcachefs/journal_io.c index 68113a0..45b1b83 100644 --- a/libbcachefs/journal_io.c +++ b/libbcachefs/journal_io.c @@ -119,7 +119,7 @@ static int journal_entry_add(struct bch_fs *c, struct bch_dev *ca, journal_entry_radix_idx(c, le64_to_cpu(j->seq)), GFP_KERNEL); if (!_i) - return -ENOMEM; + return -BCH_ERR_ENOMEM_journal_entry_add; /* * Duplicate journal entries? If so we want the one that didn't have a @@ -149,12 +149,12 @@ static int journal_entry_add(struct bch_fs *c, struct bch_dev *ca, replace: i = kvpmalloc(offsetof(struct journal_replay, j) + bytes, GFP_KERNEL); if (!i) - return -ENOMEM; + return -BCH_ERR_ENOMEM_journal_entry_add; i->nr_ptrs = 0; i->csum_good = entry_ptr.csum_good; i->ignore = false; - memcpy(&i->j, j, bytes); + unsafe_memcpy(&i->j, j, bytes, "embedded variable length struct"); i->ptrs[i->nr_ptrs++] = entry_ptr; if (dup) { @@ -341,7 +341,7 @@ static int journal_entry_btree_keys_validate(struct bch_fs *c, int ret = journal_validate_key(c, jset, entry, entry->level, entry->btree_id, - k, version, big_endian, write); + k, version, big_endian, write|BKEY_INVALID_FROM_JOURNAL); if (ret == FSCK_DELETED_KEY) continue; @@ -357,7 +357,7 @@ static void journal_entry_btree_keys_to_text(struct printbuf *out, struct bch_fs struct bkey_i *k; bool first = true; - vstruct_for_each(entry, k) { + jset_entry_for_each_key(entry, k) { if (!first) { prt_newline(out); prt_printf(out, "%s: ", bch2_jset_entry_types[entry->type]); @@ -662,7 +662,8 @@ static int journal_entry_overwrite_validate(struct bch_fs *c, struct jset_entry *entry, unsigned version, int big_endian, int write) { - return journal_entry_btree_keys_validate(c, jset, entry, version, big_endian, write); + return journal_entry_btree_keys_validate(c, jset, entry, + version, big_endian, READ); } static void journal_entry_overwrite_to_text(struct printbuf *out, struct bch_fs *c, @@ -835,12 +836,12 @@ static int journal_read_buf_realloc(struct journal_read_buf *b, /* the bios are sized for this many pages, max: */ if (new_size > JOURNAL_ENTRY_SIZE_MAX) - return -ENOMEM; + return -BCH_ERR_ENOMEM_journal_read_buf_realloc; new_size = roundup_pow_of_two(new_size); n = kvpmalloc(new_size, GFP_KERNEL); if (!n) - return -ENOMEM; + return -BCH_ERR_ENOMEM_journal_read_buf_realloc; kvpfree(b->data, b->size); b->data = n; @@ -987,7 +988,6 @@ static void bch2_journal_read_device(struct closure *cl) struct journal_replay *r, **_r; struct genradix_iter iter; struct journal_read_buf buf = { NULL, 0 }; - u64 min_seq = U64_MAX; unsigned i; int ret = 0; @@ -1006,45 +1006,27 @@ static void bch2_journal_read_device(struct closure *cl) goto err; } - /* Find the journal bucket with the highest sequence number: */ - for (i = 0; i < ja->nr; i++) { - if (ja->bucket_seq[i] > ja->bucket_seq[ja->cur_idx]) - ja->cur_idx = i; - - min_seq = min(ja->bucket_seq[i], min_seq); - } - - /* - * If there's duplicate journal entries in multiple buckets (which - * definitely isn't supposed to happen, but...) - make sure to start - * cur_idx at the last of those buckets, so we don't deadlock trying to - * allocate - */ - while (ja->bucket_seq[ja->cur_idx] > min_seq && - ja->bucket_seq[ja->cur_idx] == - ja->bucket_seq[(ja->cur_idx + 1) % ja->nr]) - ja->cur_idx = (ja->cur_idx + 1) % ja->nr; - ja->sectors_free = ca->mi.bucket_size; mutex_lock(&jlist->lock); - genradix_for_each(&c->journal_entries, iter, _r) { + genradix_for_each_reverse(&c->journal_entries, iter, _r) { r = *_r; if (!r) continue; for (i = 0; i < r->nr_ptrs; i++) { - if (r->ptrs[i].dev == ca->dev_idx && - sector_to_bucket(ca, r->ptrs[i].sector) == ja->buckets[ja->cur_idx]) { + if (r->ptrs[i].dev == ca->dev_idx) { unsigned wrote = bucket_remainder(ca, r->ptrs[i].sector) + vstruct_sectors(&r->j, c->block_bits); - ja->sectors_free = min(ja->sectors_free, - ca->mi.bucket_size - wrote); + ja->cur_idx = r->ptrs[i].bucket; + ja->sectors_free = ca->mi.bucket_size - wrote; + goto found; } } } +found: mutex_unlock(&jlist->lock); if (ja->bucket_seq[ja->cur_idx] && @@ -1099,7 +1081,10 @@ void bch2_journal_ptrs_to_text(struct printbuf *out, struct bch_fs *c, } } -int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) +int bch2_journal_read(struct bch_fs *c, + u64 *last_seq, + u64 *blacklist_seq, + u64 *start_seq) { struct journal_list jlist; struct journal_replay *i, **_i, *prev = NULL; @@ -1107,9 +1092,8 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) struct bch_dev *ca; unsigned iter; struct printbuf buf = PRINTBUF; - size_t keys = 0, entries = 0; - bool degraded = false; - u64 seq, last_seq = 0; + bool degraded = false, last_write_torn = false; + u64 seq; int ret = 0; closure_init_stack(&jlist.cl); @@ -1138,41 +1122,46 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) if (jlist.ret) return jlist.ret; - *start_seq = 0; + *last_seq = 0; + *start_seq = 0; + *blacklist_seq = 0; /* * Find most recent flush entry, and ignore newer non flush entries - * those entries will be blacklisted: */ genradix_for_each_reverse(&c->journal_entries, radix_iter, _i) { + int write = READ; + i = *_i; if (!i || i->ignore) continue; if (!*start_seq) - *start_seq = le64_to_cpu(i->j.seq) + 1; - - if (!JSET_NO_FLUSH(&i->j)) { - int write = READ; - if (journal_entry_err_on(le64_to_cpu(i->j.last_seq) > le64_to_cpu(i->j.seq), - c, &i->j, NULL, - "invalid journal entry: last_seq > seq (%llu > %llu)", - le64_to_cpu(i->j.last_seq), - le64_to_cpu(i->j.seq))) - i->j.last_seq = i->j.seq; - - pr_info("last flush %llu-%llu csum good %u", - le64_to_cpu(i->j.last_seq), - le64_to_cpu(i->j.seq), - i->csum_good); - - last_seq = le64_to_cpu(i->j.last_seq); - *blacklist_seq = le64_to_cpu(i->j.seq) + 1; - break; + *blacklist_seq = *start_seq = le64_to_cpu(i->j.seq) + 1; + + if (JSET_NO_FLUSH(&i->j)) { + i->ignore = true; + continue; + } + + if (!last_write_torn && !i->csum_good) { + last_write_torn = true; + i->ignore = true; + continue; } - journal_replay_free(c, i); + if (journal_entry_err_on(le64_to_cpu(i->j.last_seq) > le64_to_cpu(i->j.seq), + c, &i->j, NULL, + "invalid journal entry: last_seq > seq (%llu > %llu)", + le64_to_cpu(i->j.last_seq), + le64_to_cpu(i->j.seq))) + i->j.last_seq = i->j.seq; + + *last_seq = le64_to_cpu(i->j.last_seq); + *blacklist_seq = le64_to_cpu(i->j.seq) + 1; + break; } if (!*start_seq) { @@ -1180,12 +1169,18 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) return 0; } - if (!last_seq) { + if (!*last_seq) { fsck_err(c, "journal read done, but no entries found after dropping non-flushes"); - ret = -1; - goto err; + return 0; } + bch_info(c, "journal read done, replaying entries %llu-%llu", + *last_seq, *blacklist_seq - 1); + + if (*start_seq != *blacklist_seq) + bch_info(c, "dropped unflushed entries %llu-%llu", + *blacklist_seq, *start_seq - 1); + /* Drop blacklisted entries and entries older than last_seq: */ genradix_for_each(&c->journal_entries, radix_iter, _i) { i = *_i; @@ -1194,7 +1189,7 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) continue; seq = le64_to_cpu(i->j.seq); - if (seq < last_seq) { + if (seq < *last_seq) { journal_replay_free(c, i); continue; } @@ -1202,13 +1197,12 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) if (bch2_journal_seq_is_blacklisted(c, seq, true)) { fsck_err_on(!JSET_NO_FLUSH(&i->j), c, "found blacklisted journal entry %llu", seq); - - journal_replay_free(c, i); + i->ignore = true; } } /* Check for missing entries: */ - seq = last_seq; + seq = *last_seq; genradix_for_each(&c->journal_entries, radix_iter, _i) { i = *_i; @@ -1246,7 +1240,7 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) " prev at %s\n" " next at %s", missing_start, missing_end, - last_seq, *blacklist_seq - 1, + *last_seq, *blacklist_seq - 1, buf1.buf, buf2.buf); printbuf_exit(&buf1); @@ -1258,8 +1252,6 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) } genradix_for_each(&c->journal_entries, radix_iter, _i) { - struct jset_entry *entry; - struct bkey_i *k, *_n; struct bch_replicas_padded replicas = { .e.data_type = BCH_DATA_journal, .e.nr_required = 1, @@ -1274,10 +1266,10 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) struct bch_dev *ca = bch_dev_bkey_exists(c, i->ptrs[ptr].dev); if (!i->ptrs[ptr].csum_good) - printk(KERN_ERR "bcachefs (%s) sector %llu: invalid journal checksum, seq %llu%s\n", - ca->name, i->ptrs[ptr].sector, - le64_to_cpu(i->j.seq), - i->csum_good ? " (had good copy on another device)" : ""); + bch_err_dev_offset(ca, i->ptrs[ptr].sector, + "invalid journal checksum, seq %llu%s", + le64_to_cpu(i->j.seq), + i->csum_good ? " (had good copy on another device)" : ""); } ret = jset_validate(c, @@ -1309,18 +1301,7 @@ int bch2_journal_read(struct bch_fs *c, u64 *blacklist_seq, u64 *start_seq) if (ret) goto err; } - - for_each_jset_key(k, _n, entry, &i->j) - keys++; - entries++; } - - bch_info(c, "journal read done, %zu keys in %zu entries, seq %llu", - keys, entries, *start_seq); - - if (*start_seq != *blacklist_seq) - bch_info(c, "dropped unflushed entries %llu-%llu", - *blacklist_seq, *start_seq - 1); err: fsck_err: printbuf_exit(&buf); @@ -1358,8 +1339,7 @@ static void __journal_write_alloc(struct journal *j, if (!ca->mi.durability || ca->mi.state != BCH_MEMBER_STATE_rw || !ja->nr || - bch2_bkey_has_device(bkey_i_to_s_c(&w->key), - ca->dev_idx) || + bch2_bkey_has_device_c(bkey_i_to_s_c(&w->key), ca->dev_idx) || sectors > ja->sectors_free) continue; @@ -1518,6 +1498,8 @@ static void journal_write_done(struct closure *cl) bch2_do_discards(c); closure_wake_up(&c->freelist_wait); + + bch2_reset_alloc_cursors(c); } } else if (!j->err_seq || seq < j->err_seq) j->err_seq = seq; @@ -1639,6 +1621,52 @@ static void do_journal_write(struct closure *cl) return; } +static void bch2_journal_entries_postprocess(struct bch_fs *c, struct jset *jset) +{ + struct jset_entry *i, *next, *prev = NULL; + + /* + * Simple compaction, dropping empty jset_entries (from journal + * reservations that weren't fully used) and merging jset_entries that + * can be. + * + * If we wanted to be really fancy here, we could sort all the keys in + * the jset and drop keys that were overwritten - probably not worth it: + */ + vstruct_for_each_safe(jset, i, next) { + unsigned u64s = le16_to_cpu(i->u64s); + + /* Empty entry: */ + if (!u64s) + continue; + + if (i->type == BCH_JSET_ENTRY_btree_root) + bch2_journal_entry_to_btree_root(c, i); + + /* Can we merge with previous entry? */ + if (prev && + i->btree_id == prev->btree_id && + i->level == prev->level && + i->type == prev->type && + i->type == BCH_JSET_ENTRY_btree_keys && + le16_to_cpu(prev->u64s) + u64s <= U16_MAX) { + memmove_u64s_down(vstruct_next(prev), + i->_data, + u64s); + le16_add_cpu(&prev->u64s, u64s); + continue; + } + + /* Couldn't merge, move i into new position (after prev): */ + prev = prev ? vstruct_next(prev) : jset->start; + if (i != prev) + memmove_u64s_down(prev, i, jset_u64s(u64s)); + } + + prev = prev ? vstruct_next(prev) : jset->start; + jset->u64s = cpu_to_le32((u64 *) prev - jset->_data); +} + void bch2_journal_write(struct closure *cl) { struct journal *j = container_of(cl, struct journal, io); @@ -1661,20 +1689,42 @@ void bch2_journal_write(struct closure *cl) j->write_start_time = local_clock(); spin_lock(&j->lock); - if (bch2_journal_error(j) || - w->noflush || - (!w->must_flush && - (jiffies - j->last_flush_write) < msecs_to_jiffies(c->opts.journal_flush_delay) && - test_bit(JOURNAL_MAY_SKIP_FLUSH, &j->flags))) { + + /* + * If the journal is in an error state - we did an emergency shutdown - + * we prefer to continue doing journal writes. We just mark them as + * noflush so they'll never be used, but they'll still be visible by the + * list_journal tool - this helps in debugging. + * + * There's a caveat: the first journal write after marking the + * superblock dirty must always be a flush write, because on startup + * from a clean shutdown we didn't necessarily read the journal and the + * new journal write might overwrite whatever was in the journal + * previously - we can't leave the journal without any flush writes in + * it. + * + * So if we're in an error state, and we're still starting up, we don't + * write anything at all. + */ + if (!test_bit(JOURNAL_NEED_FLUSH_WRITE, &j->flags) && + (bch2_journal_error(j) || + w->noflush || + (!w->must_flush && + (jiffies - j->last_flush_write) < msecs_to_jiffies(c->opts.journal_flush_delay) && + test_bit(JOURNAL_MAY_SKIP_FLUSH, &j->flags)))) { w->noflush = true; SET_JSET_NO_FLUSH(jset, true); jset->last_seq = 0; w->last_seq = 0; j->nr_noflush_writes++; - } else { + } else if (!bch2_journal_error(j)) { j->last_flush_write = jiffies; j->nr_flush_writes++; + clear_bit(JOURNAL_NEED_FLUSH_WRITE, &j->flags); + } else { + spin_unlock(&j->lock); + goto err; } spin_unlock(&j->lock); @@ -1688,7 +1738,7 @@ void bch2_journal_write(struct closure *cl) * entry: */ - bch2_journal_entries_to_btree_roots(c, jset); + bch2_journal_entries_postprocess(c, jset); start = end = vstruct_last(jset);