Skip to content

[WIP] CAS draft (adopting to CI/CD, not for review / merge) - #2073

Draft
filimonov wants to merge 4422 commits into
antalya-26.6from
cas-gc-rebuild
Draft

[WIP] CAS draft (adopting to CI/CD, not for review / merge)#2073
filimonov wants to merge 4422 commits into
antalya-26.6from
cas-gc-rebuild

Conversation

@filimonov

@filimonov filimonov commented Jul 17, 2026

Copy link
Copy Markdown
Member

Changelog category (leave one):

  • Not for changelog (changelog entry is not required)

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

content addressable storage - draft PR

Documentation entry for user-facing changes

TBD.

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • CAS (content-addressed storage; Antalya only)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Workflow [PR], commit [4b2bb92]

filimonov added a commit that referenced this pull request Jul 17, 2026
…ent_addressed stateless lanes)

Config Workflow check failed with 'Workflows are outdated' for
master.yml, pull_request.yml, pull_request_community.yml,
release_builds.yml. Regenerated via 'python3 -m praktika yaml'.
The regeneration adds the two CAS stateless jobs to the generated
workflows: 'Stateless tests (arm_binary, content_addressed storage,
parallel)' and 'Stateless tests (arm_binary, content_addressed s3
storage, parallel)' (the rustfs-backed lane).

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=927ea142c9cb14759623861eb004261d0b4b1c8f&name_0=PR&name_1=Config+Workflow
PR: #2073

Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 17, 2026
…teless lane

The lane's start_rustfs expected a pre-extracted binary at ci/tmp/rustfs
and failed on CI runners where nothing provisions it (the workflow wipes
ci/tmp on every run). Download the static musl build for the runner
architecture from the RustFS GitHub release (1.0.0-beta.9) when the
binary is absent, mirroring how setup_minio.sh downloads minio/mc.

Validated locally: the beta.9 binary passes the conditional-operation
semantics the CA pool requires (second 'If-None-Match: *' PUT -> 412,
wrong-etag conditional DELETE -> 412, right-etag DELETE succeeds), and
download_rustfs provisions an executable binary end-to-end.

PR: #2073
Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 17, 2026
Fast test fails at cmake generation: 'Target "dbms" links to
ch_contrib::crc32c but the target was not found' — the fast-test job
initializes a limited submodule list that does not include
contrib/crc32c, so the unconditional add_contrib is skipped while the
dbms link line still references the target.

The dependency is dead: it was wired in for per-block CRC32C in the
early CAS run-file format (5f1272c), which was later replaced by
the text record-stream codecs; no source file includes the library
today. Restore the pre-CAS state: crc32c is built only for
google-cloud-cpp, and dbms does not link it.

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=835251f81cb5af73ad9eaa3a835f50f0c8b678db&name_0=PR&name_1=Fast+test
PR: #2073

Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 17, 2026
Fast test builds without SSL and failed on the unconditional
'openssl/evp.h' include in CasBlobHashingWriteBuffer.cpp. Wrap the
OpenSSL-backed Sha256 hashing write buffer and the one-shot digest in
'#if USE_SSL'; on non-SSL builds selecting blob_hash = 'sha256' now
fails closed with SUPPORT_IS_DISABLED. CityHash128 and XXH3-128 blob
hashes are unaffected.

PR: #2073
Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 18, 2026
… test regression)

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=aeb13b24394023fa8cd9d310d4cbcbc308380af1&name_0=PR&name_1=Fast+test
PR: #2073

A CAS parser commit grouped `RELOAD_DICTIONARY`/`RELOAD_MODEL`/
`RELOAD_FUNCTION` with `CONTENT_ADDRESSED_GARBAGE_COLLECTION` into a
format case that prints only the optional disk, dropping the reload
targets: `SYSTEM RELOAD MODEL my_model` formatted as
`SYSTEM RELOAD MODEL` (failed 04117_parser_system_query_variants and
04124_parser_system_query_extra in Fast test). Fold all four types back
into the generic target-printing case (table / target_model /
target_function / disk else-if chain) — for the CA GC command the disk
branch produces the identical output. Both stateless tests verified
locally via clickhouse-local against their references.

Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 18, 2026
…als (arm_tidy, T13 batch 1)

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=aeb13b24394023fa8cd9d310d4cbcbc308380af1&name_0=PR&name_1=Build+(arm_tidy)
PR: #2073

Removes default arguments from all virtual/override methods flagged by
`google-default-arguments` (147 sites: `CasBackend.h` interface,
`IObjectStorage.h`/`S3ObjectStorage.h`, all backend implementers, test
helpers/fixtures) and adds non-virtual convenience overloads on the base
classes that forward the previous default values. Derived classes gain
`using` declarations to unhide the base overloads. Qualified
parent-implementation calls in test fault backends switched to the explicit
3-arg form — the 2-arg form would now route through the base forwarder and
re-enter the derived override virtually (double fault injection; caught by
the battery).

Bulk edits produced by codex (gpt-5.6-luna) per the T13 brief; overload
visibility and qualified-call fixes plus verification by Claude. Battery
919/919 green.

Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 18, 2026
CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=aeb13b24394023fa8cd9d310d4cbcbc308380af1&name_0=PR&name_1=Build+(arm_tidy)
PR: #2073

