Exercise 2: A Consumer With Nothing to Consume Never Blocks Anyone Else — Possible Solution ==================================================================== THE TEST ------------------------------ # lonely_consumer: 5 real consume() attempts against a buffer that # only bystander ever produces into programs[lonely_consumer.pid] = [('consume',)] * 5 programs[bystander.pid] = [('produce', 99), ('produce', 88)] # run 30 real steps, QUANTUM=1 RESULT ------------------------------ after 30 real steps: consumer's own remaining ops = 3 bystander's own remaining ops = 0 bystander's own 2 produce operations both completed fully. lonely_consumer successfully consumed exactly 2 of its 5 planned attempts (the 2 items bystander actually produced) and has 3 genuinely unsatisfiable attempts left, since nothing will ever produce a 3rd item in this scenario. WHY A FAILED try_consume() NEVER BLOCKS THE SCHEDULER ------------------------------ ProducerConsumerKernel.run_step()'s own logic is: if op[0] == 'consume': result = self.buffer_obj.try_consume() if result is not None: self.consumed_log.append(result) ops.pop(0) self.ticks_since_switch += 1 if self.ticks_since_switch >= self.quantum: self.interrupts.dispatch(TIMER_INTERRUPT) When try_consume() returns None (nothing available), the op simply stays at the front of the process's own program -- ops.pop(0) is never reached. But the very next two lines run regardless of success or failure: the tick counter still advances, and the timer interrupt still fires on schedule. A failed attempt still consumes exactly one real "turn" and still triggers a real preemption at QUANTUM=1 -- the CPU is handed to whichever process is next in the ready queue no matter what just happened. WHY THIS MATTERS ------------------------------ This is the same design decision Chapter 2's own SpinLockPreemptiveKernel made for a failed ACQUIRE: "waiting" in this kernel is never a special state that halts scheduling -- it's just a real op that keeps getting retried on the process's own next turn, exactly like any other instruction. The cost of waiting is real (wasted turns spent repeatedly failing), but it's never infectious -- a process stuck waiting on a semaphore that will never be satisfied can spin forever without ever preventing any OTHER process from making its own real progress. WHY THIS WORKS AS AN ANSWER ------------------------------ Deliberately constructing a scenario where the consumer can NEVER fully succeed (5 requested, only 2 ever producible) and confirming the bystander's own unrelated work still completes 100% within the same run proves the blocking is scoped to the ONE process making the failed calls, not a property of the shared buffer or the kernel's own scheduler as a whole.