e3c68b
From 48f6929590157d9a1697e11c02441207afdc1bed Mon Sep 17 00:00:00 2001
e3c68b
From: Xavi Hernandez <xhernandez@redhat.com>
e3c68b
Date: Fri, 27 Mar 2020 23:56:15 +0100
e3c68b
Subject: [PATCH 362/362] write-behind: fix data corruption
e3c68b
e3c68b
There was a bug in write-behind that allowed a previous completed write
e3c68b
to overwrite the overlapping region of data from a future write.
e3c68b
e3c68b
Suppose we want to send three writes (W1, W2 and W3). W1 and W2 are
e3c68b
sequential, and W3 writes at the same offset of W2:
e3c68b
e3c68b
    W2.offset = W3.offset = W1.offset + W1.size
e3c68b
e3c68b
Both W1 and W2 are sent in parallel. W3 is only sent after W2 completes.
e3c68b
So W3 should *always* overwrite the overlapping part of W2.
e3c68b
e3c68b
Suppose write-behind processes the requests from 2 concurrent threads:
e3c68b
e3c68b
    Thread 1                    Thread 2
e3c68b
e3c68b
    <received W1>
e3c68b
                                <received W2>
e3c68b
    wb_enqueue_tempted(W1)
e3c68b
    /* W1 is assigned gen X */
e3c68b
                                wb_enqueue_tempted(W2)
e3c68b
                                /* W2 is assigned gen X */
e3c68b
e3c68b
                                wb_process_queue()
e3c68b
                                  __wb_preprocess_winds()
e3c68b
                                    /* W1 and W2 are sequential and all
e3c68b
                                     * other requisites are met to merge
e3c68b
                                     * both requests. */
e3c68b
                                    __wb_collapse_small_writes(W1, W2)
e3c68b
                                    __wb_fulfill_request(W2)
e3c68b
e3c68b
                                  __wb_pick_unwinds() -> W2
e3c68b
                                  /* In this case, since the request is
e3c68b
                                   * already fulfilled, wb_inode->gen
e3c68b
                                   * is not updated. */
e3c68b
e3c68b
                                wb_do_unwinds()
e3c68b
                                  STACK_UNWIND(W2)
e3c68b
e3c68b
                                /* The application has received the
e3c68b
                                 * result of W2, so it can send W3. */
e3c68b
                                <received W3>
e3c68b
e3c68b
                                wb_enqueue_tempted(W3)
e3c68b
                                /* W3 is assigned gen X */
e3c68b
e3c68b
                                wb_process_queue()
e3c68b
                                  /* Here we have W1 (which contains
e3c68b
                                   * the conflicting W2) and W3 with
e3c68b
                                   * same gen, so they are interpreted
e3c68b
                                   * as concurrent writes that do not
e3c68b
                                   * conflict. */
e3c68b
                                  __wb_pick_winds() -> W3
e3c68b
e3c68b
                                wb_do_winds()
e3c68b
                                  STACK_WIND(W3)
e3c68b
e3c68b
    wb_process_queue()
e3c68b
      /* Eventually W1 will be
e3c68b
       * ready to be sent */
e3c68b
      __wb_pick_winds() -> W1
e3c68b
      __wb_pick_unwinds() -> W1
e3c68b
        /* Here wb_inode->gen is
e3c68b
         * incremented. */
e3c68b
e3c68b
    wb_do_unwinds()
e3c68b
      STACK_UNWIND(W1)
e3c68b
e3c68b
    wb_do_winds()
e3c68b
      STACK_WIND(W1)
e3c68b
e3c68b
So, as we can see, W3 is sent before W1, which shouldn't happen.
e3c68b
e3c68b
The problem is that wb_inode->gen is only incremented for requests that
e3c68b
have not been fulfilled but, after a merge, the request is marked as
e3c68b
fulfilled even though it has not been sent to the brick. This allows
e3c68b
that future requests are assigned to the same generation, which could
e3c68b
be internally reordered.
e3c68b
e3c68b
Solution:
e3c68b
e3c68b
Increment wb_inode->gen before any unwind, even if it's for a fulfilled
e3c68b
request.
e3c68b
e3c68b
Special thanks to Stefan Ring for writing a reproducer that has been
e3c68b
crucial to identify the issue.
e3c68b
e3c68b
Upstream patch:
e3c68b
> Upstream patch link: https://review.gluster.org/c/glusterfs/+/24263
e3c68b
> Change-Id: Id4ab0f294a09aca9a863ecaeef8856474662ab45
e3c68b
> Signed-off-by: Xavi Hernandez <xhernandez@redhat.com>
e3c68b
> Fixes: #884
e3c68b
e3c68b
Change-Id: Id4ab0f294a09aca9a863ecaeef8856474662ab45
e3c68b
Signed-off-by: Xavi Hernandez <xhernandez@redhat.com>
e3c68b
BUG: 1819059
e3c68b
Reviewed-on: https://code.engineering.redhat.com/gerrit/196250
e3c68b
Tested-by: RHGS Build Bot <nigelb@redhat.com>
e3c68b
Reviewed-by: Sunil Kumar Heggodu Gopala Acharya <sheggodu@redhat.com>
e3c68b
---
e3c68b
 tests/bugs/write-behind/issue-884.c                | 267 +++++++++++++++++++++
e3c68b
 tests/bugs/write-behind/issue-884.t                |  40 +++
e3c68b
 .../performance/write-behind/src/write-behind.c    |   4 +-
e3c68b
 3 files changed, 309 insertions(+), 2 deletions(-)
e3c68b
 create mode 100644 tests/bugs/write-behind/issue-884.c
e3c68b
 create mode 100755 tests/bugs/write-behind/issue-884.t
