DF-4310 / ste_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 | /* * ste_callout_replica.c - deterministic replica of if_ste.c's callout protocol. * * if_ste.c (DragonFly) arms ONE NON-MP callout: * * callout_init(&sc->ste_stat_timer); if_ste.c:914 * callout_reset(&sc->ste_stat_timer, hz, * ste_stats_update, sc); if_ste.c:1220 * * ste_stats_update() if_ste.c:780-813: * mii = device_get_softc(sc->ste_miibus); :788 (outside ser) * lwkt_serialize_enter(ifp->if_serializer); :790 (BLOCKING, 1st action) * ...stats reads / mii_pollstat / link work... :792-809 * callout_reset(&sc->ste_stat_timer, hz, * ste_stats_update, sc); :811 (re-arm INSIDE ser) * lwkt_serialize_exit(ifp->if_serializer); :812 * * ste_stop() if_ste.c:1224-1232: * callout_stop(&sc->ste_stat_timer); :1231 (SYNC stop) * ...free all RX/TX mbufs, bzero rings... :1248-1262 * * ste_stop is reached WITH if_serializer held from: * ste_detach (:997-998), ste_init (:1130; itself reached from * ste_ioctl SIOCSIFFLAGS :1322/:1326, ste_intr HOSTERR :617, * ste_txeoc :723/:736, ste_watchdog :1494), and ste_intr's * STATS_OFLOW path calls callout_stop directly under the serializer * from interrupt-thread context (:607-610). ste_watchdog itself runs * serialized on netisr0 (sys/net/if.c if_slowtimo_dispatch -> * ifnet_tryserialize_all). * * DragonFly callout_stop() synchronously waits for an in-progress callback * (sys/kern/kern_timeout.c:1091 -> :859 _callout_cancel_or_stop sync=1, * ssleep(...,"costp",0) at :920). ste_stats_update is a NON-MP callout, so * its callback runs holding the MP lock (kern_timeout.c:580-585) and its * FIRST action is the blocking lwkt_serialize_enter (:790). A dequeued * callback parked on the serializer held by the ste_stop caller therefore * deadlocks against it (AB-BA): stopper sleeps forever in "costp" holding * if_serializer; the softclock thread sleeps forever on the serializer * WITH THE MP LOCK HELD -> system-wide legacy-path stall. * * Secondary outcome (same root cause): if the STOP lands while the tick is * INPROG *inside* the serializer body, the tick's own callout_reset (:811) * is processed recursively and overrides the STOP (kern_timeout.c RESET * beats STOP), so ste_stop returns with the timer re-armed; at detach the * resurrected tick dereferences the freed softc/miibus/ste_ldata one * second later (use-after-free). * * This module reproduces the protocol with two callouts: * "tick timer" == ste_stats_update shape (non-mp callout, BLOCKING * enter, hz re-arm inside the serializer) * "fixed timer" == the family-fix shape (try-enter, re-arm outside) -- * positive control: callout_stop under a held * serializer returns for this shape. * * sysctls: * kern.sterplica.arm=1 arm both replicas (tick fires in 1 tick, * then self re-arms at hz like if_ste.c:811) * kern.sterplica.stop_fixed=1 hold serializer, callout_stop(fixed) -> returns * kern.sterplica.stop_blk=1 hold serializer, wait until the tick handler * is BLOCKED on it, callout_stop(tick) -> * if the AB-BA analysis is right this NEVER * returns (wedges this thread + a softclock * thread; guest must be reset afterwards) */ #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; static struct callout tick_c; /* ste_stats_update replica */ static struct callout fixed_c; /* fixed (try-enter) shape */ static volatile int tick_fired, tick_ran; static volatile int fixed_fired, fixed_ran; static void tick_timer(void *arg) { tick_fired++; /* if_ste.c:790 - BLOCKING acquire, first action */ lwkt_serialize_enter(&ser); /* if_ste.c:792-809 stat work elided */ tick_ran++; /* if_ste.c:811 - hz re-arm INSIDE the serializer */ callout_reset(&tick_c, hz, tick_timer, NULL); /* if_ste.c:812 */ lwkt_serialize_exit(&ser); } static void fixed_timer(void *arg) { fixed_fired++; /* family fix: never block on the serializer */ if (lwkt_serialize_try(&ser)) { fixed_ran++; lwkt_serialize_exit(&ser); } /* re-arm unconditionally, outside the serializer */ callout_reset(&fixed_c, 1, fixed_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) { tick_fired = tick_ran = fixed_fired = fixed_ran = 0; callout_reset(&tick_c, 1, tick_timer, NULL); callout_reset(&fixed_c, 1, fixed_timer, NULL); kprintf("sterplica: armed (tick: 1 tick then hz; fixed: 1 tick)\n"); } return 0; } static int stop_fixed_sysctl(SYSCTL_HANDLER_ARGS) { int error, v = 0, i; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return error; if (v != 1) return 0; for (i = 0; i < 20; i++) { lwkt_serialize_enter(&ser); kprintf("sterplica[fixed]: holding serializer, calling callout_stop()\n"); callout_stop(&fixed_c); kprintf("sterplica[fixed]: callout_stop RETURNED (no deadlock), " "pending=%d\n", callout_pending(&fixed_c)); lwkt_serialize_exit(&ser); if (callout_pending(&fixed_c) == 0) break; tsleep(&fixed_ran, 0, "fxr", hz / 10); } 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 (ste_stop shape, exactly what if_ste.c:1231 does from * ste_ioctl/ste_intr STATS_OFLOW/ste_watchdog/ste_detach paths): * take the serializer, wait until the ste_stats_update-shaped * handler is parked inside lwkt_serialize_enter (fired but not * ran), then callout_stop(). If the analysis is right, the next * kprintf never happens: this thread sleeps in "costp" * (kern_timeout.c:920) while the handler sleeps on the serializer * we hold -- and, being a non-MP callout, it holds the MP lock. * Both wedge permanently. */ lwkt_serialize_enter(&ser); while (tick_ran == tick_fired) tsleep(&tick_ran, 0, "stew", 2); kprintf("sterplica[blk]: ste_stats_update handler BLOCKED on our " "serializer (fired=%d ran=%d) - entering callout_stop()\n", tick_fired, tick_ran); kprintf("sterplica[blk]: IF THIS IS THE LAST sterplica LINE, " "AB-BA DEADLOCK IS PROVEN (ste_stats_update vs ste_stop)\n"); callout_stop(&tick_c); kprintf("sterplica[blk]: callout_stop returned (NO deadlock)\n"); lwkt_serialize_exit(&ser); return 0; } static SYSCTL_NODE(_kern, OID_AUTO, sterplica, CTLFLAG_RW, 0, "ste callout replica"); SYSCTL_PROC(_kern_sterplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0, arm_sysctl, "I", "arm ste replica callouts"); SYSCTL_PROC(_kern_sterplica, OID_AUTO, stop_fixed, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_fixed_sysctl, "I", "ste_stop vs try-based fixed timer"); SYSCTL_PROC(_kern_sterplica, OID_AUTO, stop_blk, CTLTYPE_INT | CTLFLAG_RW, 0, 0, stop_blk_sysctl, "I", "ste_stop vs ste_stats_update (AB-BA)"); static int ste_replica_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&ser); /* if_ste.c:914 - NON-mp callout, ste's exact flavor */ callout_init(&tick_c); callout_init(&fixed_c); kprintf("sterplica: loaded (hz=%d)\n", hz); return 0; case MOD_UNLOAD: /* only safe if stop_blk was not run (it wedges this module) */ callout_stop(&fixed_c); callout_stop(&tick_c); return 0; } return 0; } static moduledata_t ste_replica_mod = { "ste_replica", ste_replica_load, NULL }; DECLARE_MODULE(ste_replica, ste_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |