bcachefs: Break up bch2_journal_write()
authorKent Overstreet <kent.overstreet@linux.dev>
Fri, 3 Nov 2023 01:01:25 +0000 (21:01 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Sun, 5 Nov 2023 18:12:18 +0000 (13:12 -0500)
Split up bch2_journal_write() to simplify locking:
 - bch2_journal_write_pick_flush(), which needs j->lock
 - bch2_journal_write_prep, which operates on the journal buffer to be
   written and will need the upcoming buf_lock for synchronization with
   the btree write buffer flush path

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/journal_io.c

index 65878542940d924e9d6656ff5590a14fe8a70b14..392e90d4d4fbeb2131b5ed3e47e7020af0643bf2 100644 (file)
@@ -1724,68 +1724,18 @@ static void bch2_journal_entries_postprocess(struct bch_fs *c, struct jset *jset
        jset->u64s = cpu_to_le32((u64 *) prev - jset->_data);
 }
 
-void bch2_journal_write(struct closure *cl)
+static int bch2_journal_write_prep(struct journal *j, struct journal_buf *w)
 {
-       struct journal *j = container_of(cl, struct journal, io);
        struct bch_fs *c = container_of(j, struct bch_fs, journal);
-       struct bch_dev *ca;
-       struct journal_buf *w = journal_last_unwritten_buf(j);
-       struct bch_replicas_padded replicas;
        struct jset_entry *start, *end;
        struct jset *jset;
-       struct bio *bio;
-       struct printbuf journal_debug_buf = PRINTBUF;
+       unsigned sectors, bytes, u64s;
        bool validate_before_checksum = false;
-       unsigned i, sectors, bytes, u64s, nr_rw_members = 0;
        int ret;
 
-       BUG_ON(BCH_SB_CLEAN(c->disk_sb.sb));
-
        journal_buf_realloc(j, w);
        jset = w->data;
 
-       j->write_start_time = local_clock();
-
-       spin_lock(&j->lock);
-
-       /*
-        * 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 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);
-
        /*
         * New btree roots are set by journalling them; when the journal entry
         * gets written we have to propagate them to c->btree_roots
@@ -1816,7 +1766,7 @@ void bch2_journal_write(struct closure *cl)
                bch2_fs_fatal_error(c, "aieeee! journal write overran available space, %zu > %u (extra %u reserved %u/%u)",
                                    vstruct_bytes(jset), w->sectors << 9,
                                    u64s, w->u64s_reserved, j->entry_u64s_reserved);
-               goto err;
+               return -EINVAL;
        }
 
        jset->magic             = cpu_to_le64(jset_magic(c));
@@ -1835,37 +1785,115 @@ void bch2_journal_write(struct closure *cl)
                validate_before_checksum = true;
 
        if (validate_before_checksum &&
-           jset_validate(c, NULL, jset, 0, WRITE))
-               goto err;
+           (ret = jset_validate(c, NULL, jset, 0, WRITE)))
+               return ret;
 
        ret = bch2_encrypt(c, JSET_CSUM_TYPE(jset), journal_nonce(jset),
                    jset->encrypted_start,
                    vstruct_end(jset) - (void *) jset->encrypted_start);
        if (bch2_fs_fatal_err_on(ret, c,
                        "error decrypting journal entry: %i", ret))
-               goto err;
+               return ret;
 
        jset->csum = csum_vstruct(c, JSET_CSUM_TYPE(jset),
                                  journal_nonce(jset), jset);
 
        if (!validate_before_checksum &&
-           jset_validate(c, NULL, jset, 0, WRITE))
-               goto err;
+           (ret = jset_validate(c, NULL, jset, 0, WRITE)))
+               return ret;
 
        memset((void *) jset + bytes, 0, (sectors << 9) - bytes);
+       return 0;
+}
+
+static int bch2_journal_write_pick_flush(struct journal *j, struct journal_buf *w)
+{
+       struct bch_fs *c = container_of(j, struct bch_fs, journal);
+       int error = bch2_journal_error(j);
+
+       /*
+        * 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 (error && test_bit(JOURNAL_NEED_FLUSH_WRITE, &j->flags))
+               return -EIO;
+
+       if (error ||
+           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(w->data, true);
+               w->data->last_seq       = 0;
+               w->last_seq             = 0;
+
+               j->nr_noflush_writes++;
+       } else {
+               j->last_flush_write = jiffies;
+               j->nr_flush_writes++;
+               clear_bit(JOURNAL_NEED_FLUSH_WRITE, &j->flags);
+       }
+
+       return 0;
+}
+
+void bch2_journal_write(struct closure *cl)
+{
+       struct journal *j = container_of(cl, struct journal, io);
+       struct bch_fs *c = container_of(j, struct bch_fs, journal);
+       struct bch_dev *ca;
+       struct journal_buf *w = journal_last_unwritten_buf(j);
+       struct bch_replicas_padded replicas;
+       struct bio *bio;
+       struct printbuf journal_debug_buf = PRINTBUF;
+       unsigned i, nr_rw_members = 0;
+       int ret;
+
+       BUG_ON(BCH_SB_CLEAN(c->disk_sb.sb));
+
+       j->write_start_time = local_clock();
 
-retry_alloc:
        spin_lock(&j->lock);
-       ret = journal_write_alloc(j, w);
+       ret = bch2_journal_write_pick_flush(j, w);
+       spin_unlock(&j->lock);
+       if (ret)
+               goto err;
+
+       ret = bch2_journal_write_prep(j, w);
+       if (ret)
+               goto err;
+
+       while (1) {
+               spin_lock(&j->lock);
+               ret = journal_write_alloc(j, w);
+               if (!ret || !j->can_discard)
+                       break;
 
-       if (ret && j->can_discard) {
                spin_unlock(&j->lock);
                bch2_journal_do_discards(j);
-               goto retry_alloc;
        }
 
-       if (ret)
+       if (ret) {
                __bch2_journal_debug_to_text(&journal_debug_buf, j);
+               spin_unlock(&j->lock);
+               bch_err(c, "Unable to allocate journal write:\n%s",
+                       journal_debug_buf.buf);
+               printbuf_exit(&journal_debug_buf);
+               goto err;
+       }
 
        /*
         * write is allocated, no longer need to account for it in
@@ -1880,13 +1908,6 @@ retry_alloc:
        bch2_journal_space_available(j);
        spin_unlock(&j->lock);
 
-       if (ret) {
-               bch_err(c, "Unable to allocate journal write:\n%s",
-                       journal_debug_buf.buf);
-               printbuf_exit(&journal_debug_buf);
-               goto err;
-       }
-
        w->devs_written = bch2_bkey_devs(bkey_i_to_s_c(&w->key));
 
        if (c->opts.nochanges)
@@ -1908,7 +1929,7 @@ retry_alloc:
        if (ret)
                goto err;
 
-       if (!JSET_NO_FLUSH(jset) && w->separate_flush) {
+       if (!JSET_NO_FLUSH(w->data) && w->separate_flush) {
                for_each_rw_member(ca, c, i) {
                        percpu_ref_get(&ca->io_ref);