Exercise 3: A Buggy Handler's Own Exception Still Surfaces — Possible Solution ==================================================================== THE TEST ------------------------------ table = InterruptTable() def broken_handler(): raise ValueError("the handler itself has a bug") table.register(99, broken_handler) try: table.dispatch(99) handler_bug_hidden = True except ValueError as e: handler_bug_hidden = False print(e) RESULT ------------------------------ a bug INSIDE a registered handler still propagates out of dispatch(): the handler itself has a bug handler_bug_hidden is False -- the ValueError raised inside broken_handler() is not caught or suppressed anywhere inside dispatch(); it propagates straight out to the caller, exactly as if broken_handler() had been called directly. WHY dispatch() DOESN'T CATCH THIS ------------------------------ dispatch()'s own implementation is: def dispatch(self, number, *args, **kwargs): if number not in self.handlers: raise RuntimeError(f"no handler registered for interrupt {number}") return self.handlers[number](*args, **kwargs) There is no try/except anywhere around the actual call to self.handlers[number](...). The only error condition dispatch() itself checks for is "does this number have ANY handler registered at all" -- once that check passes, dispatch() simply calls the handler and returns whatever it returns (or lets whatever it raises propagate upward unchanged). Whatever happens inside the handler's own code is entirely the handler's own responsibility. WHY THIS IS A GENUINELY DIFFERENT CASE FROM FINDING 1 ------------------------------ Finding 1's own RuntimeError ("no handler registered for interrupt 32") is raised BY dispatch() ITSELF, before any handler is ever reached -- it's dispatch() reporting on its own table's own contents. This exercise's ValueError is raised BY THE HANDLER, entirely outside dispatch()'s own code, for a completely unrelated reason (a genuine bug in whatever that handler does). dispatch() correctly treats these as two different kinds of failure: "I don't know what to do with this interrupt number" (dispatch()'s own job to catch) versus "the code registered to handle this interrupt is itself broken" (not dispatch()'s job to catch, hide, or paper over). WHY THIS IS THE RIGHT BEHAVIOR, NOT A GAP ------------------------------ If dispatch() silently swallowed every exception a handler raised, a genuinely broken interrupt handler -- one with a real bug -- would fail completely silently, with the kernel proceeding as if the interrupt had been handled correctly when it hadn't been at all. A real, visible crash from a broken handler is far safer and far more debuggable than a fault that gets quietly absorbed and never reported anywhere. WHY THIS WORKS AS AN ANSWER ------------------------------ Deliberately registering a handler that's broken on purpose, rather than one that's simply missing, isolates exactly which failure mode dispatch() is designed to guard against (an unregistered number) and confirms it makes no attempt to guard against the other, genuinely different failure mode (a broken handler) -- a distinction worth understanding precisely rather than assuming "dispatch() handles errors" as one single, undifferentiated behavior.