Cherry-picks for 10.2.1 (2026-09-21) - #13709
Merged
Merged
Conversation
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
force-pushed
the
10.2.x-picks-20260921
branch
from
September 21, 2026 16:01
47973f6 to
a88ba20
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cherry-picks for the 10.2.1 release, taken from the
For v10.2.1column of theATS 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.
--versionCLI flagi == 0case in HRWSimpleTokenizer;paramsPicked in master merge order. Every commit carries a
cherry picked from committrailer.
Conflict resolutions
Four picks needed hands. In each case the release branch's own idiom was kept and
only the upstream fix applied.
#13514 —
HttpProxyServerMain.cc. 10.2.x guards probe-acceptor constructionwith
needs_probe(master does not). Kept the guard, applied the&portargumentchange inside it.
#13645 —
Log.cc. Master changed thesshvandcsshvlog fields fromdINTtoSTRINGin a commit that is not on 10.2.x, and the auto-merge draggedthat behaviour change in. Kept 10.2.x's
dINTand took only theregister_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.
#13666 —
test_ConnectionTracker.cc. The file was added to master by #13516(not picked), so 10.2.x has neither the file nor its
CMakeLists.txtentry.Kept the helpers and the
ConnectionTracker aggregate metric publicationtestcase that #13666 actually contributes, and added the one build-wiring line so it
runs. Dropped #13516's
Connection tracker server match conversiontest case:its "invalid values are clamped" section asserts clamping that 10.2.x's
SERVER_MATCH_CONV.store_intdeliberately does not do (the clamp is commentedout there), so importing it would have failed the build's test suite.
#13700 —
URL.cc/test_URL.cc/ink_ascii_tolower.h. The header comesfrom #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_tolowerhelper where master includes that header — kept the helper, took the new comment.
Four SIMD-oriented uppercase-host fixtures in
test_URL.ccare likewise #13320's,not this commit's, so they were left out. #13700's own
;paramsequivalence testsare all present.
Not picked
Three project items at
For v10.2.1are still open on master and so wereskipped: #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.