DF-2788 / df2788.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 | /* * DF-2788 trigger module for sys/kern/kern_timeout.c (DragonFly 6.5-DEVELOPMENT) * * Proves the sync fast-path in _callout_cancel_or_stop() (kern_timeout.c:888-891) * returns 0 unconditionally even when THIS stop/cancel/drain call itself dequeued * a pending callout (CALLOUT_PREVENTED was set on the backend _callout at * kern_timeout.c:229/:276 and is never read on that path). Documented contract * (callout.h / kern_timeout.c:1052-1094): * * callout_stop "Returns whether the STOP operation was responsible * for removing a queued or pending callout." * callout_stop_async same wording * callout_cancel "Returns 1 if the cancel is responsible for stopping * the callout." * callout_drain "Returns 1 if the drain is responsible for stopping * the callout." * * Deterministic: no race is required. For each API we arm a callout hz ticks * out, verify callout_pending()==1, invoke the API, verify callout_pending()==0 * (proving THIS call removed it from the wheel) and record the return value. * Contract satisfied => ret == 1. * Observed (bug) => ret == 0 while pending flipped 1 -> 0. * * Control case: let the callout fire normally, then callout_stop() must * return 0 (nothing was pending) -- shows the return value is supposed to * distinguish "I removed it" from "nothing to remove", and that both states * are indistinguishable on the stock kernel. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/callout.h> static struct callout g_c; static volatile int g_fired; static int g_summary; static void df2788_cb(void *arg __unused) { g_fired = 1; wakeup(&g_fired); } static int df2788_run_sysctl(SYSCTL_HANDLER_ARGS) { int mode = 0; int error; int pend_before, ret_stop, pend_after; int pend_b, ret_async, pend_ab; int pend_c, ret_cancel, pend_ac; int pend_d, ret_drain, pend_ad; int fired, ret_ctl; error = sysctl_handle_int(oidp, &mode, 0, req); if (error || req->newptr == NULL) return (error); if (mode != 1) return (EINVAL); /* A) callout_stop() on a PENDING callout */ callout_init_mp(&g_c); callout_reset(&g_c, hz, df2788_cb, NULL); pend_before = callout_pending(&g_c); ret_stop = callout_stop(&g_c); pend_after = callout_pending(&g_c); kprintf("DF2788 A stop: pending %d -> %d, ret=%d (contract: 1)\n", pend_before, pend_after, ret_stop); /* B) callout_stop_async() on a PENDING callout */ callout_reset(&g_c, hz, df2788_cb, NULL); pend_b = callout_pending(&g_c); ret_async = callout_stop_async(&g_c); pend_ab = callout_pending(&g_c); kprintf("DF2788 B stop_async: pending %d -> %d, ret=%d (contract: 1)\n", pend_b, pend_ab, ret_async); /* C) callout_cancel() on a PENDING callout */ callout_reset(&g_c, hz, df2788_cb, NULL); pend_c = callout_pending(&g_c); ret_cancel = callout_cancel(&g_c); pend_ac = callout_pending(&g_c); kprintf("DF2788 C cancel: pending %d -> %d, ret=%d (contract: 1)\n", pend_c, pend_ac, ret_cancel); /* D) callout_drain() on a PENDING callout */ callout_reset(&g_c, hz, df2788_cb, NULL); pend_d = callout_pending(&g_c); ret_drain = callout_drain(&g_c); pend_ad = callout_pending(&g_c); kprintf("DF2788 D drain: pending %d -> %d, ret=%d (contract: 1)\n", pend_d, pend_ad, ret_drain); /* E) control: let it fire, then stop() must return 0 */ g_fired = 0; callout_reset(&g_c, 2, df2788_cb, NULL); tsleep(&g_fired, 0, "df2788w", 10 * hz); fired = g_fired; ret_ctl = callout_stop(&g_c); kprintf("DF2788 E control: fired=%d, ret=%d (contract: 0)\n", fired, ret_ctl); callout_terminate(&g_c); /* * bug = every dequeue case flipped pending 1->0 (this call removed the * callout) yet returned 0, while the control correctly returns 0 for * "nothing pending". 1 = bug reproduced, 0 = contract honored. */ g_summary = (pend_before == 1 && pend_after == 0 && ret_stop == 0 && pend_b == 1 && pend_ab == 0 && ret_async == 0 && pend_c == 1 && pend_ac == 0 && ret_cancel == 0 && pend_d == 1 && pend_ad == 0 && ret_drain == 0 && fired == 1 && ret_ctl == 0); kprintf("DF2788 RESULT: stop=%d async=%d cancel=%d drain=%d " "ctl_fired=%d ctl_ret=%d BUG=%d\n", ret_stop, ret_async, ret_cancel, ret_drain, fired, ret_ctl, g_summary); return (0); } static int df2788_sum_sysctl(SYSCTL_HANDLER_ARGS) { int v = g_summary; return (sysctl_handle_int(oidp, &v, 0, req)); } SYSCTL_PROC(_debug, OID_AUTO, df2788, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df2788_run_sysctl, "I", "DF-2788 trigger: write 1 to run"); SYSCTL_PROC(_debug, OID_AUTO, df2788_result, CTLTYPE_INT | CTLFLAG_RD, 0, 0, df2788_sum_sysctl, "I", "DF-2788 last result (1=bug reproduced)"); static int df2788_modevent(module_t mod __unused, int type, void *data __unused) { switch (type) { case MOD_LOAD: g_summary = -1; kprintf("df2788: loaded, run: sysctl -n debug.df2788=1\n"); return (0); case MOD_UNLOAD: return (0); } return (EOPNOTSUPP); } static moduledata_t df2788_mod = { "df2788", df2788_modevent, 0 }; DECLARE_MODULE(df2788, df2788_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |