Commit 01b5a98
committed
fix(speculate): wait for every assumption to settle before merging
## Summary
### Why?
A head could merge on an assumption that had not come true. `mergeablePath` required every dependency a path assumed would *succeed* to have actually merged, but imposed no wait at all on one it assumed would *fail*.
The doc comment stated the reasoning: *"A dependency assumed to fail imposes no wait: the path is broken the moment that dependency succeeds, so a still-live path has already been vindicated on it."* That holds only if the transition were instantaneous. It is not — a dependency spends time in `speculating`, and then in `merging`, having neither succeeded nor failed. `assumptionBroken` only fires on a terminal state, so throughout that window the path is neither broken nor vindicated. It is unsettled, and the gate read unsettled as permission.
A path that assumed a dependency would fail was built *without* that dependency's changes. Landing the head while that dependency is still live, and watching it land too, puts a combination on the trunk that no build ever validated — the one thing the queue exists to prevent. It needs no textual conflict to break the trunk, because the two changes were never built together.
Measured against the real predicates before fixing, with a passed `fails(D)` path and only D's state varying:
| D's state | `mergeablePath` | correct? |
| -- | -- | -- |
| `speculating` | true | no |
| `merging` | true | no |
| `cancelling` | true | no |
| `failed` | true | yes — the assumption came true |
| `succeeded` | false | yes — `assumptionBroken` catches it |
`decide` returned `merge` in all three of the wrong rows.
### What?
The rule is now symmetric: a path may merge once every dependency it took a position on has finished the way it assumed. `succeeds` needs `Succeeded`; `fails` needs `Failed` or `Cancelled`; `ignored` is not a position, so it still imposes no wait and conflict relaxation is untouched. `allAssumedSucceedingMerged` becomes `allAssumptionsSettled`, since it no longer looks only at succeeding dependencies.
Note this is not the "bypass large diff" early merge the RFC describes. That reads a passed path for *every* combination of the dependencies, and is not implemented on the controller side — nothing enumerates combinations. A single path betting the right way was never a sound approximation of it, and `speculation.md` now says so rather than describing behaviour the code does not have.
One liveness consequence, recorded on CODEM-428 rather than fixed here: a dependency stuck in `Cancelling` now stalls its dependents too, not just itself. `finalizeCancellations` converges `Cancelling → Cancelled` on any subsequent run, so this only bites when no further run is triggered — which is that issue's edge-triggering gap.
## Test Plan
- ✅ `make test` — 96/96 pass
- ✅ `make lint`, `make check-gazelle`, `make check-tidy`
`TestMergeablePath` gains the cases the old rule got wrong: a fails assumption waits out `speculating`, `merging` and `cancelling`, and merges on `Failed` or `Cancelled`. An assumed-succeeding dependency waits out its merge, and an ignored one still imposes no wait.
## Issue
Fixes https://linear.app/uber/issue/CODEM-4291 parent 9228d86 commit 01b5a98
3 files changed
Lines changed: 64 additions & 19 deletions
File tree
- doc/rfc/submitqueue
- submitqueue/orchestrator/controller/speculate
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| 75 | + | |
| 76 | + | |
75 | 77 | | |
76 | 78 | | |
77 | 79 | | |
| |||
Lines changed: 30 additions & 17 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
67 | | - | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
68 | 69 | | |
69 | 70 | | |
70 | | - | |
71 | | - | |
72 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
73 | 74 | | |
74 | | - | |
75 | | - | |
76 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
77 | 82 | | |
78 | 83 | | |
79 | 84 | | |
| |||
82 | 87 | | |
83 | 88 | | |
84 | 89 | | |
85 | | - | |
| 90 | + | |
86 | 91 | | |
87 | 92 | | |
88 | 93 | | |
89 | 94 | | |
90 | 95 | | |
91 | 96 | | |
92 | | - | |
93 | | - | |
94 | | - | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
95 | 103 | | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
101 | 114 | | |
102 | 115 | | |
103 | 116 | | |
| |||
Lines changed: 31 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
| 63 | + | |
64 | 64 | | |
65 | 65 | | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
66 | 90 | | |
67 | 91 | | |
68 | 92 | | |
| |||
71 | 95 | | |
72 | 96 | | |
73 | 97 | | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
74 | 104 | | |
75 | 105 | | |
76 | 106 | | |
| |||
0 commit comments