Exercise 3: A Crashing Program Shouldn't Take Down the Others — Possible Solution ==================================================================== THE TEST ------------------------------ def program_crashes(): yield 0 yield 1 raise RuntimeError("simulated program crash") yield 2 # never reached def program_survivor(): for i in range(3): yield i crash_gen, survivor_gen = program_crashes(), program_survivor() crash_output, survivor_output = [], [] for _ in range(3): try: crash_output.append(next(crash_gen)) except StopIteration: pass except RuntimeError as e: crash_output.append(f"CRASHED: {e}") survivor_output.append(next(survivor_gen)) RESULT ------------------------------ crashing program's own results: [0, 1, 'CRASHED: simulated program crash'] survivor program's own results: [0, 1, 2] The crashing program's own results correctly show its first two real steps, then the caught crash. The survivor program's own results show all three of its own steps completed normally, with no sign anything went wrong anywhere else in the system. WHY CATCHING THE EXCEPTION LOCALLY IS WHAT MAKES THIS WORK ------------------------------ The try/except sits directly around the single next(crash_gen) call, INSIDE the same loop iteration that also calls next(survivor_gen). When program_crashes() raises RuntimeError on its third call, that exception is caught and handled immediately, right where it happens -- the loop itself keeps running, and the very next line, survivor_output.append(next(survivor_gen)), executes completely normally in the same iteration. Nothing about the crash reaches or affects the survivor's own generator object at all -- they are two completely independent Python objects with no shared state. WHAT WOULD HAPPEN WITHOUT THIS ISOLATION ------------------------------ If the try/except were removed -- or worse, wrapped around the WHOLE loop instead of just the one call -- RuntimeError raised on crash_gen's third call would propagate up and terminate the entire for loop immediately. survivor_gen's own third next() call would never happen at all, even though survivor_gen itself never did anything wrong. A single failing program would have taken down every other program sharing the same interleaving loop, purely because of where the exception was allowed to escape to -- not because of anything the other programs themselves did. WHY THIS WORKS AS AN ANSWER ------------------------------ Deliberately triggering a real exception inside one of two interleaved programs, and confirming precisely how much containment a correctly-placed try/except provides (complete: the survivor finishes all three of its own steps untouched) versus how much damage an incorrectly-placed one would cause (total: the whole loop, and every program in it, would die with the first one), demonstrates concretely why Chapter 4's own Process Control Block and Chapter 6's own syscall boundary exist -- to make this kind of per-process containment automatic and structural, rather than something every piece of scheduling code has to remember to add correctly by hand, every single time.