Commit 7091007
authored
fix: Defer adaptive chunk adjustment without a measurable sample (#232)
Adaptive chunking computed
duration_per_task = completed_task_duration / completed_task_count
adaptive_chunk_size = target_runtime_seconds / duration_per_task
with neither divisor guarded. Implements step 3 of the adaptive chunking
algorithm in the CLI specification, added upstream in openjd-rs #282: "If
the cumulative duration is zero or non-finite, keep the current chunk size
and wait for a measurable sample."
Be clear about severity: this is spec conformance and hardening, NOT a fix
for a reachable crash. I could not reach either divisor through a normal
`openjd run`:
- completed_task_count cannot be zero. It accumulates
len(IntRangeExpr.from_str(...)), and an IntRangeExpr cannot be empty --
"1-0" and "5-1" raise ValueError, "0-0" and "1-1" have len 1.
- completed_task_duration cannot be 0.0 in practice. run_task() always
spawns and waits on a real subprocess, and raises before the
accumulation line on failure, so the measured delta would have to fit
inside one perf_counter tick (~42 ns on this host, against a ~10 ms
floor for the cheapest possible subprocess).
Correcting an earlier claim of mine: I previously recorded this as a
reproduced defect. It was not. The "reproduction" replayed the arithmetic
with a hardcoded 0.0, which demonstrates nothing about the code path.
The guard is still worth having, because the two implementations fail
differently if it is ever reached: Rust produces inf and saturates to a
huge chunk size, while Python raises ZeroDivisionError out of the run.
Extracted as a module-level _calculate_adaptive_chunk_size returning
Optional[int], mirroring openjd-rs's calculate_adaptive_chunk_size, so the
deferral is unit-testable without provoking an unreachable timing
condition.
Mutation-checked, 10 mutants, 0 survivors: removing the guard or any one
of its three arms, making it always defer, ignoring the sentinel at the
call site, and removing the blend or the clamp are each caught by name.
Removing the sentinel check is caught by the pre-existing end-to-end
test_openjd_run_on_chunked_job_adaptive_chunking, which is the evidence
the extraction is behaviour-preserving on the real path.
One finding from that exercise worth recording. My first draft used
`continue` on the deferral path, which also skips the maximum-task
countdown further down the loop and so silently breaks --maximum-tasks. I
caught it by reading, and then confirmed the suite would NOT have: the
mutant survived. The existing
test_openjd_run_on_chunked_job_maximum_task_count[1] covers adaptive
chunking with --maximum-tasks, but with real durations the estimate never
defers, so the interaction was unpinned. Adds
test_maximum_task_count_is_honoured_when_every_estimate_defers, which
forces every estimate to defer; it now catches that mutant.
Raised as its own PR rather than added to #230: that PR is the RFC
0007/0008 feature work, is already MERGEABLE and waiting only on
approval, and its merge unblocks the specifications conformance suite.
This change is unrelated to its scope and not urgent enough to reset its
review.
Verified: 301 passed / 2 skipped (289 before), ruff clean, black clean,
mypy clean.
Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>1 parent d697d7a commit 7091007
2 files changed
Lines changed: 225 additions & 12 deletions
File tree
- src/openjd/cli/_run/_local_session
- test/openjd/cli
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| |||
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 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 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
52 | 97 | | |
53 | 98 | | |
54 | 99 | | |
| |||
300 | 345 | | |
301 | 346 | | |
302 | 347 | | |
303 | | - | |
304 | | - | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
305 | 357 | | |
306 | | - | |
307 | | - | |
| 358 | + | |
| 359 | + | |
308 | 360 | | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
313 | | - | |
314 | | - | |
315 | 361 | | |
316 | 362 | | |
317 | 363 | | |
318 | 364 | | |
319 | 365 | | |
320 | | - | |
| 366 | + | |
321 | 367 | | |
322 | 368 | | |
323 | | - | |
| 369 | + | |
324 | 370 | | |
325 | 371 | | |
326 | 372 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 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 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
0 commit comments