Exercise 1: A Shared Lock Blocking a Concurrent Exclusive Request — Possible Solution ==================================================================== THE TEST ------------------------------ lock_mgr = LockManager() reader_has_lock = threading.Event() results = {} def reader(): lock_mgr.acquire_shared('page', 'reader') reader_has_lock.set() time.sleep(0.3) lock_mgr.release_all('reader') def writer(): reader_has_lock.wait() start = time.perf_counter() lock_mgr.acquire_exclusive('page', 'writer') results['writer_wait'] = time.perf_counter() - start lock_mgr.release_all('writer') tr = threading.Thread(target=reader); tw = threading.Thread(target=writer) tr.start(); tw.start(); tr.join(); tw.join() RESULT ------------------------------ writer's own wait time for the exclusive lock: 0.300s The writer's acquire_exclusive() call takes essentially the full 0.3 seconds to return -- it doesn't succeed immediately. WHY THE WRITER GENUINELY BLOCKS ------------------------------ acquire_exclusive()'s own loop condition is: holder = self.exclusive_holder.get(page_key) sharers = self.shared_holders.get(page_key, set()) - {txn_id} if (holder is None or holder == txn_id) and not sharers: ...succeed... self.cond.wait() Even though no OTHER transaction holds an exclusive lock on the page (holder is None), the reader's own shared lock is still sitting in shared_holders, so sharers is non-empty (it contains 'reader', which isn't the writer's own txn_id). The condition fails, and the writer calls self.cond.wait(), which genuinely suspends the thread until something calls self.cond.notify_all() -- which only happens inside release_all(). Since the reader doesn't call release_all('reader') until after its own 0.3-second sleep finishes, the writer has no way to wake up and re-check the condition any earlier than that. WHY reader_has_lock.wait() MATTERS FOR THE TEST'S OWN RELIABILITY ------------------------------ Without waiting for reader_has_lock first, the writer's thread could start and reach acquire_exclusive() before the reader thread has even acquired its own shared lock yet -- in which case the writer might succeed immediately (nothing is held yet), making the test measure the wrong thing entirely. The Event guarantees the writer only starts timing once the shared lock is genuinely, verifiably already in place. WHY THIS WORKS AS AN ANSWER ------------------------------ Measuring the writer's own real wait time -- not just checking that it eventually succeeds -- proves the block is genuine and lasts as long as the shared lock is actually held, rather than being a lucky coincidence of scheduling. A wait time close to 0.3s (not close to 0s) is direct, measured evidence that shared and exclusive locks are genuinely mutually exclusive with each other, even though multiple shared locks are not mutually exclusive with one another.