DF-3738 / emx_callout_replica_fixed.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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | /* * emx_callout_replica.c - deterministic replica of if_emx.c's callout * protocol. (Pass-2 twin of DF-3715's callout_replica.c for em(4).) * * if_emx.c arms two MP-safe callouts (callout_init_mp at :553 and :587): * * emx_timer() if_emx.c:1908-1931 lwkt_serialize_enter( * &sc->main_serialize) * BLOCKING at :1914 * + self re-arm at :1928 * (callout_reset(&sc->timer, hz, * emx_timer, sc) INSIDE the * serializer, before exit :1930) * emx_txgc_timer() if_emx.c:426-449 lwkt_serialize_TRY( * &tdata->tx_serialize) * non-blocking at :436 * + unconditional re-arm at :448 * (the `done:` fall-through) * * emx_stop() tears the timer down with callout_stop(&sc->timer) at :2030 * while holding ALL serializers (ASSERT_IFNET_SERIALIZED_ALL at :2026; * callers: SIOCSIFFLAGS :1227, emx_init :1354, emx_detach :989, * emx_suspend :1059). The same callout_stop-under-serializer exists in * emx_intr_body() LSC path :1534 (ISR holds main_serialize, then * emx_serialize_skipmain adds tx+rx) and emx_npoll_status() :4196. * * DragonFly callout_stop() is SYNCHRONOUS (kern_timeout.c:1091 -> * _callout_cancel_or_stop sync=1; ssleep "costp" at :920) - it waits for * an in-progress callback to finish. lwkt_serialize_enter() BLOCKS * (tsleep "slize", kern/lwkt_serialize.c:234). A callback dequeued and * parked on the serializer therefore deadlocks against the stopper * (AB-BA): the stopper sleeps in callout_stop holding the serializer; * the softclock thread of that CPU is parked inside the callback. * * This module reproduces exactly that protocol with two callouts: * "blocking timer" == emx_timer shape * "try gc timer" == emx_txgc_timer shape * and exposes sysctls to run the two interleavings: * * kern.emxreplica.arm =1 arm both replicas * kern.emxreplica.stop_gc =1 hold serializer, wait for gc handler to be * INPROG, callout_stop() -> must return; * reports callout_pending() (expected 0: * STOP post-processing unloads the racing * re-arm for the try-based handler) * kern.emxreplica.stop_blk=1 hold serializer, wait until blocking * handler is BLOCKED on it, then * callout_stop() -> if the AB-BA analysis is * right this NEVER returns (the writing * thread AND a softclock thread wedge * permanently - emx_stop shape). */ #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; /* == sc->main_serialize replica */ static struct callout blk_c; /* == sc->timer (emx_timer) replica */ static struct callout gc_c; /* == tdata->tx_gc_timer replica */ static volatile int blk_fired, blk_ran; static volatile int gc_fired, gc_ran; static volatile int fx_fired, fx_ran; static struct callout fx_c; /* FIXED emx_timer shape (fix.diff) */ static void blk_timer(void *arg) { blk_fired++; /* if_emx.c:1914 - BLOCKING acquire of main_serialize */ lwkt_serialize_enter(&ser); blk_ran++; /* if_emx.c:1928 - self re-arm INSIDE the serializer */ callout_reset(&blk_c, hz / 4, blk_timer, NULL); /* if_emx.c:1930 - exit */ lwkt_serialize_exit(&ser); } static void gc_timer(void *arg) { gc_fired++; /* if_emx.c:436 - non-blocking try acquire of tx_serialize */ if (lwkt_serialize_try(&ser)) { gc_ran++; lwkt_serialize_exit(&ser); } /* if_emx.c:447-448 - `done:` unconditional re-arm */ callout_reset(&gc_c, 1, gc_timer, NULL); } static void fx_timer(void *arg) { /* fix.diff shape: flags check, then non-blocking try + 1-tick retry */ fx_fired++; if (!lwkt_serialize_try(&ser)) { callout_reset(&fx_c, 1, fx_timer, NULL); /* retry, never block */ return; } fx_ran++; lwkt_serialize_exit(&ser); callout_reset(&fx_c, hz / 4, fx_timer, 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) { blk_fired = blk_ran = gc_fired = gc_ran = 0; fx_fired = fx_ran = 0; callout_reset(&fx_c, 1, fx_timer, NULL); callout_reset(&gc_c, 1, gc_timer, NULL); callout_reset(&blk_c, 1, blk_timer, NULL); kprintf("emxreplica: armed (gc every tick, blk every hz/4)\n"); } return 0; } static int stop_gc_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; /* * Hold the serializer (as emx_stop does), wait for a gc callback to * be INPROG (fired but not yet re-armed), then callout_stop(). The * gc handler never blocks, so callout_stop's sync wait must * complete. Negative control for the AB-BA test. */ for (tries = 0; tries < 100; tries++) { lwkt_serialize_enter(&ser); while (gc_fired == gc_ran && /* handler in try/re-arm */ callout_pending(&gc_c) == 0) tsleep(&gc_fired, 0, "gcw", 1); kprintf("emxreplica[gc]: stop while INPROG (fired=%d ran=%d)\n", gc_fired, gc_ran); callout_stop(&gc_c); kprintf("emxreplica[gc]: callout_stop returned, pending=%d\n", callout_pending(&gc_c)); lwkt_serialize_exit(&ser); if (callout_pending(&gc_c) == 0) break; /* stopped for good */ tsleep(&gc_ran, 0, "gcr", hz / 10); } kprintf("emxreplica[gc]: RESULT pending=%d (0 == re-arm did NOT " "survive callout_stop for the try-based emx_txgc_timer shape)\n", callout_pending(&gc_c)); return 0; } static int stop_blk_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 (emx_stop shape): take the serializer, wait until the * blocking handler is parked inside lwkt_serialize_enter (fired but * not ran), then callout_stop(). * * callout_stop (kern_timeout.c:1091 -> _callout_cancel_or_stop * sync=1) sleeps on "costp" until the INPROG callback completes; * the callback sleeps in lwkt_serialize_enter until we release the * serializer; we sleep in callout_stop holding it. If the analysis * is right this never prints the next line - the writing thread AND * a softclock thread are wedged permanently. */ lwkt_serialize_enter(&ser); while (blk_ran == blk_fired) tsleep(&blk_ran, 0, "blkw", 2); kprintf("emxreplica[blk]: handler BLOCKED on our serializer " "(fired=%d ran=%d) - entering callout_stop() [emx_stop shape]\n", blk_fired, blk_ran); kprintf("emxreplica[blk]: IF THIS IS THE LAST emxreplica LINE, " "AB-BA DEADLOCK IS PROVEN\n"); callout_stop(&blk_c); kprintf("emxreplica[blk]: callout_stop returned (NO deadlock)\n"); lwkt_serialize_exit(&ser); return 0; } static int stop_fixed_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; /* * emx_stop shape against the FIXED emx_timer (try + 1-tick retry): * hold the serializer, wait until the fixed handler has fired and * (because we hold the serializer) taken its retry path, then * callout_stop(). The handler never blocks, so the stop must * complete and unload the racing re-arm. */ for (tries = 0; tries < 100; tries++) { lwkt_serialize_enter(&ser); while (fx_fired == fx_ran && callout_pending(&fx_c) == 0) tsleep(&fx_fired, 0, "fxw", 1); kprintf("emxreplica[fx]: stop while INPROG/parked (fired=%d ran=%d)\n", fx_fired, fx_ran); callout_stop(&fx_c); kprintf("emxreplica[fx]: callout_stop returned, pending=%d\n", callout_pending(&fx_c)); lwkt_serialize_exit(&ser); if (callout_pending(&fx_c) == 0) break; tsleep(&fx_ran, 0, "fxr", hz / 10); } kprintf("emxreplica[fx]: RESULT pending=%d (fix effective: " "emx_stop-shaped stop completes against fixed shape)\n", callout_pending(&fx_c)); return 0; } static SYSCTL_NODE(_kern, OID_AUTO, emxreplica, CTLFLAG_RW, 0, "emx callout replica"); SYSCTL_PROC(_kern_emxreplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0, arm_sysctl, "I", "arm replica callouts"); SYSCTL_PROC(_kern_emxreplica, OID_AUTO, stop_gc, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_gc_sysctl, "I", "emx_stop vs try-based gc timer"); SYSCTL_PROC(_kern_emxreplica, OID_AUTO, stop_blk, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_blk_sysctl, "I", "emx_stop vs blocking timer (AB-BA)"); SYSCTL_PROC(_kern_emxreplica, OID_AUTO, stop_fixed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_fixed_sysctl, "I", "emx_stop vs FIXED (try-based) emx_timer"); static int emx_callout_replica_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&ser); callout_init_mp(&gc_c); callout_init_mp(&blk_c); callout_init_mp(&fx_c); kprintf("emx_callout_replica: loaded (hz=%d)\n", hz); return 0; case MOD_UNLOAD: /* only safe if the AB-BA test was not run to the deadlock */ callout_stop(&gc_c); callout_stop(&blk_c); callout_stop(&fx_c); return 0; } return 0; } static moduledata_t emx_callout_replica_mod = { "emx_callout_replica", emx_callout_replica_load, NULL }; DECLARE_MODULE(emx_callout_replica, emx_callout_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |