DF-2568 / race_flush_v6.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | /* * DF-2568 โ v6: concurrent fsync-vs-unlink on a SHARED pool of files. * * KEY INSIGHT: fsync() blocks until the flush xop completes. So fsync+unlink * must be in DIFFERENT processes to overlap. Workers race on a shared pool: * * - TYPE A (dirtier): open(file_i), write blocks, fsync (dispatches flush * xop for file_i's inode, BLOCKS until flush done). * - TYPE B (unlinker): unlink(file_i) concurrently with A's fsync (dispatches * unlink xop via parent dir inode โ DIFFERENT thread group โ runs while * A's flush xop still holds file_i's chain lock). * * When B's unlink xop looks up file_i and tries to lock its chain, it blocks * because A's flush holds it. When A's flush_core hits :662 (unlock chain), * B grabs the lock and calls chain_delete (sets chain->parent = NULL). A's * flush then sees chain->parent != parent at :681 -> retry=1 -> :405 NULL deref. * * Workers pick random files from a shared pool. Syncers run sync() to drive * additional topology-wide flushes. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/wait.h> #include <sys/stat.h> #include <errno.h> static volatile sig_atomic_t stop = 0; static void on_alarm(int s){ (void)s;stop=1; } #define BLK 8192 static const char *g_dir; static int g_poolsize; static void path_for(char *buf, size_t bufsz, int idx) { snprintf(buf, bufsz, "%s/pool_%d", g_dir, idx); } /* Dirtier: opens a pool file, writes blocks (dirties chains), fsyncs. * The fsync dispatches a flush xop and blocks. Meanwhile, unlinkers may * unlink the same file, racing the flush. */ static void dirtier(int id, long iters) { char path[512], buf[BLK]; long i; unsigned seed = (unsigned)(id * 7 + 1); memset(buf, (char)(id + 0x43), sizeof(buf)); for (i = 0; i < iters && !stop; i++) { int idx = rand_r(&seed) % g_poolsize; int fd, b; path_for(path, sizeof(path), idx); fd = open(path, O_RDWR | O_CREAT, 0666); if (fd < 0) continue; buf[0] = (char)(id ^ i); for (b = 0; b < 6; b++) write(fd, buf, sizeof(buf)); /* fsync: dispatches flush xop, BLOCKS until done */ fsync(fd); close(fd); } _exit(0); } /* Unlinker: rapidly unlinks and recreates pool files. * The unlink races with any concurrent flush (from a dirtier's fsync). */ static void unlinker(int id, long iters) { char path[512]; long i; unsigned seed = (unsigned)(id * 13 + 3); for (i = 0; i < iters && !stop; i++) { int idx = rand_r(&seed) % g_poolsize; int fd; path_for(path, sizeof(path), idx); unlink(path); /* recreate so dirtiers can find it */ fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0666); if (fd >= 0) { char c = (char)(id ^ i); write(fd, &c, 1); close(fd); } } _exit(0); } /* Syncer: drives topology-wide flushes via sync() */ static void syncer(int id, long iters) { long i; for (i = 0; i < iters && !stop; i++) sync(); _exit(0); } int main(int argc, char **argv) { const char *dir = NULL; int ndirty = 12, nunlink = 12, nsync = 6; int poolsize = 64; long iters = 50000000; int timeout = 0, opt; int k, ki = 0, nkids; pid_t *kids; while ((opt = getopt(argc, argv, "d:D:U:s:p:i:t:")) != -1) { switch (opt) { case 'd': dir = optarg; break; case 'D': ndirty = atoi(optarg); break; case 'U': nunlink = atoi(optarg); break; case 's': nsync = atoi(optarg); break; case 'p': poolsize = atoi(optarg); break; case 'i': iters = atol(optarg); break; case 't': timeout = atoi(optarg); break; default: fprintf(stderr,"usage: %s -d dir [-D dirtiers] [-U unlinkers] [-s syncers] [-p pool] [-i iters] [-t sec]\n",argv[0]); return 2; } } if (!dir) { fprintf(stderr,"%s: need -d dir\n",argv[0]); return 2; } g_dir = dir; g_poolsize = poolsize; fprintf(stderr,"DF-2568 v6: dir=%s dirtiers=%d unlinkers=%d syncers=%d pool=%d iters=%ld timeout=%d\n", dir, ndirty, nunlink, nsync, poolsize, iters, timeout); /* Pre-populate pool so unlinkers/dirtiers have something to race on */ { char path[512]; int i, fd; char buf[BLK]; memset(buf, 'P', sizeof(buf)); for (i = 0; i < poolsize; i++) { path_for(path, sizeof(path), i); fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0666); if (fd >= 0) { write(fd, buf, sizeof(buf)); close(fd); } } } signal(SIGALRM, on_alarm); if (timeout > 0) alarm((unsigned)timeout); nkids = ndirty + nunlink + nsync; kids = calloc(nkids, sizeof(pid_t)); if (!kids) { perror("calloc"); return 1; } for (k = 0; k < ndirty; k++) { pid_t p = fork(); if (p < 0) { perror("fork"); return 1; } if (p == 0) dirtier(k, iters); kids[ki++] = p; } for (k = 0; k < nunlink; k++) { pid_t p = fork(); if (p < 0) { perror("fork"); return 1; } if (p == 0) unlinker(k, iters); kids[ki++] = p; } for (k = 0; k < nsync; k++) { pid_t p = fork(); if (p < 0) { perror("fork"); return 1; } if (p == 0) syncer(k, iters); kids[ki++] = p; } for (k = 0; k < nkids; k++) if (kids[k] > 0) waitpid(kids[k], NULL, 0); fprintf(stderr, "DF-2568 v6: done (no panic)\n"); return 0; } |