DF-4109 / tlnge_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 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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | /* * tlnge_replica.c - deterministic replica of the callout/serializer protocol * used by if_tl.c and if_nge.c on this DragonFly tree. * * PROTOCOL UNDER TEST (identical shape in both drivers): * * tl_stats_update() sys/dev/netif/tl/if_tl.c:1666-1674 * - callout created with plain callout_init() (if_tl.c:1197) * - callback does lwkt_serialize_enter(if_serializer) BLOCKING * (if_tl.c:1671 -> sys/kern/lwkt_serialize.c:234 tsleep "slize") * - re-arms itself with callout_reset(..., hz, ...) INSIDE the * serializer (if_tl.c:1722) * tl_stop() sys/dev/netif/tl/if_tl.c:2121 * - callout_stop(&sc->tl_stat_timer) while the caller HOLDS the same * serializer (tl_stop runs under if_serializer from tl_ioctl * if_tl.c:2062, tl_init :1916, tl_watchdog :2103, tl_detach :1258, * tl_intvec_adchk :1571). * * nge_tick() sys/dev/netif/nge/if_nge.c:1366-1406 * - callout_init() (if_nge.c:731) * - lwkt_serialize_enter(ifp->if_serializer) BLOCKING (if_nge.c:1372) * - callout_reset(..., hz, ...) inside the serializer (if_nge.c:1403) * nge_stop() sys/dev/netif/nge/if_nge.c:2094 * - callout_stop(&sc->nge_stat_timer) under the serializer (callers: * nge_ioctl :2031, nge_init :1716, nge_watchdog :2067, nge_detach * :934, nge_intr :1491). * * DragonFly callout_stop() synchronously waits for an in-progress callback * (sys/kern/kern_timeout.c:1091 -> _callout_cancel_or_stop(sync=1), ssleep * "costp" at :920). A callback that was dequeued and is parked in * lwkt_serialize_enter on the stopper's serializer therefore deadlocks * against it (AB-BA) - the same mechanism PROVEN for em(4) as DF-3715. * * This module reproduces the protocol verbatim (plain callout_init, hz * period, blocking enter, re-arm inside the serializer) and exposes, for * each driver shape, three sysctls: * * kern.<drv>replica.arm =1 arm the replica callout * kern.<drv>replica.stop_plain =1 callout_stop() WITHOUT the serializer * (negative control - must return) * kern.<drv>replica.stop_blk =1 hold serializer, wait for the handler * to park 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> struct shape { struct lwkt_serialize ser; struct callout c; struct callout cf; /* fix-shape handler */ volatile int fired; volatile int ran; volatile int ffired, fran; /* fix-shape counters */ const char *name; }; static struct shape tl_sh = { .name = "tl" }; static struct shape nge_sh = { .name = "nge" }; /* * Exact tl_stats_update / nge_tick shape: * blocking enter -> (work) -> re-arm inside -> exit. */ static void replica_tick(void *arg) { struct shape *sh = arg; sh->fired++; /* if_tl.c:1671 / if_nge.c:1372 - BLOCKING acquire */ lwkt_serialize_enter(&sh->ser); sh->ran++; /* if_tl.c:1722 / if_nge.c:1403 - self re-arm while serialized */ callout_reset(&sh->c, hz, replica_tick, sh); lwkt_serialize_exit(&sh->ser); } static int arm_sysctl(struct shape *sh, 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) { sh->fired = sh->ran = 0; /* if_tl.c:1981 / if_nge.c:1717 */ callout_reset(&sh->c, hz, replica_tick, sh); kprintf("replica[%s]: armed (period hz=%d)\n", sh->name, hz); } return 0; } static int stop_plain_sysctl(struct shape *sh, 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; /* * Negative control: callout_stop() WITHOUT holding the serializer. * The handler can always finish (it only blocks on us), so this * must return promptly. */ kprintf("replica[%s]: stop_plain entering callout_stop()\n", sh->name); callout_stop(&sh->c); kprintf("replica[%s]: stop_plain callout_stop RETURNED pending=%d " "(negative control ok)\n", sh->name, callout_pending(&sh->c)); return 0; } static int stop_blk_sysctl(struct shape *sh, SYSCTL_HANDLER_ARGS) { int error, v = 0, spins; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return error; if (v != 1) return 0; /* * AB-BA test - exact tl_stop()/nge_stop() shape: take the serializer, * wait until the handler is parked inside lwkt_serialize_enter * (fired but not ran), then callout_stop(). * * callout_stop (kern_timeout.c:1091, sync=1) sleeps on "costp" until * the INPROG callback completes; the callback sleeps in * lwkt_serialize_enter ("slize") until we release the serializer; we * sleep in callout_stop holding it. If the analysis is right the * next kprintf NEVER executes - this sysctl thread AND a softclock * thread wedge permanently. */ lwkt_serialize_enter(&sh->ser); for (spins = 0; spins < 5 * hz && sh->ran == sh->fired; spins++) tsleep(&sh->ran, 0, "blkw", 2); if (sh->ran == sh->fired) { kprintf("replica[%s]: handler never fired/parked\n", sh->name); lwkt_serialize_exit(&sh->ser); return 0; } kprintf("replica[%s]: handler PARKED on our serializer " "(fired=%d ran=%d) - entering callout_stop()\n", sh->name, sh->fired, sh->ran); kprintf("replica[%s]: IF THIS IS THE LAST replica LINE, " "AB-BA DEADLOCK IS PROVEN\n", sh->name); callout_stop(&sh->c); kprintf("replica[%s]: callout_stop returned (NO deadlock)\n", sh->name); lwkt_serialize_exit(&sh->ser); return 0; } /* * FIX-pattern shape: handler never blocks - try-enter, on failure retry * in one tick (the em_txgc_timer / DF-3715 fix, proposed for tl_stats_ * update and nge_tick). callout_stop() under a held serializer must * return AND fully unload it despite the unconditional re-arms. */ static void replica_fix_tick(void *arg) { struct shape *sh = arg; sh->ffired++; if (lwkt_serialize_try(&sh->ser)) { sh->fran++; callout_reset(&sh->cf, hz, replica_fix_tick, sh); lwkt_serialize_exit(&sh->ser); } else { callout_reset(&sh->cf, 1, replica_fix_tick, sh); } } static int stop_fix_sysctl(struct shape *sh, SYSCTL_HANDLER_ARGS) { int error, v = 0, tries, f0; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return error; if (v != 1) return 0; sh->ffired = sh->fran = 0; callout_reset(&sh->cf, 1, replica_fix_tick, sh); for (tries = 0; tries < 100; tries++) { lwkt_serialize_enter(&sh->ser); /* wait until the handler is missing against our serializer */ while (sh->ffired == sh->fran) tsleep(&sh->ffired, 0, "fixw", 2); kprintf("replica[%s]: fix-shape missing (f=%d r=%d) - " "callout_stop under serializer\n", sh->name, sh->ffired, sh->fran); callout_stop(&sh->cf); kprintf("replica[%s]: fix-shape callout_stop RETURNED " "(no deadlock)\n", sh->name); lwkt_serialize_exit(&sh->ser); if (callout_pending(&sh->cf) == 0) break; tsleep(&sh->fran, 0, "fixr", hz / 10); } f0 = sh->ffired; tsleep(&sh->ffired, 0, "fixs", hz / 2); kprintf("replica[%s]: fix-shape RESULT pending=%d fired-stalled=%s " "(f=%d->%d r=%d)\n", sh->name, callout_pending(&sh->cf), (sh->ffired == f0 && callout_pending(&sh->cf) == 0) ? "YES" : "NO", f0, sh->ffired, sh->fran); return 0; } #define SHAPE_SYSCTLS(prefix, var, shp) \ static int \ prefix##_arm(SYSCTL_HANDLER_ARGS) \ { \ return arm_sysctl(shp, oidp, arg1, arg2, req); \ } \ static int \ prefix##_plain(SYSCTL_HANDLER_ARGS) \ { \ return stop_plain_sysctl(shp, oidp, arg1, arg2, req); \ } \ static int \ prefix##_blk(SYSCTL_HANDLER_ARGS) \ { \ return stop_blk_sysctl(shp, oidp, arg1, arg2, req); \ } \ static int \ prefix##_fix(SYSCTL_HANDLER_ARGS) \ { \ return stop_fix_sysctl(shp, oidp, arg1, arg2, req); \ } \ static SYSCTL_NODE(_kern, OID_AUTO, prefix##replica, CTLFLAG_RW, 0, \ "callout replica"); \ SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, arm, \ CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_arm, "I", "arm replica"); \ SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, stop_plain, \ CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_plain, "I", \ "stop without serializer (control)"); \ SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, stop_blk, \ CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_blk, "I", \ "stop under serializer (AB-BA)"); \ SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, stop_fix, \ CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_fix, "I", \ "try-enter fix shape: stop under serializer must return") SHAPE_SYSCTLS(tl, tl_sh, &tl_sh); SHAPE_SYSCTLS(nge, nge_sh, &nge_sh); static int tlnge_replica_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&tl_sh.ser); lwkt_serialize_init(&nge_sh.ser); /* if_tl.c:1197 / if_nge.c:731 - plain callout_init (MPSAFE) */ callout_init(&tl_sh.c); callout_init(&nge_sh.c); callout_init(&tl_sh.cf); callout_init(&nge_sh.cf); kprintf("tlnge_replica: loaded (hz=%d)\n", hz); return 0; case MOD_UNLOAD: /* only safe if the AB-BA test was not run to the wedge */ callout_stop(&tl_sh.c); callout_stop(&nge_sh.c); callout_stop(&tl_sh.cf); callout_stop(&nge_sh.cf); return 0; } return 0; } static moduledata_t tlnge_replica_mod = { "tlnge_replica", tlnge_replica_load, NULL }; DECLARE_MODULE(tlnge_replica, tlnge_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |