H must know which obligations remain for this transaction. A raw packet count is not enough if a response could be misattributed, repeated or associated with an earlier use of the same pending entry. The entry generation is a counter that tells successive uses of one pending entry apart. Even a reliable transport that never duplicates needs this association; a retry-capable extension also needs duplicate handling.
Use MSI. The existing upgrade trace already shows H collecting C1's acknowledgement before granting C0, the writer. Instead of redrawing it, inspect the bookkeeping H needs when it has two targets, as in the next lesson's back-invalidation of C0 and C1.
Tracker event, not a coherence delivery trace | Remaining sources | Completion evidence |
|---|---|---|
Initialise required set | {C0,C1} | No response yet |
Matching C1 response | {C0} | One of two distinct sources |
Repeated C1 response in a retry-capable extension | {C0} | No new evidence |
Wrong transaction's C0 response | {C0} | No evidence for this transaction |
Matching C0 response | {} | All sources covered, still check any data obligation |
H fixes the expected set when it sends the multicast, before any ack can arrive, so an ack never arrives for an unknown set. In the Primer's variant, acks go to the requester and the count comes in Data; an Inv-Ack can arrive before Data, so the requester must count acks before it knows the total (for example a counter that goes negative and completes at zero after Data). The course collects at H, so this case does not arise. Acks may still return in any order, and an empty expected set does not replace the data check.
class Tracker:
def __init__(self, tx, expected):
self.tx = tx
self.remaining = set(expected)
def ack(self, tx, source):
if tx != self.tx or source not in self.remaining:
return False
self.remaining.remove(source)
return True
class GrowingTracker: # bad: builds its set as acks arrive
def __init__(self):
self.expected, self.got = set(), set()
def ack(self, source):
self.expected.add(source)
self.got.add(source)
return self.got == self.expected # "complete"
t = Tracker(("X", 7), {"C0", "C1"})
assert not t.ack(("X", 6), "C0")
assert not t.ack(("Y", 7), "C0")
assert t.ack(("X", 7), "C1")
assert not t.ack(("X", 7), "C1") and t.remaining == {"C0"}
assert t.ack(("X", 7), "C0") and not t.remaining
bad = GrowingTracker()
assert bad.ack("C1") # completes early while C0 still holds S
print("PASS: a fixed expected set completes only on matching first responses")The tuple is illustrative matching metadata (line, entry generation), not a new wire opcode or a claim about field widths.
Completion holds under A1-A6 (assumptions), here A1: every target's ack is eventually delivered. Target membership proves who must respond; it does not prove they eventually will.
No. Permission release and current-data availability are separate obligations. H must wait for and protect the newest value before granting the data-dependent request.