e3c68b
e3c68b
diff --git a/tests/bugs/write-behind/issue-884.c b/tests/bugs/write-behind/issue-884.c
e3c68b
new file mode 100644
e3c68b
index 0000000..e9c33b3
e3c68b
--- /dev/null
e3c68b
+++ b/tests/bugs/write-behind/issue-884.c
e3c68b
@@ -0,0 +1,267 @@
e3c68b
+
e3c68b
+#define _GNU_SOURCE
e3c68b
+
e3c68b
+#include <stdlib.h>
e3c68b
+#include <stdio.h>
e3c68b
+#include <string.h>
e3c68b
+#include <time.h>
e3c68b
+#include <assert.h>
e3c68b
+#include <errno.h>
e3c68b
+#include <sys/types.h>
e3c68b
+#include <sys/stat.h>
e3c68b
+#include <pthread.h>
e3c68b
+
e3c68b
+#include <glusterfs/api/glfs.h>
e3c68b
+
e3c68b
+/* Based on a reproducer by Stefan Ring. It seems to be quite sensible to any
e3c68b
+ * timing modification, so the code has been maintained as is, only with minor
e3c68b
+ * changes. */
e3c68b
+
e3c68b
+struct glfs *glfs;
e3c68b
+
e3c68b
+pthread_mutex_t the_mutex = PTHREAD_MUTEX_INITIALIZER;
e3c68b
+pthread_cond_t the_cond = PTHREAD_COND_INITIALIZER;
e3c68b
+
e3c68b
+typedef struct _my_aiocb {
e3c68b
+    int64_t size;
e3c68b
+    volatile int64_t seq;
e3c68b
+    int which;
e3c68b
+} my_aiocb;
e3c68b
+
e3c68b
+typedef struct _worker_data {
e3c68b
+    my_aiocb cb;
e3c68b
+    struct iovec iov;
e3c68b
+    int64_t offset;
e3c68b
+} worker_data;
e3c68b
+
e3c68b
+typedef struct {
e3c68b
+    worker_data wdata[2];
e3c68b
+
e3c68b
+    volatile unsigned busy;
e3c68b
+} all_data_t;
e3c68b
+
e3c68b
+all_data_t all_data;
e3c68b
+
e3c68b
+static void
e3c68b
+completion_fnc(struct glfs_fd *fd, ssize_t ret, struct glfs_stat *pre,
e3c68b
+               struct glfs_stat *post, void *arg)
e3c68b
+{
e3c68b
+    void *the_thread;
e3c68b
+    my_aiocb *cb = (my_aiocb *)arg;
e3c68b
+    long seq = cb->seq;
e3c68b
+
e3c68b
+    assert(ret == cb->size);
e3c68b
+
e3c68b
+    pthread_mutex_lock(&the_mutex);
e3c68b
+    pthread_cond_broadcast(&the_cond);
e3c68b
+
e3c68b
+    all_data.busy &= ~(1 << cb->which);
e3c68b
+    cb->seq = -1;
e3c68b
+
e3c68b
+    the_thread = (void *)pthread_self();
e3c68b
+    printf("worker %d is done from thread %p, seq %ld!\n", cb->which,
e3c68b
+           the_thread, seq);
e3c68b
+
e3c68b
+    pthread_mutex_unlock(&the_mutex);
e3c68b
+}
e3c68b
+
e3c68b
+static void
e3c68b
+init_wdata(worker_data *data, int which)
e3c68b
+{
e3c68b
+    data->cb.which = which;
e3c68b
+    data->cb.seq = -1;
e3c68b
+
e3c68b
+    data->iov.iov_base = malloc(1024 * 1024);
e3c68b
+    memset(data->iov.iov_base, 6,
e3c68b
+           1024 * 1024); /* tail part never overwritten */
e3c68b
+}
e3c68b
+
e3c68b
+static void
e3c68b
+init()
e3c68b
+{
e3c68b
+    all_data.busy = 0;
e3c68b
+
e3c68b
+    init_wdata(&all_data.wdata[0], 0);
e3c68b
+    init_wdata(&all_data.wdata[1], 1);
e3c68b
+}
e3c68b
+
e3c68b
+static void
e3c68b
+do_write(struct glfs_fd *fd, int content, int size, int64_t seq,
e3c68b
+         worker_data *wdata, const char *name)
e3c68b
+{
e3c68b
+    int ret;
e3c68b
+
e3c68b
+    wdata->cb.size = size;
e3c68b
+    wdata->cb.seq = seq;
e3c68b
+
e3c68b
+    if (content >= 0)
e3c68b
+        memset(wdata->iov.iov_base, content, size);
e3c68b
+    wdata->iov.iov_len = size;
e3c68b
+
e3c68b
+    pthread_mutex_lock(&the_mutex);
e3c68b
+    printf("(%d) dispatching write \"%s\", offset %lx, len %x, seq %ld\n",
e3c68b
+           wdata->cb.which, name, (long)wdata->offset, size, (long)seq);
e3c68b
+    pthread_mutex_unlock(&the_mutex);
e3c68b
+    ret = glfs_pwritev_async(fd, &wdata->iov, 1, wdata->offset, 0,
e3c68b
+                             completion_fnc, &wdata->cb);
e3c68b
+    assert(ret >= 0);
e3c68b
+}
e3c68b
+
e3c68b
+#define IDLE 0  // both workers must be idle
e3c68b
+#define ANY 1   // use any worker, other one may be busy
e3c68b
+
e3c68b
+int
e3c68b
+get_worker(int waitfor, int64_t excl_seq)
e3c68b
+{
e3c68b
+    int which;
e3c68b
+
e3c68b
+    pthread_mutex_lock(&the_mutex);
e3c68b
+
e3c68b
+    while (waitfor == IDLE && (all_data.busy & 3) != 0 ||
e3c68b
+           waitfor == ANY &&
e3c68b
+               ((all_data.busy & 3) == 3 ||
e3c68b
+                excl_seq >= 0 && (all_data.wdata[0].cb.seq == excl_seq ||
e3c68b
+                                  all_data.wdata[1].cb.seq == excl_seq)))
e3c68b
+        pthread_cond_wait(&the_cond, &the_mutex);
e3c68b
+
e3c68b
+    if (!(all_data.busy & 1))
e3c68b
+        which = 0;
e3c68b
+    else
e3c68b
+        which = 1;
e3c68b
+
e3c68b
+    all_data.busy |= (1 << which);
e3c68b
+
e3c68b
+    pthread_mutex_unlock(&the_mutex);
e3c68b
+
e3c68b
+    return which;
e3c68b
+}
e3c68b
+
e3c68b
+static int
e3c68b
+doit(struct glfs_fd *fd)
e3c68b
+{
e3c68b
+    int ret;
e3c68b
+    int64_t seq = 0;
e3c68b
+    int64_t offset = 0;     // position in file, in blocks
e3c68b
+    int64_t base = 0x1000;  // where to place the data, in blocks
e3c68b
+
e3c68b
+    int async_mode = ANY;
e3c68b
+
e3c68b
+    init();
e3c68b
+
e3c68b
+    for (;;) {
e3c68b
+        int which;
e3c68b
+        worker_data *wdata;
e3c68b
+
e3c68b
+        // for growing to the first offset
e3c68b
+        for (;;) {
e3c68b
+            int gap = base + 0x42 - offset;
e3c68b
+            if (!gap)
e3c68b
+                break;
e3c68b
+            if (gap > 80)
e3c68b
+                gap = 80;
e3c68b
+
e3c68b
+            which = get_worker(IDLE, -1);
e3c68b
+            wdata = &all_data.wdata[which];
e3c68b
+
e3c68b
+            wdata->offset = offset << 9;
e3c68b
+            do_write(fd, 0, gap << 9, seq++, wdata, "gap-filling");
e3c68b
+
e3c68b
+            offset += gap;
e3c68b
+        }
e3c68b
+
e3c68b
+        // 8700
e3c68b
+        which = get_worker(IDLE, -1);
e3c68b
+        wdata = &all_data.wdata[which];
e3c68b
+
e3c68b
+        wdata->offset = (base + 0x42) << 9;
e3c68b
+        do_write(fd, 1, 62 << 9, seq++, wdata, "!8700");
e3c68b
+
e3c68b
+        // 8701
e3c68b
+        which = get_worker(IDLE, -1);
e3c68b
+        wdata = &all_data.wdata[which];
e3c68b
+
e3c68b
+        wdata->offset = (base + 0x42) << 9;
e3c68b
+        do_write(fd, 2, 55 << 9, seq++, wdata, "!8701");
e3c68b
+
e3c68b
+        // 8702
e3c68b
+        which = get_worker(async_mode, -1);
e3c68b
+        wdata = &all_data.wdata[which];
e3c68b
+
e3c68b
+        wdata->offset = (base + 0x79) << 9;
e3c68b
+        do_write(fd, 3, 54 << 9, seq++, wdata, "!8702");
e3c68b
+
e3c68b
+        // 8703
e3c68b
+        which = get_worker(async_mode, -1);
e3c68b
+        wdata = &all_data.wdata[which];
e3c68b
+
e3c68b
+        wdata->offset = (base + 0xaf) << 9;
e3c68b
+        do_write(fd, 4, 81 << 9, seq++, wdata, "!8703");
e3c68b
+
e3c68b
+        // 8704
e3c68b
+        // this writes both 5s and 6s
e3c68b
+        // the range of 5s is the one that overwrites 8703
e3c68b
+
e3c68b
+        which = get_worker(async_mode, seq - 1);
e3c68b
+        wdata = &all_data.wdata[which];
e3c68b
+
e3c68b
+        memset(wdata->iov.iov_base, 5, 81 << 9);
e3c68b
+        wdata->offset = (base + 0xaf) << 9;
e3c68b
+        do_write(fd, -1, 1623 << 9, seq++, wdata, "!8704");
e3c68b
+
e3c68b
+        offset = base + 0x706;
e3c68b
+        base += 0x1000;
e3c68b
+        if (base >= 0x100000)
e3c68b
+            break;
e3c68b
+    }
e3c68b
+
e3c68b
+    printf("done!\n");
e3c68b
+    fflush(stdout);
e3c68b
+
e3c68b
+    pthread_mutex_lock(&the_mutex);
e3c68b
+
e3c68b
+    while ((all_data.busy & 3) != 0)
e3c68b
+        pthread_cond_wait(&the_cond, &the_mutex);
e3c68b
+
e3c68b
+    pthread_mutex_unlock(&the_mutex);
e3c68b
+
e3c68b
+    ret = glfs_close(fd);
e3c68b
+    assert(ret >= 0);
e3c68b
+    /*
e3c68b
+        ret = glfs_fini(glfs);
e3c68b
+        assert(ret >= 0);
e3c68b
+    */
e3c68b
+    return 0;
e3c68b
+}
e3c68b
+
e3c68b
+int
e3c68b
+main(int argc, char *argv[])
e3c68b
+{
e3c68b
+    int ret;
e3c68b
+    int open_flags = O_RDWR | O_DIRECT | O_TRUNC;
e3c68b
+    struct glfs_fd *fd;
e3c68b
+
e3c68b
+    glfs = glfs_new(argv[1]);
e3c68b
+    if (!glfs) {
e3c68b
+        printf("glfs_new!\n");
e3c68b
+        goto out;
e3c68b
+    }
e3c68b
+    ret = glfs_set_volfile_server(glfs, "tcp", "localhost", 24007);
e3c68b
+    if (ret < 0) {
e3c68b
+        printf("set_volfile!\n");
e3c68b
+        goto out;
e3c68b
+    }
e3c68b
+    ret = glfs_init(glfs);
e3c68b
+    if (ret) {
e3c68b
+        printf("init!\n");
e3c68b
+        goto out;
e3c68b
+    }
e3c68b
+    fd = glfs_open(glfs, argv[2], open_flags);
e3c68b
+    if (!fd) {
e3c68b
+        printf("open!\n");
e3c68b
+        goto out;
e3c68b
+    }
e3c68b
+    srand(time(NULL));
e3c68b
+    return doit(fd);
e3c68b
+out:
e3c68b
+    return 1;
e3c68b
+}
e3c68b
diff --git a/tests/bugs/write-behind/issue-884.t b/tests/bugs/write-behind/issue-884.t
e3c68b
new file mode 100755
e3c68b
index 0000000..2bcf7d1
e3c68b
--- /dev/null
e3c68b
+++ b/tests/bugs/write-behind/issue-884.t
e3c68b
@@ -0,0 +1,40 @@
e3c68b
+#!/bin/bash
e3c68b
+
e3c68b
+. $(dirname $0)/../../include.rc
e3c68b
+. $(dirname $0)/../../volume.rc
e3c68b
+
e3c68b
+# This test tries to detect a race condition in write-behind. It's based on a
e3c68b
+# reproducer written by Stefan Ring that is able to hit it sometimes. On my
e3c68b
+# system, it happened around 10% of the runs. This means that if this bug
e3c68b
+# appears again, this test will fail once every 10 runs. Most probably this
e3c68b
+# failure will be hidden by the automatic test retry of the testing framework.
e3c68b
+#
e3c68b
+# Please, if this test fails, it needs to be analyzed in detail.
e3c68b
+
e3c68b
+function run() {
e3c68b
+    "${@}" >/dev/null
e3c68b
+}
e3c68b
+
e3c68b
+cleanup
e3c68b
+
e3c68b
+TEST glusterd
e3c68b
+TEST pidof glusterd
e3c68b
+
e3c68b
+TEST $CLI volume create $V0 $H0:$B0/$V0
e3c68b
+# This makes it easier to hit the issue
e3c68b
+TEST $CLI volume set $V0 client-log-level TRACE
e3c68b
+TEST $CLI volume start $V0
e3c68b
+
e3c68b
+TEST $GFS --volfile-server=$H0 --volfile-id=$V0 $M0
e3c68b
+
e3c68b
+build_tester $(dirname $0)/issue-884.c -lgfapi
e3c68b
+
e3c68b
+TEST touch $M0/testfile
e3c68b
+
e3c68b
+# This program generates a file of 535694336 bytes with a fixed pattern
e3c68b
+TEST run $(dirname $0)/issue-884 $V0 testfile
e3c68b
+
e3c68b
+# This is the md5sum of the expected pattern without corruption
e3c68b
+EXPECT "ad105f9349345a70fc697632cbb5eec8" echo "$(md5sum $B0/$V0/testfile | awk '{ print $1; }')"
e3c68b
+
e3c68b
+cleanup
e3c68b
diff --git a/xlators/performance/write-behind/src/write-behind.c b/xlators/performance/write-behind/src/write-behind.c
e3c68b
index 70e281a..90a0bcf 100644
e3c68b
--- a/xlators/performance/write-behind/src/write-behind.c
e3c68b
+++ b/xlators/performance/write-behind/src/write-behind.c
e3c68b
@@ -1284,14 +1284,14 @@ __wb_pick_unwinds(wb_inode_t *wb_inode, list_head_t *lies)
e3c68b
 
e3c68b
         wb_inode->window_current += req->orig_size;
e3c68b
 
e3c68b
+        wb_inode->gen++;
e3c68b
+
e3c68b
         if (!req->ordering.fulfilled) {
e3c68b
             /* burden increased */
e3c68b
             list_add_tail(&req->lie, &wb_inode->liability);
e3c68b
 
e3c68b
             req->ordering.lied = 1;
e3c68b
 
e3c68b
-            wb_inode->gen++;
e3c68b
-
e3c68b
             uuid_utoa_r(req->gfid, gfid);
e3c68b
             gf_msg_debug(wb_inode->this->name, 0,
e3c68b
                          "(unique=%" PRIu64
e3c68b
-- 
e3c68b
1.8.3.1
e3c68b