Skip to content

fix: avoid "No clang found" when the clang cache is deleted by a parallel request - #6649

Merged
gharlan merged 1 commit into
5.xfrom
fix-clang-cache-race
Aug 31, 2026
Merged

fix: avoid "No clang found" when the clang cache is deleted by a parallel request#6649
gharlan merged 1 commit into
5.xfrom
fix-clang-cache-race

Conversation

@gharlan

@gharlan gharlan commented Aug 31, 2026

Copy link
Copy Markdown
Member

Fixes #6648

Problem

rex_clang::checkCache() used a check-then-read pattern:

if (!is_file($file)) {
    rex_clang_service::generateCache();
}
foreach (rex_file::getCache($file) as $id => $data) {

rex_delete_cache() wipes the whole cache directory. If that happens between the is_file() check and the read, file_get_contents() fails, rex_file::getCache() returns its empty default [] — and checkCache() still sets cacheLoaded = true. The request then runs with zero languages.

The visible symptom is LogicException: No clang found. from getStartId(), which boot.php evaluates on nearly every request (rex_request('clang', 'int', rex_clang::getStartId())) — hence the reports of the error hitting completely unrelated pages. Less visible, but worse: exists(), get() and getAll() silently return wrong results in the same situation.

Note that the "reading while another request writes" theory from the issue does not apply — rex_file::put() already writes atomically via tempnam() + rename().

Fix

Read the file first and fall back to a freshly generated cache when the result is empty. rex_clang_service::generateCache() now returns the data it generated, so the fallback does not read the file back from disk and cannot lose the same race a second time. This mirrors the read-first pattern already used by mediapool and structure.

As a side effect, an empty or truncated cache file no longer leads to foreach over null.

The exception in getStartId() still fires when the clang table really is empty.

Compatibility

generateCache() gained a return value (previously @return void); callers are unaffected.

Testing

  • Manually verified with a deleted and with a truncated clang.cache: the cache is regenerated and the languages load correctly.
  • composer phpstan, composer psalm, composer cs clean; no baseline changes.

@rex-bot rex-bot added the bug label Aug 31, 2026
@gharlan gharlan added this to the 5.21.5 milestone Aug 31, 2026
@gharlan

gharlan commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@skerbis Da nicht so direkt reproduzierbar bei dir, ist es wahrscheinlich nicht so leicht testbar für dich, oder? Dann würde ich es einfach so mergen und hoffen, dass es tatsächlich hilft.

@skerbis

skerbis commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

@gharlan danke. ja es ist schwer zu testen.

…llel request

`rex_clang::checkCache()` checked the cache file with `is_file()` and read it
afterwards. If a parallel request cleared the cache in between, the read failed
silently, `rex_file::getCache()` returned its empty default and the clang list
stayed empty for the whole request - which surfaces as `LogicException: No clang
found.` in `getStartId()`, and as silently wrong results in `exists()`/`getAll()`.

Read the file first and fall back to a freshly generated cache if the result is
empty. `rex_clang_service::generateCache()` now returns the generated data, so
the fallback does not have to read the file again and cannot lose the same race.
@gharlan
gharlan force-pushed the fix-clang-cache-race branch from bcf97c0 to cdf4931 Compare August 31, 2026 15:14
@gharlan
gharlan merged commit 731bbce into 5.x Aug 31, 2026
18 checks passed
@gharlan
gharlan deleted the fix-clang-cache-race branch August 31, 2026 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

rex_clang::getStartId() wirft sporadisch "No clang found" bei parallelen Requests direkt nach Cache-Clear

3 participants