DF-2870 / tqdead.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 | /* * DF-2870 PoC: taskqueue_free() terminate lost-wakeup deadlock. * * taskqueue_thread_loop() (subr_taskqueue.c:617-619) parks in * TQ_SLEEP(tq, tq) after taskqueue_run() WITHOUT re-checking TQ_FLAGS_ACTIVE. * taskqueue_terminate() (subr_taskqueue.c:141-147) delivers wakeup(tq) * after dropping the queue spinlock; if the worker is executing a task * function at that moment (not yet on the sleep queue) the wakeup is lost. * The worker later parks unconditionally, the terminator is asleep on * tq_threads, nobody is left to wake either -> permanent deadlock. * * Build: make * Run: kldload ./tqdead.ko * Watch: kernel console shows "calling taskqueue_free"; free never returns; * `ps -axH` shows tquafctrl sleeping in "taskqueue_terminate" and * the worker sleeping in "tqthr" forever. */ #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 *dtq; static struct task dt; static volatile int d_entered; static volatile int d_release; static volatile int d_free_returned; static struct thread *ctrl_td; static void d_func(void *arg, int pending) { d_entered = 1; while (!d_release) tsleep(&d_release, 0, "dfn", 1); } static void tqdead_ctrl(void *arg) { int error; dtq = taskqueue_create("tqdead", M_WAITOK, taskqueue_thread_enqueue, &dtq); error = taskqueue_start_threads(&dtq, 1, TDPRI_KERN_DAEMON, -1, "tqdead"); kprintf("tqdead: queue %p started (%d)\n", dtq, error); TASK_INIT(&dt, 0, d_func, NULL); taskqueue_enqueue(dtq, &dt); while (!d_entered) tsleep(&d_entered, 0, "dw1", 1); /* * Worker dequeued dt (ta_pending == 0) and is now INSIDE ta_func, * holding no lock and not on any sleep queue for ident `tq`. */ kprintf("tqdead: task in-flight; calling taskqueue_free()...\n"); taskqueue_free(dtq); /* NOT REACHED when the bug reproduces */ d_free_returned = 1; kprintf("tqdead: taskqueue_free RETURNED (not reproduced)\n"); wakeup(&d_free_returned); kthread_exit(); } static void tqdead_releaser(void *arg) { int i; /* give free() time to park in taskqueue_terminate */ for (i = 0; i < 10; i++) { if (d_free_returned) break; tsleep(&d_free_returned, 0, "drel1", hz / 2); } kprintf("tqdead: releasing in-flight task func\n"); d_release = 1; /* worker func returns; worker parks in TQ_SLEEP(tq, tq) "tqthr" * without re-checking TQ_FLAGS_ACTIVE -> sleeps forever. */ for (i = 0; i < 10; i++) { if (d_free_returned) break; tsleep(&d_free_returned, 0, "drel2", hz); } if (d_free_returned) { kprintf("tqdead: RESULT: free returned - NOT REPRODUCED\n"); } else { kprintf("tqdead: RESULT: DEADLOCK CONFIRMED - " "taskqueue_free() stuck for 10+ seconds; ctrl_td=%p " "(wchan %p), worker parked forever\n", ctrl_td, ctrl_td->td_wchan); } /* leave the wedged threads for ps(1) inspection */ kthread_exit(); } static int tqdead_ev(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kthread_create(tqdead_ctrl, NULL, &ctrl_td, "tqdeadctrl"); kthread_create(tqdead_releaser, NULL, NULL, "tqdeadrel"); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } DEV_MODULE(tqdead, tqdead_ev, NULL); |