DF-2645 / stress2645.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 | /* * DF-2645 stress: race a concurrent inode-chain modification against the * flusher's indirect-block collapse (hammer2_chain_indirect_maintenance * reached from hammer2_flush_core, hammer2_flush.c:1040). * * Mechanism under test: * - flush_core(B) scan flushes B's children (incl. file inode chains F*) * - a concurrent write to F* modifies F*'s inode chain AFTER the scan * visited it (COW -> new bref.data_off), while B is still locked in * the bottom-up phase * - maintenance(A,B) collapse loop hits the skip guard * (bcmp(&bsave,&sub->bref) mismatch, hammer2_chain.c:4202-4209) * - chain left un-moved -> hammer2_chain_repchange KKASSERT * (live_count==0 && RB_EMPTY) fires at hammer2_chain.c:2314 * (KKASSERT is unconditional on DragonFly) * * Usage: stress2645 <dir> <nwriter> <mode> * mode 1 = plain 1-byte appends, rotate over surviving files * mode 2 = append + fsync per iteration */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main(int argc, char **argv) { const char *dir = argv[1]; int nwriter = atoi(argv[2]); int mode = atoi(argv[3]); char **paths; char name[256]; char buf[256]; int nfiles = 0, i, fd; long iter = 0; FILE *lsp; int pct; if (argc < 4) { fprintf(stderr, "usage: %s dir nwriter mode\n", argv[0]); exit(1); } /* collect surviving files */ snprintf(buf, sizeof(buf), "find %s -type f 2>/dev/null | sort", dir); lsp = popen(buf, "r"); if (!lsp) { perror("popen"); exit(1); } paths = calloc(65536, sizeof(char *)); while (fgets(name, sizeof(name), lsp) && nfiles < 65536) { name[strlen(name)-1] = 0; if (name[0] == 0) continue; asprintf(&paths[nfiles], "%s", name); ++nfiles; } pclose(lsp); if (nfiles == 0) { fprintf(stderr, "no files found under %s\n", dir); exit(1); } fprintf(stderr, "stress2645: %d surviving files, %d writers, mode %d\n", nfiles, nwriter, mode); setvbuf(stdout, NULL, _IOLBF, 0); for (i = 0; i < nwriter; ++i) { pid_t pid = fork(); if (pid == 0) { unsigned long seed = i * 7919 + 13; long mine = 0; for (;;) { seed = seed * 1103515245 + 12345; pct = (seed >> 16) % nfiles; fd = open(paths[pct], O_WRONLY|O_APPEND); if (fd < 0) { usleep(1000); continue; } write(fd, "x", 1); if (mode == 2 && fsync(fd) < 0) { /* ignore */ } close(fd); ++mine; if ((mine & 0xFFFF) == 0) { fprintf(stdout, "w%d: %ld appends\n", i, mine); } } _exit(0); } } /* churn children: continuously create+delete randomly-named files * across the whole dirhash key space so the flusher's * indirect-maintenance always has freshly-created indirects that * are becoming sparse (pending deletes) -- i.e. live collapse work * overlapping the writers' inode-chain modifications */ for (i = 0; i < 2; ++i) { pid_t pid = fork(); if (pid == 0) { unsigned long seed = 4242 + i; long mine = 0; char nm[32], path[1024]; int j, mode2 = i; for (;;) { seed = seed * 6364136223846793005UL + 1442695040888963407UL; for (j = 0; j < 24; ++j) { nm[j] = 'a' + ((seed >> ((j % 21) + 3)) % 26); seed = seed * 6364136223846793005UL + 1442695040888963407UL; } nm[24] = 0; snprintf(path, sizeof(path), "%s/%s", dir, nm); if (mode2 == 0) { int fd2 = open(path, O_CREAT|O_RDWR|O_TRUNC, 0644); if (fd2 >= 0) { write(fd2, "c", 1); close(fd2); } } else { unlink(path); } ++mine; if ((mine & 0x3FFF) == 0) fprintf(stdout, "churn%d: %ld\n", mode2, mine); } _exit(0); } } /* parent: progress heartbeat */ for (;;) { sleep(30); fprintf(stdout, "parent alive, files=%d\n", nfiles); } return 0; } |