Exercise 3: A Blocked Process Must Return Through READY — Possible Solution ==================================================================== THE TEST ------------------------------ proc = kernel.create_process(num_pages=1) proc.transition(ProcessState.RUNNING) proc.transition(ProcessState.BLOCKED) # e.g. waiting on I/O try: proc.transition(ProcessState.RUNNING) # jump straight back? skip_worked = True except IllegalStateTransition: skip_worked = False proc.transition(ProcessState.READY) # the real, required path proc.transition(ProcessState.RUNNING) RESULT ------------------------------ after RUNNING -> BLOCKED: state = BLOCKED BLOCKED -> RUNNING directly was correctly rejected after the real path BLOCKED -> READY -> RUNNING: state = RUNNING skip_worked is False -- the direct BLOCKED-to-RUNNING attempt raises IllegalStateTransition. The two-step path, BLOCKED -> READY followed by READY -> RUNNING, succeeds cleanly both times. WHY THE DIRECT JUMP IS REJECTED ------------------------------ ALLOWED_TRANSITIONS[ProcessState.BLOCKED] is defined as exactly {ProcessState.READY} -- a single allowed destination, not RUNNING. transition() checks new_state against this set unconditionally, with no special case for "well, the process probably wants to run again anyway": BLOCKED can only ever become READY, full stop. WHY THIS MATTERS FOR WHAT A SCHEDULER ACTUALLY DOES ------------------------------ A process becomes BLOCKED because it's waiting on something outside its own control -- I/O completing, a lock becoming available, a timer firing. The MOMENT that condition clears, the process is eligible to run again -- but "eligible" is not the same claim as "running right now." Some other process might already be using the CPU. Forcing the transition to pass back through READY models this correctly: the process re-enters the same pool every other runnable process waits in, and the scheduler (built in later chapters) decides when it actually gets the CPU -- rather than a blocked process being able to seize the CPU back for itself the instant its own wait ends, regardless of what else might be running. WHY THIS IS THE SAME SHAPE AS THE CHAPTER'S OWN "SKIP RUNNING" FINDING ------------------------------ The chapter's own Finding 3 already showed READY can't jump straight to BLOCKED, skipping RUNNING. This exercise shows the same principle from the opposite side: BLOCKED can't jump straight to RUNNING, skipping READY. Both are the same underlying rule -- a process only ever reaches RUNNING by going through READY first -- enforced uniformly by the one shared ALLOWED_TRANSITIONS table, not by two separately-written special cases. WHY THIS WORKS AS AN ANSWER ------------------------------ Confirming both the rejected shortcut and the real, required path succeed exactly as the state table specifies demonstrates the transition rules aren't just decorative bookkeeping -- they encode a real, meaningful invariant about how a scheduler is allowed to regain control over an unblocked process, matching how a real kernel actually has to behave.