DF-3717 / mxge_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 230 | /* * mxge_callout_replica.c - deterministic replica of if_mxge.c's * callout/serializer teardown protocol (DF-3717). * * mxge protocol under test (sys/dev/netif/mxge/if_mxge.c): * * mxge_tick() :3712-3750 self-re-arming MP callout (co_hdl, armed * :4445 callout_reset_bycpu); callback's FIRST * act is a BLOCKING lwkt_serialize_enter of * sc->main_serialize (:3720), re-arm via * callout_reset INSIDE the serializer (:3747). * * mxge_detach() :4463-4474 ifnet_serialize_all(ifp) (takes main_serialize * first, :4463) then callout_stop(&sc->co_hdl) * (:4468) - a SYNC stop while holding the very * serializer the parked callback needs. * (callout_terminate at :4474 is outside the * serializers - the right pattern - but the * deadlock happens first at :4468.) * * DragonFly callout_stop() is synchronous: kern_timeout.c:1091 -> * _callout_cancel_or_stop(..., sync=1) ssleep's on "costp" (:920) until an * INPROG callback completes; a parked mxge_tick replica sleeps inside * lwkt_serialize_enter (lwkt_serialize.c:86 atomic_intr_cond_enter -> * lwkt_serialize_sleep) until the stopper releases the serializer. * AB-BA: permanent wedge of the detaching thread AND the softclock thread * of the callback's CPU (all callouts on that CPU stop firing). * * Two INDEPENDENT callouts (mxge never re-arms after terminate; neither do * we) and three sysctls: * kern.mxgereplica.armfix =1 arm the fix-test callout * kern.mxgereplica.fix =1 mxge_detach WITH the recommended fix: * callout_stop_async() under the serializer + * callout_terminate() after release -> must * return, pending must be 0 * kern.mxgereplica.stop =1 mxge_detach EXACTLY as if_mxge.c:4463-4468: * sync callout_stop() under the serializer -> * 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; /* == sc->main_serialize */ static struct callout fix_c; /* fix-test instance of co_hdl */ static struct callout stop_c; /* AB-BA-test instance of co_hdl */ static volatile int fix_started, fix_ran; static volatile int stop_started, stop_ran; /* mxge_tick replica (if_mxge.c:3712-3750) */ static void tick_fix(void *arg) { fix_started++; /* callback entered */ /* if_mxge.c:3720 - BLOCKING acquire, first act of the callback */ lwkt_serialize_enter(&ser); fix_ran++; /* if_mxge.c:3747 - self re-arm INSIDE the serializer */ callout_reset(&fix_c, hz / 4, tick_fix, NULL); lwkt_serialize_exit(&ser); } static void tick_stop(void *arg) { stop_started++; /* if_mxge.c:3720 - BLOCKING acquire */ lwkt_serialize_enter(&ser); stop_ran++; /* if_mxge.c:3747 - self re-arm INSIDE the serializer */ callout_reset(&stop_c, hz / 4, tick_stop, NULL); lwkt_serialize_exit(&ser); } static int armfix_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) { fix_started = fix_ran = 0; callout_reset(&fix_c, 1, tick_fix, NULL); kprintf("mxge_replica: armed FIX-test tick (hz/4)\n"); } return 0; } static int armstop_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) { stop_started = stop_ran = 0; callout_reset(&stop_c, 1, tick_stop, NULL); kprintf("mxge_replica: armed STOP-test tick (hz/4)\n"); } return 0; } /* * Wait until a callback is INPROG and parked on our serializer. * * NB: deliberately a BUSY-WAIT. A tsleep()-timeout based poll would rely * on the callout subsystem itself (_callout_setup_quick) - but once the * tick callback parks the softclock thread of the test CPU ('slize'), * tsleep timeouts serviced by that same softclock never fire and the * observer itself freezes (observed live: softclock N in B2 'slize' while * the sysctl waiter sat in D2 'mxgw' forever). */ static void wait_parked(volatile int *ran, volatile int *started) { long spins = 0; while (*ran == *started) { cpu_ccfence(); if (++spins > 500000000L) { kprintf("mxge_replica: wait_parked TIMEOUT " "(started=%d ran=%d)\n", *started, *ran); break; } } } static int fix_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; /* mxge_detach WITH the fix: stop_async under, terminate outside */ lwkt_serialize_enter(&ser); /* :4463 */ wait_parked(&fix_ran, &fix_started); kprintf("mxge_replica[fix]: tick parked (started=%d ran=%d); " "callout_stop_async + release + callout_terminate\n", fix_started, fix_ran); callout_stop_async(&fix_c); /* fixed :4468 */ lwkt_serialize_exit(&ser); /* release */ callout_terminate(&fix_c); /* :4474 */ kprintf("mxge_replica[fix]: returned; pending=%d ran=%d " "(0 == cleanly stopped, NO deadlock)\n", callout_pending(&fix_c), fix_ran); return 0; } static int stop_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; /* mxge_detach EXACTLY as shipped (if_mxge.c:4463-4468) */ lwkt_serialize_enter(&ser); /* :4463 */ wait_parked(&stop_ran, &stop_started); kprintf("mxge_replica[stop]: tick BLOCKED on our serializer " "(started=%d ran=%d) - entering callout_stop() :4468\n", stop_started, stop_ran); kprintf("mxge_replica[stop]: IF THIS IS THE LAST mxge_replica LINE, " "THE mxge_detach AB-BA DEADLOCK IS PROVEN\n"); callout_stop(&stop_c); /* :4468 - sync */ kprintf("mxge_replica[stop]: callout_stop returned (NO deadlock)\n"); lwkt_serialize_exit(&ser); return 0; } static SYSCTL_NODE(_kern, OID_AUTO, mxgereplica, CTLFLAG_RW, 0, "mxge callout teardown replica"); SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, armfix, CTLTYPE_INT | CTLFLAG_RW, 0, 0, armfix_sysctl, "I", "arm fix-test callout"); SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, fix, CTLTYPE_INT | CTLFLAG_RW, 0, 0, fix_sysctl, "I", "detach shape WITH recommended fix"); SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0, armstop_sysctl, "I", "arm AB-BA-test callout"); SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_sysctl, "I", "mxge_detach shape as shipped (AB-BA)"); static int mxge_replica_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&ser); callout_init_mp(&fix_c); callout_init_mp(&stop_c); kprintf("mxge_callout_replica: loaded (hz=%d)\n", hz); return 0; case MOD_UNLOAD: callout_stop_async(&fix_c); callout_terminate(&fix_c); callout_stop_async(&stop_c); callout_terminate(&stop_c); return 0; } return 0; } static moduledata_t mxge_replica_mod = { "mxge_callout_replica", mxge_replica_load, NULL }; DECLARE_MODULE(mxge_callout_replica, mxge_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |