Skip to content

Cherry-picks for 10.2.1 (2026-09-21) - #13709

Merged
cmcfarlen merged 14 commits into
apache:10.2.xfrom
cmcfarlen:10.2.x-picks-20260921
Sep 21, 2026
Merged

cmcfarlen merged 14 commits into
apache:10.2.xfrom
cmcfarlen:10.2.x-picks-20260921

Conversation

@cmcfarlen

Copy link
Copy Markdown
Contributor

Cherry-picks for the 10.2.1 release, taken from the For v10.2.1 column of the
ATS v10.2.x project.

Draft on purpose: this exists to run the full CI matrix over the picked set. It
lands by fast-forward, not by merging here.

# PR Notes
1 #13514 Share HTTP accept properties
2 #13645 Cleanup LogField Registration
3 #13063 Add changelog generation tool for GitHub milestones
4 #13574 Fix TransformVConnection leak in abort_tunnel()
5 #13450 h2origin autest: honor the 30s squid.log wait budget
6 #13355 hrw4u: add --version CLI flag
7 #13672 header_rewrite: guard the i == 0 case in HRWSimpleTokenizer
8 #13673 Fix build errors with LLVM 23
9 #13616 Allow a metric to be unlisted
10 #13697 Fix strncasecmp checks that accepted a prefix as equal
11 #13666 Retract per server metrics when the mode changes
12 #13700 Fix the 9.2 compatibility cache key for paths that carry ;params
13 #13696 hrw4u: admit SESSION_VARS as a denyable section
14 #13611 Make the 9.2 compatibility cache key usable end to end

Picked in master merge order. Every commit carries a cherry picked from commit
trailer.

Conflict resolutions

Four picks needed hands. In each case the release branch's own idiom was kept and
only the upstream fix applied.

#13514HttpProxyServerMain.cc. 10.2.x guards probe-acceptor construction
with needs_probe (master does not). Kept the guard, applied the &port argument
change inside it.

#13645Log.cc. Master changed the sshv and csshv log fields from
dINT to STRING in a commit that is not on 10.2.x, and the auto-merge dragged
that behaviour change in. Kept 10.2.x's dINT and took only the
register_field() refactor. Verified mechanically: all 138 (symbol, name, type)
registrations are byte-identical to the pre-pick branch, so this commit is a pure
refactor here, as intended.

#13666test_ConnectionTracker.cc. The file was added to master by #13516
(not picked), so 10.2.x has neither the file nor its CMakeLists.txt entry.
Kept the helpers and the ConnectionTracker aggregate metric publication test
case that #13666 actually contributes, and added the one build-wiring line so it
runs. Dropped #13516's Connection tracker server match conversion test case:
its "invalid values are clamped" section asserts clamping that 10.2.x's
SERVER_MATCH_CONV.store_int deliberately does not do (the clamp is commented
out there), so importing it would have failed the build's test suite.

#13700URL.cc / test_URL.cc / ink_ascii_tolower.h. The header comes
from #13320 (Highway-accelerated ASCII to_lower, not picked) and #13700 only
touches its comment, so it was dropped. 10.2.x keeps a local memcpy_tolower
helper where master includes that header — kept the helper, took the new comment.
Four SIMD-oriented uppercase-host fixtures in test_URL.cc are likewise #13320's,
not this commit's, so they were left out. #13700's own ;params equivalence tests
are all present.

Not picked

Three project items at For v10.2.1 are still open on master and so were
skipped: #12825 (hrw4u native C++ parser), #13086 (per-thread FD tables),
#13196 (CompileParseRules refactor).

Verification

Per-commit file sets were diffed against their master counterparts; the only two
that differ are #13666 (+ the CMakeLists wiring line) and #13700 (− the dropped
header), both as described above.

Local build could not run — the host's Xcode license prompt is blocking the
toolchain — so CI here is the first real compile of this set.

bneradt and others added 14 commits September 21, 2026 10:43
Each HTTP protocol acceptor keeps a separate copy of proxy-port
properties, so adding or consuming one requires protocol-specific
plumbing and can leave newer protocols without configured defaults.

This patch introduces a common HTTP acceptor base that shares one
immutable property set per proxy port and retains the source
HttpProxyPort. Sessions track that acceptor, while transactions copy
mutable outbound settings at transaction start so overrides stay
isolated. Handoff paths keep their acceptors alive for the resulting
session.

Fixes: apache#3427
(cherry picked from commit 670d195)
* logging: register log fields through a single helper

Prevent bugs like 7772518 ("Fix Proxy-Protocol log field symbols",

Every field registration duplicated an add() to global_field_list and
an emplace() into field_symbol_hash, with the symbol spelled out twice.
Log::register_field() does both and keys the hash off the field's own
symbol, so the list and the hash cannot drift apart.

* logging: drop the unused copy flag from LogFieldList::add()

No caller ever asked for the copying behaviour, and the default of true
made the ownership of an added field read as ambiguous at each call
site. LogFieldList now unconditionally takes ownership.

* Error handling of field_symbol_hash insertion fails

* logging: document LogFieldList::add() ownership transfer

The comment claimed elements are copied on insert, which stopped being
true when the unused copy flag was dropped (and the flag defaulted to
false before that anyway).

(cherry picked from commit a5955c6)
* Add changelog generation tool for GitHub milestones

Replaces tools/git/changelog.pl with a Python implementation
that generates changelogs from merged PRs in a milestone using
the GitHub API or gh CLI. Default output matches the existing
CHANGELOG-* file format. The --doc mode includes merge SHAs,
labels, and full PR descriptions to guide AI-assisted release
documentation updates. Supports text and YAML output formats.

Co-Authored-By: Claude <noreply@anthropic.com>

* Update release process docs to use new changelog tool

Replace reference to tools/git/changelog.pl with the new
tools/changelog/changelog.py invocation using uv run.

Co-Authored-By: Claude <noreply@anthropic.com>

fix python formatting

* remove old changelog.pl script

copilot review

* Add git range support to generate changelog from commits without PR or milestone

* changelog: surface gh api failures in the merge check

Any non-zero `gh api .../merge` exit was read as "not merged", so 403
secondary rate limiting or a 5xx silently dropped merged PRs from a
release changelog while still exiting 0. That is the failure to expect,
since the check runs once per PR across a whole milestone.

Use --include so the status line separates a real 404 from a transport
or rate-limit error, and exit on anything else. The --doc detail fetch
fails the same way now rather than substituting an empty sha and body.

Also correct the --doc wording, which stores the PR body and not the
commit message; discourage -a, since it exposes the token in ps output
and shell history; and declare the Apache-2.0 license that the sibling
tool packages set.

* changelog: point uv.lock at public PyPI, tighten two checks

The committed uv.lock resolved through an internal Apple package mirror,
so every artifact URL and the registry itself were unreachable for anyone
outside that network. Rewritten to pypi.org and files.pythonhosted.org;
package versions, sizes and sha256 hashes are unchanged, since the mirror
serves the same artifacts.

The --from-git PR-number regex matched "#N" anywhere in the subject, not
the trailing "(#N)" its docstring describes, so an issue reference could
be captured as a PR number and merge unrelated entries in
merge_changelogs(). Now anchored.

_check_rate_limit() reported a rate limit for every 403, but GitHub also
uses 403 for a missing token or insufficient scopes, where that advice is
wrong and hides the cause. It now exits only on a primary limit
(x-ratelimit-remaining: 0), a secondary limit (retry-after) or a 429, and
lets anything else fall through to the raise_for_status() that follows
each call site.

* changelog: require PyYAML, dedupe git entries by PR number

pyproject.toml declares pyyaml as a required dependency, but the code
guarded the import and failed at runtime if it was missing. httpx is
imported unconditionally and is just as much a third-party dependency, so
the guarded path could only be reached by an install that pyproject.toml
does not describe. YAML output is a documented mode, so the dependency is
required: import it like httpx and drop the dead branch.

merge_changelogs() documented deduplication by PR number but only avoided
collisions between the git and milestone sources, not within the git range
itself. A revert and reapply, or the same commit cherry-picked twice,
carries the same trailing "(#N)" and produced two entries for one PR. Now
deduplicated on the git side too, keeping the first occurrence so the
surviving entry is the chronological one.

* changelog: safe_dump, drop the inert entry point, document prerequisites

yaml.safe_dump() instead of yaml.dump(), so the output cannot grow Python
object tags if a non-primitive ever reaches the entry dicts.

Removed [project.scripts]. Without a [build-system] table this is a uv
virtual project -- uv.lock records source = { virtual = "." } -- so the
package is never built and the console script it declares is never created:

    $ uv run --project tools/changelog changelog --help
    error: Failed to spawn: `changelog`

The declaration described an interface that does not exist, and reviewers
read it as the supported entry point twice. Documented the actual
invocation in its place rather than adding a build backend for a
single-file in-tree script.

The release guide now names its prerequisites: uv, and for --use-gh an
authenticated gh. It also documents the GH_TOKEN alternative, since an
unauthenticated run exceeds the API rate limit partway through a
release-sized milestone and exits without writing a changelog.

---------

Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit dc82542)
Fix a resource leak in HttpSM::state_read_server_response_header() when
abort_tunnel() is called while a request transform plugin registered at
TS_HTTP_READ_REQUEST_HDR_HOOK is active.

(cherry picked from commit 4773475)
The hand-rolled watcher relied on StartBefore's default 10 second
readiness gate, so the intended 30 second budget was never reached and
the test failed intermittently on loaded CI workers. Use the existing
AddAwaitFileContainsTestRun helper, which allows 30 seconds, and flush
log buffers every second so the entry appears sooner.

Fixes: apache#13445

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Chris McFarlen <chris@mcfarlen.us>
(cherry picked from commit 30c3baa)
…3672)

* header_rewrite: guard the i == 0 case in HRWSimpleTokenizer

A value whose first character is '{' or '<' made the tokenizer read
line[-1], so whether the config loaded or was rejected with an opaque
"basic_string" error depended on the adjacent byte. Matches the guard
parse_line already uses for the same idiom.

* header_rewrite: cover the '<' form of the leading-brace tokenizer case

The i == 0 guard protects both '{' and '<', but only the '{' form was
exercised.

(cherry picked from commit 9e26453)
* Add includes libc++ 23 no longer provides transitively

LLVM 23 flips libc++'s transitive-include compatibility block from
opt-out to opt-in (_LIBCPP_KEEP_TRANSITIVE_INCLUDES_LLVM23), so
<algorithm> and <string> no longer drag in <iterator> and <cstdlib>.
Include them where the symbols are actually used rather than relying
on the old default.

* Drop dead records code flagged by -Wunused-*

The const char (&)[N] overload of matches_bracketed_int_range can
never be selected: RecordElement::regex is a const char *, so the
string_view overload always wins. RecMessageRegisterRecvCb has had no
callers since traffic_manager was removed, leaving its cookie global
set but never read.

(cherry picked from commit 5a23990)
* Allow a metric to be unlisted

A metric name, once created, was published for the life of the process.
Any metric whose name or publication policy depends on a runtime
changeable setting could therefore never retract a name it had already
published, so such a setting only ever took effect for names created
after the change.

Unlisting takes a slot out of the store's listing. It keeps its slot, its
name and its atomic, so lookup by name still resolves and creating the
name again relists it with its value intact. Iteration skips it, which is
what removes it from traffic_ctl, the JSONRPC record lookup and
stats_over_http without any of them changing.

* Make iterator comparison terminate across snapshots

Each iterator captures its own bound, and exhaustion was judged against
that. A subrange whose stop iterator was made later held a larger bound,
so the walk could pass its own bound and go on comparing unequal to a
stop that was still live, with operator++ unable to make progress. Two
find() calls with a metric created between them was enough.

Exhaustion between two positional iterators is now judged against the
earlier of the two bounds, so such a subrange ends at the earlier
snapshot. The sentinel keeps its own answer, since its bound means
nothing.

* Anchor the unlisted-tail test in its own metric

It asserted the store had at least one listed metric left, which depends
on what other sections put there. A listed metric of its own says the
same thing without that coupling.

* Say which iterator comparisons are meaningful

Exhaustion is a property of an iterator's own snapshot bound, so two
taken at different times can compare equal to each other while
disagreeing about end. That is not a total equivalence relation, which
makes these unfit for a generic algorithm; only same snapshot
comparisons, and comparison against end, are meaningful.

* Frame the snapshot as the sequence, not as a caveat

The previous note disclaimed the equivalence relation while the type
still declared input_iterator_tag, which advertises what it then denied.
A snapshot is the sequence: iterators from different ones are no more
comparable than iterators into different containers, so mixing them is
unspecified rather than broken, and within one snapshot equality is the
relation an input iterator requires.

* Replace metric iteration with for_each

A public iterator lets a caller name a position, and a position stops
meaning anything once iteration skips unlisted slots. An iterator held
at a slot that is later unlisted becomes a range bound the walk steps
straight over and never reaches, and find() could hand back the next
listed metric rather than the one asked for. Supporting either would
mean defining iterator invalidation for listing changes, to keep a
surface with no callers: every consumer walks the whole store, and
find() had none at all.

for_each is the whole store or nothing. With no cursor to outlive the
walk, the equality rules, the snapshot bound comparison and find() go
away along with the defects they carried. lookup() remains the way to
reach a single metric by name.

(cherry picked from commit 99893cf)
* Fix strncasecmp checks that accepted a prefix as equal

strncasecmp(a, b, n) is an equality test only when the lengths are
compared first. Without that, any input that is a prefix of the
expected literal matches: `Vary: Accept` suppressed the
`Vary: Accept-Encoding` compress adds, `escape: jsonX` loaded as JSON,
and isTrue("1abc") was true. Replace these with ts::iequals, which
short-circuits on a length mismatch.

Prefix and suffix matches keep using strncasecmp. StringCompare.h joins
the installed tsutil headers because plugins now include it.

* Address Copilot's comment

* Add an autest for compress appending to an existing Vary header

The existing compress tests only ever see an origin `Vary: Accept-Encoding`,
so they still pass if the prefix-match fix in vary_header() is reverted.
Verified: this test fails with the old strncasecmp, reporting
`vary` as "Accept" instead of "Accept, Accept-Encoding".

(cherry picked from commit 13ef6a7)
* hrw4u: add --version flag

Adds a standard --version flag to the hrw4u CLI script. Reports the
installed apple-hrw4u package version via importlib.metadata, falling
back to "unknown" when the package is not installed (e.g. running from
source).

* hrw4u: drop --version CLI test

(cherry picked from commit f7ad1a8)
* Withdraw per group metrics when metric_aggregate is raised

metric_aggregate is dynamic and overridable, but the publication
decision is made when a group is constructed and a published metric
name was never removable. A name published while the setting was 0
therefore kept reporting for the life of the process, leaving per group
and per hostname metrics side by side at metric_aggregate 2.

AGGREGATE_ONLY now tombstones the per group names it declines to
publish. A group is rebuilt on the first connection after its count
falls to zero, so the change converges as groups go idle.

* Rename the per hostname MAX metric to current_connection.max

ATS metric names separate a qualifier with a dot, as in
proxy.process.eventloop.time.max, not an underscore. The aggregate added
in apache#13506 has only ever existed on master, so renaming it now costs
nothing.

Also wait for the reconfigure in the retraction autest: http_config_cb
schedules it a second out, so a request made as soon as traffic_ctl
returns is still served by the previous configuration.

* Split the aggregate-only mode into max-only and sums-plus-max

The requirement for the suppressed-per-group mode was a single metric
per hostname, the max, rather than the sums as well. Mode 2 is now that
max alone, and mode 3 is the sums and the max, for when the totals are
wanted too. Mode 1 is unchanged.

The sums are withdrawn the same way the per group metrics are when a
mode stops asking for them.

* Include <utility> for std::forward

for_each forwards its callable but the header got <utility> only through
another include. It compiles today; that is not a property this header
controls.

* Note the Metrics API removals for v11

Metrics.h is installed, so dropping the iterator, find(), createSpan()
and rename() breaks downstream plugins even though nothing in tree used
them. Record the removals and the for_each replacement where upgraders
will look.

* Format test_ConnectionTracker.cc

clang-format only, from merging this file with the one master added.

* Drop the duplicate test source entry

The rebase added test_ConnectionTracker.cc to test_net again; master
already listed it when it added its own tests to that file.

* Name the mode under test in the assertion message

It said AGGREGATE_ONLY, which no longer exists, so a failure pointed at
the wrong configuration. Interpolate the configured value instead.

* Add Metrics::Derived::remove_source

A derived metric is shared by its sources, so a contributor that goes
away cannot unlist it: another source may still be publishing through
that name. remove_source drops one source and unlists the name only when
the last one goes, and re-adding relists it.

* Stop contributing to shared aggregate names instead of unlisting them

The per hostname sums and max are named per hostname, so every group of
that hostname shares them. A group built for a mode that does not publish
them was unlisting names another group was still publishing: two mappings
to one hostname with different metric_aggregate values, which is what
overriding it is for, and the second group hid the first one's aggregate.
Worse with mixed match types, where MATCH_HOST's own metric carries the
same name as the MATCH_BOTH aggregate.

Groups now remove their source instead. The per group names go the same
way, though they have a single source, so the derived pass stops
recomputing a value into a name that is no longer published.

* Document that shared aggregate names are not retracted by one group

The per group and per hostname names now behave differently, and the
earlier text said the last group rebuilt decides whether the sums are
published, which was describing the defect.

* Pair the derived metric listing change with the source change

remove_source unlisted the derived name after releasing metrics_lock,
while add_source relists inside create() before taking it. A concurrent
pair could therefore register a source and then have the unlist land on
top of it, leaving a metric that update_derived keeps recomputing but
nothing enumerates. Doing both transitions under the lock, next to the
mutation that justifies them, closes that ordering.

The in-tree caller was already safe because ConnectionTracker
constructors are serialized by the outbound table lock, but the public
API should not depend on its callers for this.

(cherry picked from commit 6cd0566)
…pache#13700)

ATS 9.2 split "/path;params" into separate path and params components
and hashed them as path + ";" + params. That parsing was removed, so
";params" now stays inside the path, yet the 9.2 hasher still appended
a separator. A path such as "/a;b=1" hashed as "/a;b=1;", a key 9.2
never produced, so the compatibility lookup could not find objects a
9.2 cache stored for such paths. Only add the separator when the path
does not carry one, and pin the equivalence with unit tests.

The fast hash path had the same separator baked in, which made it a
9.2 implementation rather than a canonical one: enabling it through
url_hash_method would have made canonical keys collide with 9.2 keys
and tripped the debug parity assert on the first request. Rename it
to url_CryptoHash_get_fast_92 and leave it reachable only from
url_CryptoHash_get_92, so the invariant holds by construction instead
of by the option staying off.

(cherry picked from commit 281b608)
* hrw4u: admit SESSION_VARS as a denyable section

SESSION_VARS is already checked by visitSessionVarSection like VARS,
but the schema enum and the sandbox section text both listed only
VARS, so a policy denying session variables worked while being invalid
against the published schema. Add a fixture to pin the behavior:
stubbing out the check_section call in visitSessionVarSection fails it
and nothing else.

* Add suggested doc change

(cherry picked from commit 37009bf)
proxy.config.http.cache.try_compat_key_read could find an object under
the previous key but not maintain one: revalidating it could never
succeed, since a 304 cannot be applied to a vector the current key does
not address, and invalidations left the old copy behind. Revalidate a
legacy object's GET without conditional headers so the full response
is stored under the current key, keeping HEAD and range requests
conditional since neither response is stored. Send invalidations to
both keys, serve a plugin update of a legacy object without storing
it, and document what enabling the setting costs.

Deletes now address the URL the lookup used rather than
cache_info.lookup_url, which does not track a redirect follow when the
pristine host header is kept. A purge on a redirect-followed
transaction therefore removes the object that was looked up. This
applies with the compatibility key off as well.

(cherry picked from commit 26a3921)
@cmcfarlen
cmcfarlen force-pushed the 10.2.x-picks-20260921 branch from 47973f6 to a88ba20 Compare September 21, 2026 16:01
@cmcfarlen
cmcfarlen merged commit a88ba20 into apache:10.2.x Sep 21, 2026
14 checks passed
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.

7 participants