Semantics-preserving conformance for the remaining flagged classes:
readability-container-contains, readability-isolate-declaration,
google-runtime-int (AWS SDK retry-API overrides keep `long` with targeted
NOLINT — the override contract owns the type), readability-duplicate-include,
cppcoreguidelines-init-variables, cert-msc, modernize-raw-string-literal,
modernize-use-starts-ends-with, bugprone-empty-catch (comments only — no new
behavior), googletest naming, bugprone-argument-comment,
bugprone-optional-value-conversion, bugprone-misplaced-widening-cast
(CasTypes.h site audited: not a real precision bug — the value is
range-validated to 0-5; cast made explicit without value change).
CasRefCowMap's own `contains` keeps its `find` with NOLINT (self-recursion).

Bulk edits by codex (gpt-5.6-luna) per the T13 brief
(.superpowers/sdd/task-13-batch2-report.md); one over-removed include
(PartFolderAccess.h) restored and verification by Claude. Battery 919/919.

Signed-off-by: Mikhail Filimonov <mfilimonov@altinity.com>
filimonov added a commit that referenced this pull request Jul 21, 2026
…n't abort the server (STID 0883)

CI PR#2073 (content_addressed storage lanes) crashed with
"Too large size (9223372036854775870) passed to allocator" (LOGICAL_ERROR,
server abort) running the regression test 04070_no_crash_extreme_compress_block
_size. Root cause: an extreme max_compress_block_size (2^63-1) flows into
ContentAddressedTransaction::writeFile's buf_size and, unclamped, reaches the
CaContentWriteBuffer base-class allocation (Memory::alloc), where checkSize
(>= 0x8000000000000000) fires. The ordinary MergeTree writers clamp compress
-block sizes to 256 MiB (MergeTreeWriterSettings::MAX_COMPRESS_BLOCK_SIZE) for
exactly this reason; the CAS write path received the value unclamped.

Fix: clamp buf_size and adaptive_write_buffer_initial_size to 256 MiB at the CAS
allocation site (both CaContentWriteBuffer ctors), mirroring the ordinary clamp.
New gtest CasContentWriteBuffer.ExtremeBufferSizeIsClampedNotPassedToAllocator
reproduces the exact crash number without the clamp (verified RED) and passes
with it. CA gtest gate 1057/1057.

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=latest&name_0=PR
Related: #2073
filimonov added a commit that referenced this pull request Jul 21, 2026
…cle (UNMOUNT/FSCK)

Design for STID 3982-3b48 (CI PR#2073). Five parts: (1) MountLeaseKeeper's
vanished-backing-store case throws FILE_DOESNT_EXIST (not LOGICAL_ERROR) so a
background renewal thread never aborts the server + new CasMountLeaseLost
counter; (2) SYSTEM CONTENT ADDRESSED UNMOUNT <disk> (shutdown() the pool,
auto-remount on next access) as a clean alternative to rm -rf under a live
mount; (3) online SYSTEM CONTENT ADDRESSED FSCK <disk> via read-only
runFsck(Pool&); (4) rewrite the no-leftovers test teardown to GC RUN -> FSCK ->
UNMOUNT -> rm -rf; (5) rename the offline clickhouse-disks fsck applet to
ca-fsck for consistency. Auto-teardown-on-DROP stays out of scope.

Related: #2073
filimonov added a commit that referenced this pull request Jul 21, 2026
…ence

Task 7 added the SYSTEM_CONTENT_ADDRESSED_FSCK AccessType but only updated the
AccessType.h macro list, not the 01271_show_privileges reference — the test enumerates
every privilege, so the new row (right after MOUNT, matching the AccessType declaration
order) was missing and fasttest failed with a one-line diff. 01271 was not in the local
gtest/FSCK-access test runs, so the gap surfaced only in CI.

CI report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=d780fa7473807203916125e96cf73a9774c84464&name_0=PR&name_1=Fast%20test
PR: #2073

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
filimonov added a commit that referenced this pull request Jul 24, 2026
Add two DESIRABLE items under §3 (GC correctness/observability) surfaced by
triaging the CAS-s3 stateless run (Altinity PR #2073, run 30019911967):

- [RECOVERED-INDEGREE-ATTRIBUTION] the "delete_pending recovered in-degree —
  structurally impossible … investigate" warning is a false alarm (dedup-adopt
  vs condemn TOCTOU, spared, no data loss). Downgrade the GC log to a
  ProfileEvent + Debug and move the real adopt-without-resurrect detector to
  the writer's edge-commit.
- [CONDEMN-GRACE-WINDOW] a cool-down before condemning a just-zeroed blob to
  kill hot-dedup churn (tiny system-log blobs) at the source; flagged
  higher-risk (condemn-timing/ack-floor, TLA-gated, protocol-veto).

Docs-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
filimonov added a commit that referenced this pull request Jul 24, 2026
…GABRT)

CI-confirmed on Altinity PR #2073 (run 30019911967, asan_ubsan CAS-s3
stateless): a mount-lease renewal PUT that times out client-side (ambiguous —
may have applied server-side) gets soft-retried with a stale token, mismatches
against its own bumped body, and falls through MountLeaseKeeper's classifier
(same uuid/epoch, unfenced — none of the 3 cases match) into the base class's
generic LOGICAL_ERROR, aborting the server under ASan. A third variant of the
STID 3982-3b48 family (parts 1a/1b covered vanished/absent-at-release).

Also notes: amd_msan/amd_tsan CAS-s3 stateless jobs in the same run hit the 6h
job timeout with zero artifacts — unknown if same crash-loop or a plain hang.

Docs-only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
filimonov added a commit that referenced this pull request Jul 27, 2026
The unsharded CA-s3 sanitizer lanes do not fit the 6h GitHub job timeout,
and the kill lands after the test loop but before result upload, so the
lane reports NO artifacts at all and praktika's aggregate stays RUNNING
forever. In run 30203355812 (sha e2d04bf): tsan finished all
10990/10990 tests at 5h59m and was killed 16s later during teardown; msan
was at 5824/10990 (53%, steady progress, no hang) when killed; asan_ubsan
passed at 5.4h — too close. This is the full explanation of the
"msan/tsan CA-s3 cancelled at exactly 6h with zero artifacts" pattern
seen in three consecutive runs.

Shard asan_ubsan and tsan 2 ways and msan (the slowest, ~11h projected)
3 ways, following the existing "amd_tsan, s3 storage, parallel, N/M"
convention; ci/jobs/functional_tests.py already parses the N/M batch
token generically. Workflow YAML regenerated with praktika yaml.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=e2d04bfe37eec5af58bde837e1cc89d11016ef6f&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
filimonov added a commit that referenced this pull request Jul 27, 2026
…irectory

The refusal in prepareAdoptFromManifest throws LOGICAL_ERROR, which aborts
the whole process in debug/sanitizer builds instead of behaving like a
catchable exception, so the EXPECT_THROW form killed unit_tests_dbms on
all three sanitizer CI lanes (asan_ubsan/tsan/msan, 0s failures with
"Logical error: 'Relink target ... does not address a content-addressed
part directory of a live table'"). Split it the same way as the
CasWiringOpsDeathTest precedent in this file: EXPECT_THROW stays for
plain release builds, and a CasWiringExchangeDeathTest EXPECT_DEATH
variant proves the same refusals positively abort under
DEBUG_OR_SANITIZER_BUILD.

Verified: build_asan runs the death test (1 test, OK), build (release)
runs the EXPECT_THROW variant (9 tests from 2 suites, all OK).

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=e2d04bfe37eec5af58bde837e1cc89d11016ef6f&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
filimonov added a commit that referenced this pull request Jul 27, 2026
The pre-scrape cleanup removed only filesystem_caches/*/status (one
level), but dynamic cache disks created by tests nest their path — the
binary CA-s3 lane died on filesystem_caches/disks/cache_03517/status
with "Cannot lock file ... Another server instance in same directory is
already running" (StatusFile.cpp flock, EWOULDBLOCK) raised from
FileCache::initialize when clickhouse-local initialized the disk map.
Make the glob recursive so any depth is covered, same mechanism as
before: removing the file lets clickhouse-local create and lock a fresh
inode even when the not-fully-stopped server still holds the old one.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=e2d04bfe37eec5af58bde837e1cc89d11016ef6f&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
filimonov added a commit that referenced this pull request Jul 27, 2026
…lanes

Tag it no-content-addressed-storage: the coverage is the inline
content-addressed disk the test creates itself, so it stays meaningful on
every ordinary lane. On lanes whose DEFAULT MergeTree storage is
content-addressed, system.remote_data_paths (no disk_name pushdown —
the applyFilters TODO in StorageSystemRemoteDataPaths.cpp) also walks
the huge shared default pool with the whole run's data, and on the S3
(RustFS) variant that walk exceeds the 600s test timeout (recurred in
two consecutive runs, plus a "Some queries hung" ride-along). The
BACKLOG pushdown item stays open as the real fix; also note the
ConnectionGroup Disk-session pressure signal from the same run.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=e2d04bfe37eec5af58bde837e1cc89d11016ef6f&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
filimonov added a commit that referenced this pull request Jul 27, 2026
Same class as 5f292e0: PreparedPartWrite's one-shot-terminal rejection
throws LOGICAL_ERROR, which aborts the whole process in debug/sanitizer
builds (Exception.cpp's handle_error_code) instead of behaving like a
catchable exception, so CasPartFolderAccess.PreparedPartWriteRejectsASecondTerminal
killed unit_tests_dbms on all three sanitizer CI lanes (0s failures). The
file's author knew the class (the NETWORK_ERROR/MEMORY_LIMIT_EXCEEDED
choices further down cite it) — these two spots were the leftovers:

- PreparedPartWriteRejectsASecondTerminal: split per the
  CasWiringOpsDeathTest precedent — expectThrowsCode stays for plain
  release builds, a CasPartFolderAccessDeathTest EXPECT_DEATH twin proves
  the same rejections positively abort under DEBUG_OR_SANITIZER_BUILD.
- PreparedPartWriteMoveTransfersTheTerminalDuty: the single moved-from
  abort check is guarded in place (EXPECT_DEATH forks, so the rest of
  the test continues in the parent).

A sweep of all CAS gtest files for unguarded LOGICAL_ERROR throw
expectations found no other gaps (blob_upload_pool, ref_writer,
ref_install_safety, upload_detached, upload_fanout all carry guards).

Verified: build_asan 39/39 with both death tests OK; build (release)
30/30 with the EXPECT_THROW variants.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=8417137aeb842f463066fd68a75b60ffeae30d63&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
filimonov added a commit that referenced this pull request Jul 28, 2026
CasRefInstallSafetyDeathTest.DenyGuardStopsAnAllocation dispatched on
DEBUG_OR_SANITIZER_BUILD, but DENY_ALLOCATIONS_IN_SCOPE itself is gated
on MEMORY_TRACKER_DEBUG_CHECKS (MemoryTracker.h: defined only under
!NDEBUG). Sanitizer builds define NDEBUG, so there the guard compiles to
static_assert(true) and the death test "failed to die" on all three
sanitizer CI lanes — visible verbatim in the CI error output. (These
lanes only now reached this test at all: the earlier LOGICAL_ERROR
aborts in gtest_ca_wiring / gtest_cas_part_folder_access killed the
process before it, so each fix un-shadows the next tail failure.)

Gate the death test on MEMORY_TRACKER_DEBUG_CHECKS instead, and drop the
throw-only EXPECT_ANY_THROW branch as dead code: MEMORY_TRACKER_DEBUG_CHECKS
implies !NDEBUG implies DEBUG_OR_SANITIZER_BUILD, so whenever the guard
exists its LOGICAL_ERROR aborts — death is the only observable outcome
(the old comment claimed the opposite implication).

Verified: build_debug runs the death test (OK, guard fires); build_asan
compiles it out, remaining CasRefInstallSafety* 21 tests x5 repeats green.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=07f8398acddff2c676aded5f804f122984845a4a&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
filimonov added a commit that referenced this pull request Jul 28, 2026
RCA of the fence cascades on the CA-s3 sanitizer lanes (run for
07f8398, msan 2/3 lane): clickhouse-server logged 351 bursts of
'Connection refused' (e.code() = 111) to rustfs at localhost:11121
spread over the whole run — the service stayed up between bursts, so
this is fd/accept exhaustion, not a crash. rustfs was launched WITHOUT
the open-files-limit raise that start_azurite performs for exactly this
failure mode, while the server under parallel sanitizer load holds 10k+
active Disk-group S3 sessions. Most bursts were absorbed by the CAS
request retries (max_attempts=16 / 90s deadline); the one at ~02:00-02:01
outlived (a) in-flight writes' 90s budgets (Code 210 UNCERTAIN) and
(b) the mount-lease renewal confirm window (TTL 30s), so the mount fence
tripped (fail-closed by design) and every writing test failed with
Code 668/210 from 02:02:52 until remount completed ~02:05:00, with
straggler failures to ~02:10 — ~40 of the run's 66 test failures.

Also add rustfs.log to the uploaded artifacts: it was already written to
ci/tmp/rustfs.log but never uploaded (azurite/kafka/minio logs are),
which was exactly the missing evidence for this class of triage.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=07f8398acddff2c676aded5f804f122984845a4a&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky

```sql
SYSTEM CONTENT ADDRESSED GC RUN [ON CLUSTER cluster_name] [disk_name]
```

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GC DISABLE ?

filimonov added a commit that referenced this pull request Jul 30, 2026
… razor

RefWriterStalePrecommitSweep.BoundedBatchesAndInterruptionResumeAcrossMounts
failed on 5 of 6 sanitizer-lane runs across two CI rounds (asan/tsan/msan,
'refLaneWedgedForTest — Actual: false, Expected: true') while passing every
quiet local run. Reproduced locally 14/14 by adding full CPU load with the
test otherwise unchanged and filtered alone — so not test-ordering
contamination, pure timing.

Root cause: the test set attempt_timeout_ms == operation_deadline_ms == 100,
which turns the request controller's pre-send gate (putIfAbsentControlled:
'now + attempt_timeout > deadline' returns Unresolved WITHOUT sending) into
a zero-width race — it only passes when no millisecond tick elapses between
the deadline capture and the gate. This test uniquely burns that window
encoding the ~1700-op removal chunk, so on a loaded or sanitizer-slow
machine the gate fired first, the injected ambiguous fault was never
reached, the sweep failed CLEAN — nothing sent, nothing ambiguous, and the
product CORRECTLY did not wedge the lane. The test had over-specified the
failure mode it would meet.

Ambiguity is guaranteed by max_attempts = 1 alone; widen
operation_deadline_ms to 5000 so the PUT is always actually sent and the
wedge is deterministic on any machine. Verified: 20/20 green (5 repeats x 4
sweep tests) on ASan under half-core CPU load; the sibling wedge tests keep
the tight budget deliberately — their capture-to-gate window is empty.

Report: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=834c9517f56a83927b0c941ac08d8dfd82460579&name_0=PR
PR: #2073

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKmSZa7T87WbRGKkNkSXky
@svb-alt svb-alt added antalya antalya-26.6 roadmap Key features and improvements for Antalya project labels Jul 30, 2026
Comment thread ci/defs/job_configs.py Outdated
Comment thread ci/defs/job_configs.py Outdated
Comment thread ci/jobs/scripts/clickhouse_proc.py Outdated
filimonov added a commit that referenced this pull request Aug 3, 2026
… (PR 2073 review)

Addresses the three review recommendations from strtgbb on
#2073

- All 10 CAS `Job.ParamSet` entries move out of `JobConfigs.functional_tests_jobs`
  into a new `AltinityJobConfigs.cas_functional_tests_jobs`. Every workflow that
  picked them up implicitly -- including `release_builds` (`binary` filter),
  `release_branches` and `backport_branches` (`asan` filters) -- now concatenates
  the two lists in the original order, so the job sets are unchanged. Proven by an
  empty `diff -r` of the regenerated `.github/workflows` and by an identical dump
  of `workflow.jobs` names for every `ci/workflows/*.py` module before and after.
- The CAS ParamSet comments are cut to the load-bearing constraints: RustFS rather
  than MinIO OSS for enforced conditional deletes, and sharded sanitizer lanes
  because an unsharded one exceeds the 6h GitHub job timeout.
- The CAS comments in `ci/jobs/scripts/clickhouse_proc.py` are compressed the same
  way, keeping why the disks are opened read-only, why the substitution is keyed on
  the `<metadata_type>cas</metadata_type>` marker, and why `grep -R` and
  `sed --follow-symlinks` are required.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E1r6FXkwCGRmniKKVfs2ZN
filimonov added a commit that referenced this pull request Aug 4, 2026
CASRefCheckpointJoin.EncodedCkptSizeIsIndependentOfCardinality and
CASRefWriterStalePrecommitSweep.BoundedBatchesAndInterruptionResumeAcrossMounts
failed identically on all three sanitizer unit-test lanes (asan_ubsan, tsan,
msan) at 9ad3e15, thrown from ordinary product code, not a sanitizer
report -- both are the same class of bug `8f9e63c7a19` already fixed once
for the second test: a CPU-bound encode step (committing `MANY_REFS`/
~`ref_txn_max_ops` ref ops in one call) measured against a real-wall-clock
deadline (`mount_lease_ttl_ms` for the first test, `operation_deadline_ms`
for the second), which a slow-enough sanitizer build -- msan above all --
can outrun regardless of how wide the deadline is set.

Both the mount fence and the ref-log request controller (`CasRefLedger`'s
`controller_boot_ms_fn`) already read time through `PoolConfig::boot_ms_fn`,
the same injectable seam a few lines below already uses (the `resumer` pool
in the second test). Freeze it for the pools that do the CPU-bound work
instead of widening the deadline further, removing the race rather than
giving it more room.

Not build-verified: the shared dev box was at 63 GB free disk (existing
full sanitizer builds run 43-51 GB each) and near swap exhaustion, so no
fresh build was attempted. Verified by tracing the exact production wiring
(PoolConfig::boot_ms_fn -> CasRefLedger::controller_boot_ms_fn ->
CasRequestController::now_ms, and CasMountRuntime::bootMsNow) and matching
the pattern already in use for this file's `resumer` pool.

CI: https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2073&sha=9ad3e15b68823633cc1c2a1539292f20de3e7b5e&name_0=PR
PR: #2073

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E1r6FXkwCGRmniKKVfs2ZN
filimonov and others added 30 commits August 24, 2026 16:59
…spec

Review found two defects in rev.2's own construction.

`GcMetaWriter` cannot be a direct member of `Gc`. Its pool size comes from
`store->poolConfig()`, and a direct member is initialized before the
constructor body, where `Gc::Gc` validates a null store, a zero `gc_id` and a
zero `gc_stuck_removal_rounds`. It would dereference a null store before the
first of those checks, breaking behaviour that
`CASGCLease.CtorFailsClosedOnBadArguments` pins. It is held by `unique_ptr`
and created in the constructor body, exactly as `meta_pool` is today and for
the same documented reason.

The destruction test deadlocked as sequenced: the pool's destructor joins its
workers, so a thread that destroys the `Gc` and only then releases the
backend latch blocks inside the join forever. The release is now sequenced by
destruction order instead of by a second thread -- the harness member
declared after the `Gc` is destroyed first and opens the latch, so
destruction has provably begun before the job proceeds, and the release
precedes the join on the same thread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rev.3 replaced a deadlocking teardown test with a destruction-ordered latch
release and called the result deterministic. It is not: the harness member
that opens the latch is destroyed before the `Gc` it sits next to, so the
released worker may finish before `~Gc` runs at all, and a closure capturing
`this` would still pass. The claim is withdrawn rather than patched.

Adding an observable notification inside the destruction boundary would mean
writing a destructor purely for a test hook, on the very class whose lack of
a hand-written destructor is the property being asserted -- and it would buy
a detector that still cannot fail, since the pre-change access is to
destructed-but-allocated storage that no sanitizer reports. The test is
therefore stated as functional coverage: it exercises the pool's join against
an in-flight job and asserts the write landed, and it claims nothing about
ordering. The lifetime regression guard is named as the only thing that
actually is one -- the absence of an entry point accepting a callable.

Also corrects the B2a count: five of the eight branches need an in-window
mutation, not four; fenced-during-adoption needs the seam too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four tasks against the rev.4 spec: extract `GcMetaWriter` and delete the
callback-accepting entry point onto the meta pool, drain that pool on a
round's throwing exit, reclassify the six `claim` conflicts to `ABORTED` with
a test per branch, and gate the result on debug and ASan builds.

The plan marks which steps have a real red-then-green cycle and which do not:
the round-exit drain does, asserted as "the round must not return while a
meta job is held", which is the only formulation that does not race the job's
completion. The ownership test is stated as functional coverage, since the
pre-change access is to destructed-but-allocated storage that no sanitizer
reports; its guard is a grep for the deleted API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review found the plan not executable as written; every finding checked out
against the code.

Two harness blockers. `deleteConfirmedMeta` returns immediately when no meta
object exists, so the confirmed-delete test waited forever for a job that
never reached the latch -- the meta is now seeded first, and the spin loop is
replaced by a bounded wait that fails loudly. And the latch blocked every
blob-meta mutation from construction, including the fixture's own part write,
so it now starts disarmed and is armed once the fixture is built.

The drain test's fixture was also unsound: the outcome log is written from
settled entries, not from fresh condemns, so a round that merely condemns
writes none and the fault never fires. The fixture now drives rounds until
`previewDeletes` reports a pending delete and faults the round that actually
deletes -- the one that schedules a confirmed-meta delete and writes an
outcome log, in that order. The test additionally separates the two ways a
timeout can lie, since blocking in the pre-existing protocol wait looks the
same as blocking in the drain.

Three coverage and compliance gaps: the ownership tests asserted no effect at
all and now assert durable Condemned meta, registry visibility after a drain,
and actual removal; the six conflict tests used a code-only assertion for six
branches that now share one code, and get a message-substring helper; and the
file lacked the required frontmatter and heading anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers item 10 of the CAS pre-release checklist -- findings B3 and B4 of the
second 2026-08-05 umbrella review -- plus the third recommendation triage
made and the item text omits, a `~Pool` that cannot terminate the process.
That one is in scope because the drain increases its exposure: `~Pool` today
runs late and rarely on the shutdown thread, and afterwards runs there on
every shutdown, so fixing the timing alone would raise the frequency of the
crash it removes.

Teardown unpublishes the pool before waiting on anything, since `poolAccess`
hands out a strong reference under `pointer_mutex` and a wait taken while the
pool is still published proves nothing. The guarantee is stated narrowly and
honestly: a successful drain establishes that no TRACKED detached work holds
the pool, not that `~Pool` runs on the shutdown thread, because a `PoolPtr`
handed out earlier can outlive shutdown.

The stop is observed at four points, not one. `settleSnapshotPublish`
re-admits its own successor under the same lock it decrements, so a stop
checked only at the dispatch entry point leaves a chain that outruns the
bounded wait and makes expiry the normal outcome.

The event sinks hold a weak reference and drop counted events on an expired
one, and the two CAS-owned `Context` accessors adopt `getZooKeeperLog`'s
shape -- holding `mutex_shared_context` across the nested lock and the copy,
since a bare ternary would leave the race it appears to remove.

In `~Pool` all three phases are guarded and `drained` defaults to false: the
clean-release marker is a successor's proof that no in-flight conditional PUT
can still land, so a swallowed drain failure must never be able to forge it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six defects, each checked against the code before accepting.

The farewell ordering was described backwards. `MountLeaseKeeper::terminate`
does its `putOverwrite` FIRST and emits `MountRelease` last, so the marker is
already durable when the emit crashes; the original review's "the crash also
eats the farewell write" described an older tree. Overstating the cost would
misdirect the next reader, so the claim is removed and the correction stated.

The RAII pin as specified could not compile: `startThreadFromGlobalPool`
takes `std::function<void()>`, so a captured pin must be copy-constructible.
It becomes a copyable single-release guard -- an aliasing `shared_ptr` whose
control block owns the `Pool` reference and releases the count once, on the
last copy -- with `pin_owner` renamed to `try_pin_detached_work` so that a
refusal after stop is part of its signature.

The counter and its test disagreed: after `resetSharedContext` on a live
`Context` the weak reference still locks and the accessor merely returns
nothing, so no "expired" counter fires. The three outcomes are now tabulated
and only a genuinely expired reference is counted -- an unconfigured system
log is the ordinary steady state and counting it would make the counter mean
nothing.

Two sections contradicted each other about `~Pool`; explicit teardown is now
described as the usual place the last reference is released, never the
guaranteed one. `~Pool` under `pointer_mutex` was listed as out of scope
while the new sequencing actually closes it, so it is claimed as a
consequence instead. And a bare weak `Context` could not tell "never
supplied" from "already released", so the member is an optional weak
reference: `nullopt` disables the integration, an expired supplied reference
fails startup rather than silently degrading.

Also: real anchor links instead of literal tags, one verified commit instead
of per-claim line numbers, an Observability section naming both counters, an
idempotent teardown helper, and tests specified without sleeps and with the
`std::terminate` check as a death test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five corrections, each checked against the code.

`pin_owner` is not a `Pool` method: it is `CasRefLedger`'s injected
`std::function<std::shared_ptr<void>()>` member. `Pool` gains
`tryPinDetachedWork` and the ledger's callback is renamed to
`try_pin_detached_work`; its existing return type already carries the
refusal contract. Admission is also specified exception-safe -- build the
token unarmed, then check, increment and arm under the mutex -- since a count
leaked by an allocation failure makes every later drain run to its deadline
and report a timeout that never resolves.

`Context::shared` is a raw `ContextSharedPart *`, so a strong `ContextPtr`
never owned the shared part and the neighbouring comment about ownership is
not what the CAS reference violates. What it defeats is the surrounding
intent to destroy the `Context` explicitly, which the spec and its test now
say instead.

The guarantee no longer implies ordinary `PoolPtr` holders are ordered
against `shutdown` -- nothing drains them; they are short-lived by
expectation and explicitly outside the guarantee. The fail-soft rationale no
longer claims a frequency increase: `~Pool` runs once either way, and the
point is that this design moves it into managed shutdown while a timeout
keeps the old late path reachable.

Observability said two warnings carry the same facts as two counters. They do
not: the drain timeout has both, an expired context has only a counter --
the logging machinery is what has gone away -- and the clean-release warning
belongs to a different condition. A timeout is also restated as the guarantee
not being ESTABLISHED, since it is conditional on a successful drain.

The destructor test becomes a subprocess exit test with `EXPECT_EXIT`: after
the change the subprocess exits normally, which is not what `EXPECT_DEATH`
expresses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rk spec

Two architectural gaps, three unsound details, verified against the code.

The ledger had no way to read the stop flag. Two of the four checkpoints --
`runtime_still_admitted` and `settleSnapshotPublish` -- must ask whether
stopping has begun without taking a pin, and taking one to read a flag would
create the very work the caller is avoiding. The ledger gains a second,
read-only injected callback wired to a new `Pool::detachedWorkStopping`.

A refused or failed pin now rolls back the publisher's admission.
`admitSnapshotPublishUnderStateLock` increments `pending_snapshot_publishes`
before the dispatch, and only the dispatched thread decrements it, so any
path that launches no thread strands the count -- and remount quiescence, the
cache budget and `dropNamespace` all block on it reaching zero.

The stop must also cancel in-flight recovery. `ensureRefTableRecovered` runs
before `runtime_still_admitted` exists and retries transient failures for
`recovery_retry_budget_ms`, 120 seconds by default, against a drain deadline
measured in seconds. The existing per-runtime `recovery_cancel_requested` is
reused rather than duplicated, with one difference stated: self-remount
clears the flag, shutdown must leave it latched.

`startup` now resolves the weak context once and holds it, since two separate
resolutions can straddle an expiry and leave a mount half-configured. The
fail-soft guards must guard their own logging: `tryLogCurrentException`
allocates, and memory pressure is exactly when a teardown phase throws. The
drain deadline is a derived bound only for the budget-controlled snapshot
writes; for the raw diagnostic GET it is insurance.

Tests added for the two destructor paths the design introduces: implicit
destruction without `shutdown`, and destruction after it, for idempotence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The detached-work design is rebuilt around one entry point,
`Pool::tryDispatchDetached`, instead of exporting a pin and a stopping
predicate. The rollback does not disappear -- a single-flight reservation
taken under `state_mutex` necessarily precedes a fallible launch, so
compensation is required -- but it becomes one RAII guard released into the
launched task, so refusal-after-stop, allocation failure and launch failure
share a single path. The task receives a stop token, which removes the need
for a second read-only callback: both remaining checkpoints run inside the
task.

A failed dispatch now never fails its caller. `dispatchSnapshotPublisher` is
reached from read-side maintenance and from settlement's re-dispatch, and the
existing catch states the contract: a background publish must never fail an
otherwise-successful read or mutation. rev.4's "let it propagate" contradicted
that.

Recovery cancellation becomes latch-only for shutdown.
`cancelRecoveriesAndAwaitQuiescence` waits on `recovery_cv` with no deadline
and then clears the flag, so calling it from a bounded drain would block
before the bounded wait -- preventing the very timeout warning and counter
that report the failure -- and would unlatch a terminal cancellation. The
publish step is factored out; shutdown latches and lets the registry's single
bounded wait do the waiting. Recovery reads are also direct backend calls, so
the request budget does not bound them either; that is now stated.

The reset-context test moves into a subprocess exit test for the same reason
the destructor test already was: pre-change it dereferences null and takes the
binary down. A test for terminal cancellation unparking a publisher inside
recovery is added -- otherwise the new critical branch has no coverage.

All three documents now use the mandated strict gate filter `CAS*`, and the
item-9 suites are renamed to match. `Cas*` would have dragged in
`CascadeWriteBuffer` and, worse, masked exactly the misnaming it was hiding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reservation accounting. `settleSnapshotPublish` decrements and re-admits
under one `state_mutex` hold precisely so no observer sees a transient zero,
so a copyable guard that also decremented would double-decrement a successful
publish or erase a re-admitted reservation -- false quiescence, then a
negative count. The guard becomes scope-local and non-copyable, armed only
around the fallible dispatch and disarmed on success, so exactly one path
owns each decrement. Being local, it also covers the allocation that happens
while constructing the task argument, which the dispatcher itself cannot: that
throw occurs in the caller's expression, before the method body is entered, so
"never throws" was a promise the signature could not keep.

Reference release ordering. A zero in-flight count did not imply the `Pool`
reference was gone: a strong reference living in the callable's captures dies
with the `std::function`, after the count may already have reached zero, so
shutdown could observe zero and `~Pool` still run on the worker. The registry
state is now separately owned and outlives the `Pool`, and a lease releases
its `PoolPtr` BEFORE decrementing.

Recovery cancellation. Reusing `recovery_cancel_requested` was wrong twice
over: `cancelRecoveriesAndAwaitQuiescence` unconditionally clears it after an
unbounded wait, and the remount worker is still live during the drain, so a
latched terminal cancellation can be lost; and setting it would not unpark a
sleeping publisher anyway, since `recovery_retry_sleep_fn` exits early only on
a dropped fence, which a terminal shutdown does not cause -- 30s of backoff
against a deadline of seconds. The stop token is threaded into recovery
instead, including its retry sleep's slice loop, which is already built to be
interruptible and merely polls the wrong predicate. Shutdown no longer touches
remount's mechanism at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Keep `GcMetaWriter::drain` as the throwing protocol barrier and add `drainOnExitNoThrow` for the scope guard. Guard the cleanup diagnostic so an unwinding round exception is preserved.

Move the confirmed CAS unit-suite escapes under the strict `CAS*` gate and pin the cleanup `noexcept` signature at compile time.
The task lease must be a copyable handle that completes once: the task
travels through `std::function<void()>` and is copied, so a move-only lease
does not compile and a plainly copyable one releases per copy. A
`shared_ptr` control block performs the release order on last destruction.
Relatedly, neither task body may keep a `PoolPtr` of its own -- the
diagnostic lambda's `self` capture dies with the `std::function`, outside the
lease's control, which is precisely how a released count can coexist with a
live reference.

Settlement in the launched task becomes unconditional. It is reached today by
a bare call after a handler that calls `tryLogCurrentException`, which
allocates and can throw under memory pressure; the reservation is then
stranded forever and `quiesceRefTablesForRemount` and `dropNamespace` wait on
a count that never reaches zero. A stranded reservation is worse than the
crash this spec is about, because nothing reports it.

The diagnostic dispatch keeps a caller-side try/catch even though it needs no
rollback guard: it is called from a fail-closed path, and an allocation
failure while building the task must not replace the exception the caller was
already raising.

Recovery must observe the stop token in four places, not one. Two sit above
every checkpoint previously listed: `recovery_cv.wait` is called with no
predicate and no deadline while another recovery is in progress, and a fresh
recovery reads the checkpoint before its first admission check.

Also corrects `waitForSnapshotPublishSettle` to its real name,
`waitForSnapshotPublishSettleForTest`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six tasks against rev.7 of the spec. The three properties that decide
correctness each get a failing-first test rather than more prose: a zero
in-flight count implies the pool reference is already released; exactly one
path owns each reservation decrement; and a teardown unparks a publisher
wherever it can park, including a concurrent-recovery wait with no predicate
and a backoff sleep that today only watches the mount fence.

The plan marks which steps have a real red and of what kind -- task 1 opens
with a build failure rather than a test failure, and the two context and
destructor cases are subprocess exit tests because pre-change they take the
whole binary down.

Suite naming is called out as a gate rather than a style rule: the CAS gate is
exactly `CAS*`, and a suite that does not match never runs in it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Remove the dead `CountingBackendShape` exclusion now that the suite is registered as `CASCountingBackendShape`, and close the two matching backlog records with strict gate evidence.
The previous version was an outline. This one carries the test code, the
hook definitions and the implementation bodies, and separates every build,
log analysis, test run and commit into its own step so no stage can run a
stale binary or commit after a failure. Test logs are analyzed by a subagent
exactly as build logs are.

Four technical corrections came out of review. A predicate on
`rt.recovery_cv` cannot wake anyone, because the drain notifies a different
condition variable -- the concurrent-recovery wait becomes a bounded slice
wait, mirroring the sliced backoff beside it, and a token-stopped attempt is
terminal so the transient classifier cannot re-drive the walk. The
first-request test no longer demands the impossible: a stop cannot interrupt
an in-flight GET, so the publisher is parked at a pre-request hook and the
assertion is that the request is never issued; an already-started request is
a bounded timeout by design. Admission allocates before it counts, so an
allocation failure cannot strand a count that nothing will decrement, and the
release-ordering test asserts at an injected boundary hook rather than racing
a post-hoc use_count. And the timeout counter is asserted only from the task
that introduces it, with `src/Common/ProfileEvents.cpp` in the same commit as
each counter's first use.

Task boundaries are also redrawn: dispatch rewiring is its own task rather
than a step, so the publisher's rollback guard and unconditional settlement
are reviewable on their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…; six duplicates)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…ntry and the pinned beta S3 image

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
…beta rustfs image)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HKgdqVjZwkpWPxLyHzduPb
Task order is now a stated safety property: the fail-soft `~Pool` lands
BEFORE the teardown sequencing that makes `~Pool` routine, so no intermediate
commit reaches a throwing teardown phase more predictably while it is still
unguarded. The teardown helper guards each phase separately, since one `try`
around all of them would let a throw from stopping the GC skip the part-access
release and the drain itself.

Three symbols in the previous version did not exist. Recovery gets its own
token-aware sleep seam rather than the shared single-parameter
`setCasRetrySleepForTest`, which also feeds the request controller and has a
dozen call sites; the waiters accessor is `refRecoveryWaitersForTest`; and the
publisher's error hook is an injected ledger callback, because the ledger's own
`config` member is a `RefLedgerConfig` and a `PoolConfig` field is unreachable
from there. The metadata-storage fixture reference is corrected to
`gtest_cas_operation_gate.cpp`, which actually builds one.

The two handshake tests were races: opening a gate from another thread could
beat the drain, so a correct implementation could record `false` and a broken
one pass. Both now run the drain on its own thread and poll an observable stop
latch before releasing the worker. The publish-failure test faults the backend
unconditionally instead of leaving it optional -- with a healthy backend the
error handler is never entered and the test proves nothing. The token now
threads through `runRecoveryWalkOnce` and `checkRecoveryStillAdmitted`, not
only the first request, since the walk issues more I/O after that check. A
deliberately expired drain asserts the timeout counter, which no previous test
could observe.

Task 3 also adds its seams in a separate, green build step before the
behaviour tests, so its runtime red is observable rather than masked by a
compile error.

The command templates no longer append `; echo "$?"`: that makes the command's
exit status the echo's, so a failed build reports success to anything gating on
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

antalya antalya-26.6 roadmap Key features and improvements for Antalya project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants