Skip to content

Cherry-picks for 10.2.1 (2026-09-22) - #13720

Merged
cmcfarlen merged 8 commits into
apache:10.2.xfrom
cmcfarlen:10.2.x-picks-20260922
Sep 23, 2026
Merged

cmcfarlen merged 8 commits into
apache:10.2.xfrom
cmcfarlen:10.2.x-picks-20260922

Conversation

@cmcfarlen

Copy link
Copy Markdown
Contributor

Cherry-picks for the 10.2.1 release, taken from the items sitting at For v10.2.1 in the
ATS v10.2.x project.

Picked in master merge order:

PR master SHA Title
#13571 70c6b2c4 Use __atomic builtins as the freelist 128-bit CAS fallback
#13655 bfe88e10 QPACK: use || in the varint decode range guards
#13656 915543f9 hrw4u: exit non-zero on compile errors
#13699 2651038e hrw4u: add an AST round-trip test over the whole corpus
#13675 ca133303 hrw4u: enforce sandbox NOT and in on their implicit spellings
#13653 f39ee5f5 Handle all cachekey regex capture groups
#13715 0079038a Test 103 Early Hints before a cached range response
#13718 3e10681a cache: retry without O_DIRECT for an explicit file span

All eight are MERGED squash merges with no Incompatible label, and none were already
present on 10.2.x (checked by both (#NNN) subject and the cherry picked from trailer).

Conflicts

Only one: tools/hrw4u/tests/data/ops/exceptions.txt in #13699. Master's parent already
carried a json-body.input exception from a PR that was not picked here; 10.2.x has no
json-body.* fixture. Resolved to add only the bool-spelling.input entry the commit
actually introduces, which matches the original diff exactly.

Not picked

Three project items at For v10.2.1 are still OPEN on master and so were skipped:
#12825, #13086, #13196. They stay at For v10.2.1 for the next pass.

Local verification (macOS, dev preset)

Draft on purpose: to be landed by fast-forward once CI is green, not merged via the UI.

phongn and others added 8 commits September 22, 2026 19:07
)

Platforms without an inline 128-bit CAS, such as riscv64, fail the
build with "unsupported processor". Neither GCC nor LLVM emit an
inline 128-bit CAS on riscv64, even with the Zacas extension, so a
hand-written pointer-packing branch would be the only alternative
and would depend on the kernel's virtual address width. Fall back
to the __atomic builtins instead. They lower to libatomic calls,
which may take internal locks; that is correct because every access
to a shared head_p goes through INK_QUEUE_LD and ink_atomic_cas.

Also revive the orphaned atomic list stress test as Catch2 tests
and remove the dead INK_QUEUE_NT code.

Fixes: apache#13555

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 70c6b2c)
Ten guards in QPACK.cc read
`xpack_decode_integer(...) < 0 && value > 0xFFFF`, so neither condition
rejects anything: a decode failure falls through, and an oversized
varint is silently narrowed into the surrounding uint16_t. The
delta_base_index guard also had its comparison inverted.

The unchecked failure matters more than the truncation. On failure
xpack_decode_integer returns -1, and callers then do `read_len += ret`,
giving SIZE_MAX. IOBufferReader::consume() takes that as -1, its
release-assert passes because is_read_avail_more_than(-2) is true, and
start_offset moves backwards. The helper returns 0 rather than a
negative, so _on_encoder_stream_read_ready() does not abort and its
`while (is_read_avail_more_than(0))` loop re-reads the same byte.

Flip the ten operators and correct the inverted comparison.

The value guard in _read_insert_with_name_ref also has its bound raised
from 0xFF to 0xFFFF, matching the other nine sites. This is required
rather than cosmetic: value_len is a size_t, so 0xFF bounds nothing, and
under || a 0xFF bound would reject every header value longer than 255
bytes. At 0xFFFF the check is unreachable, since xpack_decode_string is
already capped by _header_field_max_size.

(cherry picked from commit bfe88e1)
* hrw4u: exit non-zero on compile errors

The exit gate required `tree is None`, but ANTLR error recovery almost
always yields a tree, so both syntax and semantic errors exited 0 while
printing diagnostics and a partial .conf. Collecting every error and
failing the build were mutually exclusive: only --stop-on-error exited 1.
Sandbox denials were caught by the same gate, so the "denied" outcome the
sandbox docs describe also exited 0.

generate_output now reports failure by return value and run_main owns the
exit, so a bad file in a bulk run no longer aborts the files after it.

A failing compile still prints its partial .conf; the exit code now marks
it untrustworthy. Suppressing those bytes would change behavior for
existing pipelines and is left as a separate decision.

* hrw4u: parse real input in generate_output return-value tests

The failure test passed parser_obj=None, which only worked because a
None tree short-circuits before the AST branch reads it. Parsing a real
input instead also pins the regression: the input parses, so the tree is
not None -- exactly what the old exit gate let through.

* hrw4u: cover u4wrh in the exit-code tests, scope the doc claim

u4wrh drives the same run_main(), so the contract regresses just as
easily there; verified the new test exits 0 against the pre-fix code.

The doc said every input is processed before the status is decided,
which reads as covering the fatal argument and I/O paths too -- those
still exit immediately.

* hrw4u: document exit status 2 for usage errors

run_main() lets argparse handle the command line, so an unknown option,
a bad option value, or conflicting output modes exit 2, not the 1 the
table claimed. Normalizing them to 1 would merge "you typed the command
wrong" into "your rules did not compile", so the doc follows the code.

Row 1 now lists only what actually exits 1; the mixed bulk/stdout
rejection is caught after parsing and belongs there, not with argparse.

(cherry picked from commit 915543f)
* hrw4u: add an AST round-trip test over the whole corpus

Render every corpus input's AST back to hrw4u and require the compiled
config to be unchanged: whatever the AST drops, the config loses too.
Unlike hand-written cases, nothing has to be enumerated in advance.

It found five losses, all fixed here:

  - an empty `else { }` looked like no else clause, so a sandbox policy
    denying 'else' was evaded by writing one
  - comments were discarded, though five .conf goldens carry them
  - a bool assignment lost the spelling the emitter echoes back
  - parentheses were unwrapped, dropping the cond %{GROUP} they emit
  - a set and an iprange both became a tuple of IPValue, though
    in [1.2.3.4] emits (1.2.3.4) and in {1.2.3.4} emits {1.2.3.4}

IfBlock.has_else is required rather than defaulted, so a site that
rebuilds the node and forgets it fails instead of reopening the bypass.

A second test asserts the corpus reaches every grammar rule; a bare
$param value had no fixture, now added.

* hrw4u: keep bool spelling in every value context

The AST kept the source spelling only on an assignment RHS, so a TRUE
in a comparison or a function argument regenerated as true and changed
the emitted config. bool-spelling.input.txt witnesses both; the reverse
normalizes an argument's spelling, hence the exceptions.txt entry.

* hrw4u: keep number spelling in every value context

The AST parsed a NUMBER into a Python int, so a leading zero the emitter
echoes back was lost: 007 became 7 in a header value, three bytes
becoming one. number-spelling.input.txt witnesses all three value
contexts; the digits survive in each.

A plain int was a deliberate call, on the grounds that no corpus input
wrote a leading zero. It did not survive a semantic pass built on the
AST, which matches structurally over ValueExpr and has nowhere to put a
naked int.

* hrw4u: make the per-test sandbox fixture compile

Its body wrote inbound.req.X-Foo inside TXN_START, where that field does
not exist, so the input was rejected before the sandbox ran and the
round-trip test had to name it as the one corpus input that does not
compile. The fixture only ever asserted that a per-test sandbox.yaml is
preferred over the shared one, and a section denial fires whatever the
body is, so the body is now a rule TXN_START actually admits.

That retires DOES_NOT_COMPILE: an input meant to be rejected is named
.fail., and a corpus input that stops compiling should fail the test
rather than opt out of it.

(cherry picked from commit 2651038)
…#13675)

* hrw4u: enforce sandbox NOT and in on their implicit spellings

`modifiers: [NOT]` only caught an explicit `with NOT`, and
`language: [in]` only caught the `[...]` value form, so `!expr`, `!=`,
`!~`, `!in` and `in {10.0.0.0/8}` all compiled unchecked. AND and OR
were already checked at `&&` and `||`; negation and IP-range membership
are now consistent with that.

The check sits at the two sites where the source introduces negation,
not at `_make_condition`, whose `negate` argument is also true for the
`[NOT]` the compiler synthesises for a bare header test. Denying `NOT`
must not reject `if inbound.req.X-Foo`; allowed-implicit-not pins that.

`modifiers` was undocumented, so the sandbox section gains it.

* Address review: name the tag the compiler actually emits

The modifiers example said `%{HEADER:X-Foo}`, but `inbound.req.X-Foo`
lowers to CLIENT-HEADER, as allowed-implicit-not's golden output shows.
Kept the literal on one line while here.

* Address review: cover the !~ spelling of NOT

NOT_TILDE is its own lexer token; without a case for it, dropping it
from the negate tuple still passed.

(cherry picked from commit ca13330)
Cachekey patterns with ten or more capture groups can crash ATS when
building a cache key because a successful match leaves the capture
vector empty. Replacement patterns also reject valid group references
when the match buffer is too small or trailing optional groups do not
participate in a match.

This patch sizes match buffers from the validated pattern capture count
and checks replacement references at initialization. Unmatched optional
groups contribute empty strings. Unit and replay coverage verifies full
cache keys across the capture limit and optional-group combinations.

Fixes: apache#13638

Co-authored-by: GPT-6 Astra Light
Co-authored-by: GPT-6 Astra Medium

Co-authored-by: bneradt <bneradt@yahooinc.com>
(cherry picked from commit f39ee5f)
Issue apache#12244 reported ATS aborting on the HttpTunnel skip_bytes
assertion for Range requests of a small, cacheable 308. The fix in
apache#12906 was tested only with a 100 Continue ahead of a compressed POST
response, so the reported scenario had no coverage. 103 Early Hints
take the same interim-response path as 100 Continue, and a Range miss
with cache.range.write enabled caches the untransformed response behind
the range transform, leaving the cache-write consumer with a stale
header size to skip in a body-only buffer.

This patch extends the regression test with a run in which the origin
sends a 103 Early Hints and then a 65 byte, cacheable 308 that the
client requests with "Range: bytes=0-64". Without the apache#12906 fix, this
run hits the same assertion reported in the issue; with it, ATS keeps
running.

Fixes: apache#12244

Co-authored-by: Claude Opus 5.5 (Medium effort) <noreply@anthropic.com>
(cherry picked from commit 0079038)
The non-direct-IO retry added in TS-1312 only fires when O_CREAT is
set, and O_CREAT is set only when the span names a directory, so that
cache.db can be created inside it. An explicit file span in
storage.config therefore gets no retry: on a file system without
O_DIRECT support the span open fails outright and the cache is
disabled, even though the very same file system works when the span is
written as a directory. Whether direct I/O is usable is a property of
the file system, not of the form the span was written in.

The cache_shm_* autests are the only ones configuring an explicit file
span, so on a sandbox whose file system lacks O_DIRECT all of them
fail with "must be placed on a file system that supports direct I/O"
followed by "Cache Disabled".

(cherry picked from commit 3e10681)
@cmcfarlen
cmcfarlen merged commit 56eb820 into apache:10.2.x Sep 23, 2026
14 checks passed
@cmcfarlen
cmcfarlen deleted the 10.2.x-picks-20260922 branch September 23, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants