DF-2873 / tqlost.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-2873 PoC attempt: taskqueue_drain() lost-wakeup window. * * taskqueue_run() executes, OUTSIDE the queue spinlock: * queue->tq_running = NULL; wakeup(task); * while taskqueue_drain()/drain_simple() check * task->ta_pending != 0 || task == queue->tq_running * under the spinlock and then park via ssleep(). The waker never takes * the spinlock before wakeup(), so its wakeup can land between the * drainer's condition check and its sleep-queue interlock enqueue and * be lost forever -> the drainer sleeps for good (wmesg "-"). * * Two hammer threads race enqueue/drain on one task; a watchdog reports * if a hammer thread freezes while the task is fully idle * (ta_pending == 0 and not running) -- which proves the lost wakeup. * * Timebox: ~90 seconds, then gives up (bug is real but window is tiny). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/kthread.h> #include <sys/malloc.h> #include <sys/proc.h> #include <sys/systm.h> #include <sys/taskqueue.h> #include <sys/thread.h> #include <sys/conf.h> #include <sys/module.h> static struct taskqueue *ltq; static struct task lt[4]; static volatile int lt_runs; static volatile unsigned long h0, h1; static volatile int stop_hammers; static volatile int hit_thr; /* 0 = running, 1 = hit, 2 = gave up */ static void lt_func(void *arg, int pending) { lt_runs++; } struct hammer_arg { volatile unsigned long *ctr; struct task *t; }; static struct hammer_arg hargs[4]; static volatile unsigned long hctr[4]; static void hammer(void *arg) { struct hammer_arg *ha = arg; while (!stop_hammers) { taskqueue_enqueue(ltq, ha->t); taskqueue_drain(ltq, ha->t); (*ha->ctr)++; } kthread_exit(); } static void watchdog(void *arg) { unsigned long snap[4]; int round, k; for (round = 0; round < 36 && hit_thr == 0; round++) { tsleep(&hit_thr, 0, "ltwd", hz / 2); if (stop_hammers) break; for (k = 0; k < 4; k++) snap[k] = hctr[k]; tsleep(&hit_thr, 0, "ltwd2", hz * 4); if (stop_hammers) break; int k, froze = -1; for (k = 0; k < 4; k++) { if (hctr[k] == snap[k]) froze = k; } if (froze >= 0 && !stop_hammers) { if (lt[froze].ta_pending == 0) { kprintf("tqlost: LOST WAKEUP REPRODUCED: " "hammer %d frozen (h%u %lu->%lu) with " "ta_pending=0 and task not running\n", froze, froze, snap[froze], hctr[froze]); hit_thr = 1; } else { kprintf("tqlost: hammer frozen but " "ta_pending=%d (still queued?)\n", lt[froze].ta_pending); } } } if (hit_thr == 0) { hit_thr = 2; kprintf("tqlost: not hit in ~90s (window is tiny; giving up)\n"); } stop_hammers = 1; wakeup(&stop_hammers); kthread_exit(); } static void lt_ctrl(void *arg) { int error, i; ltq = taskqueue_create("tqlost", M_WAITOK, taskqueue_thread_enqueue, <q); error = taskqueue_start_threads(<q, 1, TDPRI_KERN_DAEMON, -1, "tqlost"); kprintf("tqlost: queue %p started (%d), hammering drain window\n", ltq, error); for (i = 0; i < 4; i++) { TASK_INIT(<[i], 0, lt_func, NULL); hargs[i].ctr = &hctr[i]; hargs[i].t = <[i]; } hit_thr = 0; for (i = 0; i < 4; i++) kthread_create(hammer, &hargs[i], NULL, "lt_h%d", i); kthread_create(watchdog, NULL, NULL, "lt_wd"); /* controller exits; hammers stop themselves via stop_hammers */ kthread_exit(); } static int tqlost_ev(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kthread_create(lt_ctrl, NULL, NULL, "ltctrl"); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } DEV_MODULE(tqlost, tqlost_ev, NULL); |