Remove orphan cache file on non-ErrnoException download failure#110549
Conversation
FileSegment::write() creates the cache file before any bytes are accounted for. When the write fails, only the ErrnoException catch block removes the just-created empty file (when downloaded_size == 0). The generic Exception and fs::filesystem_error catch blocks (added in 2024) never got the same removal, so a non-Errno failure after the file is created leaves an empty orphan on disk. The failing segment is later reset to EMPTY while the orphan persists, and the debug/sanitizer cache self-check (assertCacheCorrectness) then aborts with LOGICAL_ERROR "Expected file ... not to exist". Concretely reproduced by OPTIMIZE TABLE on an Iceberg table: compaction reads Avro manifests through the filesystem cache with background download enabled; a manifest being background-downloaded is concurrently rewritten by the compaction, so the background read fails with a non-ErrnoException. Mirror the ErrnoException orphan-removal into the two non-Errno catch blocks. The noexcept fs::remove overload is used so the original download error is not masked. Adds a cache_filesystem_failure_non_errno failpoint and a regression test that reproduces the exact assertion (background download: 1). Closes: ClickHouse#110532 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-PR validation gate (click to expand)
Session id: cron:clickhouse-worker-slot-4:20260715-112800 |
|
Workflow [PR], commit [e3dd72c] Summary: ❌
AI ReviewSummaryThis PR mirrors the empty-orphan cleanup into the non- Tests
Final Verdict
|
The CI style checker (ci/jobs/check_style.py) rejects stateless test filenames containing 'fail'. Rename 04545_filesystem_cache_orphan_file_non_errno_failure to 04545_filesystem_cache_orphan_file_non_errno. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| # file was created. The read is expected to fail; we only care that no orphan file | ||
| # survives. | ||
| ${CLICKHOUSE_CLIENT} -q "SYSTEM ENABLE FAILPOINT cache_filesystem_failure_non_errno" | ||
| ${CLICKHOUSE_CLIENT} --enable_filesystem_cache=1 --read_from_filesystem_cache_if_exists_otherwise_bypass_cache=0 \ |
There was a problem hiding this comment.
Because .sh tests are executed without set -e unless the script opts in, this SELECT can fail or succeed and the script will still continue. That makes the regression weak: if the failpoint stops hitting this path, the test goes green without ever proving the non-ErrnoException branch ran.
Please make the expected failure explicit here, for example by inverting the command with if ...; then echo "expected failure"; exit 1; fi (and optionally checking that the error text is the injected one).
| { | ||
| /// A non-ErrnoException failure (e.g. a generic Exception thrown by the | ||
| /// underlying object-storage read) after the cache file was created. | ||
| throw Exception(ErrorCodes::CANNOT_READ_ALL_DATA, "Failpoint: simulated cache non-errno failure"); |
There was a problem hiding this comment.
Let's make it MEMORY_LIMIT_EXCEEDED, it will be more plausible than CANNOT_READ_ALL_DATA
Per review: MEMORY_LIMIT_EXCEEDED is a more plausible real-world cause of a non-ErrnoException background-download failure than CANNOT_READ_ALL_DATA. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Done in 9ca22ed: the non-errno failpoint now throws |
CI finish ledger — 9ca22edCI fully finished (Finish Workflow pass). The only red job is the fleet-wide host-OOM window; not PR-caused.
|
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered: 21/29 (72.41%) · Uncovered code |
c3c2bb3
Closes: #110532
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fix a logical error in the filesystem cache (
Expected file ... not to exist) that could occur when a background cache download failed with a non-ErrnoException(for example whileOPTIMIZE-ing an Iceberg table), leaving an empty orphan cache file behind.Description
FileSegment::write()creates the cache file before any bytes are accounted for. On a write failure, only theErrnoExceptioncatch block removed the just-created empty file whendownloaded_size == 0. The genericExceptionandfs::filesystem_errorcatch blocks never got the same cleanup, so a non-ErrnoExceptionfailure after the file was created orphaned an empty file. The segment is later reset toEMPTYwhile the file persists, andassertCacheCorrectness(debug/sanitizer builds) then aborts withLOGICAL_ERROR.Same assertion as the closed #99195, but reproduced on current master with a concrete trigger and
background download: 1.The fix mirrors the existing
ErrnoExceptionorphan-removal into the two non-Errno catch blocks, using the noexceptfs::removeoverload so the original download error is not masked. Acache_filesystem_failure_non_errnofailpoint and a stateless regression test reproduce the exact assertion.cc @kssenii