DF-3806 / msk_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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | /* * msk_callout_replica.c - deterministic replica of if_msk.c's tick protocol. * * if_msk.c arms ONE NON-MPSAFE callout (callout_init, if_msk.c:1375): * * msk_tick() if_msk.c:2930-2946 * lwkt_serialize_enter(ifp->if_serializer) BLOCKING (:2936) * mii_tick(mii) (:2940) / msk_miibus_statchg (:2941-2942) * callout_reset(&sc_if->msk_tick_ch, hz, msk_tick, sc_if) (:2943) * lwkt_serialize_exit (:2945) * * msk_stop() if_msk.c:3675-3823 (ASSERT_SERIALIZED :3687) * callout_stop(&sc_if->msk_tick_ch) (:3689) * * msk_stop() is called with the serializer held from: * msk_init (:3352; reached from msk_ioctl SIOCSIFFLAGS up/down * :880-881/:895, watchdog :2663/:2686), msk_detach (:1762-1764), * mskc_shutdown (:2699-2701), mskc_suspend (:2719-2722), * mskc_resume -> msk_init (:2756). * * DragonFly callout_stop() is the SYNCHRONOUS variant * (sys/kern/kern_timeout.c:1091 -> _callout_cancel_or_stop(..., sync=1)); * when the callback is INPROG it ssleep()s on "costp" (kern_timeout.c:923) * until the callback completes. If msk_tick is INPROG and parked inside * lwkt_serialize_enter on the serializer held by the msk_stop() caller, * neither thread can make progress: PERMANENT AB-BA DEADLOCK. * (msk's callout is NON-MPSAFE, so the parked softclock callback also * holds the MP lock while it waits - extra system-wide degradation.) * * This module reproduces exactly that protocol with two callouts: * "msk tick (as shipped)" - blocking acquire + self re-arm at hz * "msk tick (fix.diff shape)" - lwkt_serialize_try + 1-tick retry * and exposes sysctls to run the interleavings: * * kern.mskrep.arm =1 arm both replicas * kern.mskrep.stop_try =1 hold serializer, callout_stop() the * try-based (fixed) shape -> must return, * pending=0 (validates the fix mechanism) * kern.mskrep.stop_blk =1 hold serializer, wait for the shipped-shape * handler to be BLOCKED on it, callout_stop() * -> if the analysis is right this NEVER * returns (sysctl thread D-state wchan "costp"). */ #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> #include <sys/thread.h> static struct lwkt_serialize ser; /* == ifp->if_serializer */ static struct callout tick_blk_c; /* msk_tick as shipped */ static struct callout tick_try_c; /* msk_tick with fix.diff shape */ static volatile int blk_fired, blk_ran; static volatile int try_fired, try_ran; /* if_msk.c:2930-2946 - msk_tick() EXACT protocol (blocking) */ static void msk_tick_blk(void *arg) { blk_fired++; /* if_msk.c:2936 - BLOCKING acquire */ lwkt_serialize_enter(&ser); blk_ran++; /* mii_tick/statchg live here (:2940-2942) */ /* if_msk.c:2943 - self re-arm at hz */ callout_reset(&tick_blk_c, hz, msk_tick_blk, NULL); lwkt_serialize_exit(&ser); } /* msk_tick() with the proposed fix: try-enter, 1-tick retry */ static void msk_tick_try(void *arg) { try_fired++; if (lwkt_serialize_try(&ser)) { try_ran++; callout_reset(&tick_try_c, hz, msk_tick_try, NULL); lwkt_serialize_exit(&ser); } else { /* serializer held (e.g. by msk_stop) - retry next tick */ callout_reset(&tick_try_c, 1, msk_tick_try, 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 = try_fired = try_ran = 0; callout_reset(&tick_try_c, 1, msk_tick_try, NULL); callout_reset(&tick_blk_c, 1, msk_tick_blk, NULL); kprintf("mskrep: armed (hz=%d)\n", hz); } return 0; } /* * Fix-shape test: msk_stop() vs the try-based msk_tick. * callout_stop() must return (handler never blocks) and unload the * racing re-arm. Validates the DF-3806 fix.diff mechanism. */ static int stop_try_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; for (tries = 0; tries < 100; tries++) { lwkt_serialize_enter(&ser); /* as msk_stop :3687 */ if (try_fired == try_ran && callout_pending(&tick_try_c) == 0) tsleep(&try_fired, 0, "tryw", 1); kprintf("mskrep[try]: stop (fired=%d ran=%d)\n", try_fired, try_ran); callout_stop(&tick_try_c); /* :3689 */ kprintf("mskrep[try]: callout_stop returned, " "pending=%d\n", callout_pending(&tick_try_c)); lwkt_serialize_exit(&ser); if (callout_pending(&tick_try_c) == 0) break; tsleep(&try_ran, 0, "tryr", hz / 10); } kprintf("mskrep[try]: RESULT pending=%d " "(0 == try-shape fully stopped, no deadlock)\n", callout_pending(&tick_try_c)); return 0; } /* * Shipped-shape test: msk_stop() vs the blocking msk_tick. * Take the serializer (as msk_stop does), wait until the handler is * parked inside lwkt_serialize_enter (fired but not ran), then * callout_stop(). If the AB-BA analysis is right this never prints * the next line - the sysctl thread AND a softclock thread are wedged * permanently (D-state, wchan "costp" == kern_timeout.c:923), and the * parked non-MPSAFE callback additionally holds the MP lock. */ static int stop_blk_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; lwkt_serialize_enter(&ser); /* as msk_stop :3687 */ /* Wait until the handler is parked inside lwkt_serialize_enter * (fired but not ran). Spin+yield against 'ticks' (advanced by * hardclock, NOT softclock), NOT tsleep(): a sleeping timeout * would itself be delivered by a softclock thread, and the * parked NON-MPSAFE callback takes out both the softclock * thread and the MP lock of the CPU it parks on (observed on * the first run: sysctl stuck D1 'blkw', softclock B1 'slize'). * Wait up to 10 s - the tick self re-arms at hz (1 s period). */ { int start = ticks; long spin = 0; while (blk_ran == blk_fired && ticks - start < 10 * hz) { if ((++spin & 0xffff) == 0) lwkt_user_yield(); } tries = ticks - start; } kprintf("mskrep[blk]: msk_tick handler BLOCKED on our " "serializer (fired=%d ran=%d waited=%d) - entering " "callout_stop()\n", blk_fired, blk_ran, tries); kprintf("mskrep[blk]: IF THIS IS THE LAST mskrep LINE, " "THE if_msk.c AB-BA DEADLOCK IS PROVEN\n"); callout_stop(&tick_blk_c); /* :3689 */ kprintf("mskrep[blk]: callout_stop returned (NO deadlock)\n"); lwkt_serialize_exit(&ser); return 0; } static SYSCTL_NODE(_kern, OID_AUTO, mskrep, CTLFLAG_RW, 0, "msk callout replica"); SYSCTL_PROC(_kern_mskrep, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0, arm_sysctl, "I", "arm replica callouts"); SYSCTL_PROC(_kern_mskrep, OID_AUTO, stop_try, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_try_sysctl, "I", "msk_stop vs try-based (fixed) msk_tick"); SYSCTL_PROC(_kern_mskrep, OID_AUTO, stop_blk, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_blk_sysctl, "I", "msk_stop vs shipped (blocking) msk_tick"); static int msk_replica_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&ser); /* if_msk.c:1375 - callout_init(&sc_if->msk_tick_ch): * NON-MPSAFE, exactly as the driver does it. */ callout_init(&tick_try_c); callout_init(&tick_blk_c); kprintf("msk_callout_replica: loaded (hz=%d ncpus=%d)\n", hz, ncpus); return 0; case MOD_UNLOAD: /* only safe if the AB-BA test was not run */ callout_stop(&tick_try_c); callout_stop(&tick_blk_c); return 0; } return 0; } static moduledata_t msk_replica_mod = { "msk_callout_replica", msk_replica_load, NULL }; DECLARE_MODULE(msk_callout_replica, msk_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |