DF-4316 / lge_callout_replica.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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | /* * lge_callout_replica.c - deterministic replica of if_lge.c's callout protocol. * * if_lge.c arms ONE callout (callout_init, if_lge.c:435) whose handler takes * the driver serializer by hand: * * lge_tick() if_lge.c:978-987 lwkt_serialize_enter (BLOCKING, :984) * -> lge_tick_serialized() * -> self re-arm INSIDE the lock (:1017) * -> lwkt_serialize_exit (:986) * * lge_stop() tears it down with callout_stop() (if_lge.c:1442) while holding * that same serializer -- every in-tree caller of lge_stop() holds * ifp->if_serializer: * * lge_detach :576-580 (explicit serialize_enter around lge_stop) * lge_init :1211 (if_ioctl SIOCSIFFLAGS :1387, lge_rxeoc :933 from * lge_intr, lge_watchdog :1425 -- all serialized) * lge_intr :1029 (bus_setup_intr(..., ifp->if_serializer) :553-555) * lge_ioctl :1391 (SIOCSIFFLAGS, !IFF_UP && IFF_RUNNING) * and additionally, from interrupt-thread context: * lge_intr :1055 (PHY_INTR: callout_stop + lge_tick_serialized) * * DragonFly callout_stop() is synchronous (kern_timeout.c:1091 -> * _callout_cancel_or_stop sync=1): when the callback is INPROG the caller * ssleep("costp")s until it completes. A lge_tick dequeued while a stop-path * thread holds the serializer therefore parks in lwkt_serialize_enter and * deadlocks the stopper (AB-BA): the stopper sleeps in costp holding the * serializer, the callback spins on the serializer, the serializer is never * released, and the parked CPU's softclock thread stops delivering every * callout on that CPU. * * This module reproduces exactly that protocol with two callouts: * "lge timer" == lge_tick shape (blocking enter, re-arm inside) * "fixed timer" == the recommended-fix shape (lwkt_serialize_try + 1-tick * retry) and doubles as the negative control: its * callout_stop() must RETURN even while the stopper holds * the serializer. * * sysctls: * kern.lgereplica.arm =1 arm both replicas (lge timer period = hz, * exactly lge_init:1319 / lge_tick:1017) * kern.lgereplica.stop_fix =1 hold serializer, stop fixed-shape callout, * must return (deadlock-free control) * kern.lgereplica.stop_lge =1 hold serializer, wait until lge-shaped * handler is PARKED on it, callout_stop() * -> if the analysis is right this NEVER * returns. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/serialize.h> #include <sys/callout.h> #include <sys/sysctl.h> #include <sys/systm.h> static struct lwkt_serialize ser; /* == ifp->if_serializer */ static struct callout lge_c; /* lge_stat_timer replica */ static struct callout fix_c; /* patched-shape control */ static volatile int lge_fired, lge_ran; static volatile int fix_fired, fix_ran; static void lge_tick_replica(void *); static void lge_tick_serialized_replica(void) { /* if_lge.c:996-1017 body: stats + mii_tick + RE-ARM INSIDE the lock */ lge_ran++; callout_reset(&lge_c, hz, lge_tick_replica, NULL); } static void lge_tick_replica(void *arg) { lge_fired++; /* if_lge.c:984 - BLOCKING acquire */ lwkt_serialize_enter(&ser); lge_tick_serialized_replica(); /* if_lge.c:986 */ lwkt_serialize_exit(&ser); } static void fix_tick(void *arg) { fix_fired++; /* recommended-fix shape: never block, retry next tick */ if (lwkt_serialize_try(&ser)) { fix_ran++; callout_reset(&fix_c, hz, fix_tick, NULL); lwkt_serialize_exit(&ser); } else { callout_reset(&fix_c, 1, fix_tick, NULL); } } static int arm_sysctl(SYSCTL_HANDLER_ARGS) { int error, v = 0; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return error; if (v == 1) { lge_fired = lge_ran = fix_fired = fix_ran = 0; callout_reset(&lge_c, hz, lge_tick_replica, NULL); callout_reset(&fix_c, 1, fix_tick, NULL); kprintf("lgereplica: armed (lge timer every hz, fix timer every 1)\n"); } return 0; } static int stop_fix_sysctl(SYSCTL_HANDLER_ARGS) { int error, v = 0, tries; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return error; if (v != 1) return 0; /* negative control: the try-based (fixed) shape must stop cleanly */ for (tries = 0; tries < 100; tries++) { lwkt_serialize_enter(&ser); callout_stop(&fix_c); kprintf("lgereplica[fix]: callout_stop returned under held " "serializer, pending=%d\n", !!callout_pending(&fix_c)); lwkt_serialize_exit(&ser); if (callout_pending(&fix_c) == 0) break; tsleep(&fix_ran, 0, "fixr", hz / 10); } kprintf("lgereplica[fix]: RESULT pending=%d (0 == fixed shape does NOT " "deadlock lge_stop)\n", !!callout_pending(&fix_c)); return 0; } static int stop_lge_sysctl(SYSCTL_HANDLER_ARGS) { int error, v = 0; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return error; if (v != 1) return 0; /* * AB-BA test, lge_stop() shape (if_lge.c:1435-1442): take the * serializer (as every lge_stop caller does), wait until the * lge_tick-shaped handler is parked inside lwkt_serialize_enter * (fired but not ran), then callout_stop(). * * If the analysis is right, the kprintf after callout_stop() is * never reached: the caller sleeps forever on "costp" * (kern_timeout.c:920) while the softclock thread spins on the * serializer we hold. */ lwkt_serialize_enter(&ser); while (lge_ran == lge_fired) tsleep(&lge_ran, 0, "lgew", 2); kprintf("lgereplica[lge]: handler PARKED on our serializer " "(fired=%d ran=%d) - entering callout_stop()\n", lge_fired, lge_ran); kprintf("lgereplica[lge]: IF THIS IS THE LAST lgereplica LINE, " "THE LGE_STOP AB-BA DEADLOCK IS PROVEN\n"); callout_stop(&lge_c); kprintf("lgereplica[lge]: callout_stop returned (NO deadlock)\n"); lwkt_serialize_exit(&ser); return 0; } static SYSCTL_NODE(_kern, OID_AUTO, lgereplica, CTLFLAG_RW, 0, "lge callout replica"); SYSCTL_PROC(_kern_lgereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0, arm_sysctl, "I", "arm replica callouts"); SYSCTL_PROC(_kern_lgereplica, OID_AUTO, stop_fix, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_fix_sysctl, "I", "lge_stop vs fixed-shape timer (control)"); SYSCTL_PROC(_kern_lgereplica, OID_AUTO, stop_lge, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_lge_sysctl, "I", "lge_stop vs lge_tick (AB-BA deadlock)"); static int lge_callout_replica_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&ser); callout_init(&lge_c); callout_init(&fix_c); kprintf("lge_callout_replica: loaded (hz=%d)\n", hz); return 0; case MOD_UNLOAD: /* only safe if the deadlock test was not run to the wedge */ callout_stop(&fix_c); callout_stop(&lge_c); return 0; } return 0; } static moduledata_t lge_callout_replica_mod = { "lge_callout_replica", lge_callout_replica_load, NULL }; DECLARE_MODULE(lge_callout_replica, lge_callout_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |