Exercise 2: A BLOCKED Process Still Can't Be Handed a Preempted Turn — Possible Solution ==================================================================== THE TEST ------------------------------ running_proc = kernel.create_process(num_pages=1) blocked_proc = kernel.create_process(num_pages=1) running_proc.transition(ProcessState.RUNNING) blocked_proc.transition(ProcessState.RUNNING) blocked_proc.transition(ProcessState.BLOCKED) # never added to scheduler fpk = FixedPreemptiveKernel(cpu, scheduler) fpk.current_pcb = running_proc for _ in range(10): fpk.run_instruction() RESULT ------------------------------ BLOCKED process's own state after 10 preemptible instructions: ProcessState.BLOCKED running process ever switched away from (nothing else was READY): False blocked_proc's state never changes, and running_proc is never switched away from -- even though 10 instructions is enough for two full timer interrupts to fire (QUANTUM=5). WHY THE BLOCKED PROCESS IS NEVER TOUCHED ------------------------------ blocked_proc was never passed to scheduler.add() at all -- it exists in the kernel's own process table, but scheduler.ready_queue has no idea it exists. When the timer fires, _on_timer() calls self.scheduler.pick_next(), which can only ever return something that was actually appended to ready_queue. There is no code path by which pick_next() could return a process it was never given. WHICH GUARD IS ACTUALLY RESPONSIBLE ------------------------------ Trying to add it directly proves the deeper guard is still there too: scheduler.add(blocked_proc) # raises: cannot schedule PID 16: not READY (state=BLOCKED) This is Chapter 7's own Scheduler.add() check from Finding 2 -- `if pcb.state != ProcessState.READY: raise ValueError(...)`. It was never written specifically for preemption; it's the exact same guard cooperative scheduling already relied on. Preemption doesn't introduce a second, separate safety check for this -- it simply calls the same pick_next()/add() machinery Chapter 7 already built, so a rule already enforced there applies here automatically, with no new code required. WHY THIS MATTERS ------------------------------ If preemption had been built with its own, separate "who gets to run next" logic instead of reusing Scheduler directly, there would have been a real risk of the two mechanisms disagreeing about which states are schedulable -- exactly the kind of gap this course has already found more than once (Chapter 6's own quota gap, Chapter 7's own double-enqueue bug) when a rule was enforced by one code path but not another. Reusing the identical Scheduler object for both cooperative and preemptive switching closes that gap by construction, not by remembering to duplicate a check correctly. WHY THIS WORKS AS AN ANSWER ------------------------------ Testing that the interrupt fires (confirming it's genuinely happening, not just "conveniently" not mattering) and then separately testing that add() itself would reject the process, isolates exactly which layer is responsible -- the ready_queue's own contents, not a special BLOCKED-check inside the timer handler that doesn't actually exist.